diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000000..e69de29bb2 diff --git a/404.html b/404.html new file mode 100644 index 0000000000..d51ab9dd0a --- /dev/null +++ b/404.html @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + +
There’s nothing here.
+ + + diff --git a/CNAME b/CNAME new file mode 100644 index 0000000000..878bbaa28b --- /dev/null +++ b/CNAME @@ -0,0 +1,2 @@ +pulsar-edit.dev +www.pulsar-edit.dev diff --git a/about.html b/about.html new file mode 100644 index 0000000000..40efea551a --- /dev/null +++ b/about.html @@ -0,0 +1,223 @@ + + + + + + + + About Us | + + + + + + +

About Us

Less than 1 minute

About us

The team

The team behind Pulsar is a community that came about naturally after the announcement of Atom's Sunsetopen in new window and decided that they needed to do something about it to keep their favorite editor alive.

This is a true community-led project to modernize, update and improve the original Atom project into a contemporary, hackable and fully open editor.

Pulsar is all of us, feel free to contribute, discuss, answer questions and suggest ideas in any of our community areas.

The goals

  • Community developed, led and focused
    • Pulsar is being made by a community who came together from the stellar remnants of Atom. A community that wants to build upon the huge legacy that was left and make a uniquely hackable editor.
  • To continue and build upon the legacy of the Atom text editor which has been sunsetopen in new window.
    • This means not only supporting the editor itself but also the package repository with its thousands of community contributions.
  • Update core technologies to bring the editor up to date.
    • Core technologies such as Node.js, Electron etc., keeping them up to date so new features and libraries can be used and added without hacky workarounds.
  • Emphasize the elements that make the editor great to really make Pulsar stand out from the crowd, not only for ex-Atom users but for everyone.
+ + + diff --git a/assets/20221112-Daeraxa-ExamplePost.html.006b41d6.js b/assets/20221112-Daeraxa-ExamplePost.html.006b41d6.js new file mode 100644 index 0000000000..b8710f3122 --- /dev/null +++ b/assets/20221112-Daeraxa-ExamplePost.html.006b41d6.js @@ -0,0 +1 @@ +import{_ as l,o as s,c as a,e as t,a as e}from"./app.0e1565ce.js";const o="/assets/test.a0002333.png",n={},i=e("hr",null,null,-1),c=e("p",null,"title: Example Post author: Daeraxa date: 2022-11-12 category:",-1),r=e("ul",null,[e("li",null,"website"),e("li",null,"testing tag:"),e("li",null,"new feature"),e("li",null,"placeholder sticky: false star: false article: false")],-1),_=e("hr",null,null,-1),h=e("p",null,"This is the excerpt.",-1),u=e("p",null,"This is the body of the article.",-1),m=e("p",null,[e("img",{src:o,alt:"image"})],-1);function p(d,f){return s(),a("div",null,[t("Everything below this line is the example post. The above is necessary just to hide it completely from the website"),i,c,r,_,h,t(" more "),u,m])}const g=l(n,[["render",p],["__file","20221112-Daeraxa-ExamplePost.html.vue"]]);export{g as default}; diff --git a/assets/20221112-Daeraxa-ExamplePost.html.20d587f5.js b/assets/20221112-Daeraxa-ExamplePost.html.20d587f5.js new file mode 100644 index 0000000000..492b139cfe --- /dev/null +++ b/assets/20221112-Daeraxa-ExamplePost.html.20d587f5.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-f7f18754","path":"/blog/20221112-Daeraxa-ExamplePost.html","title":"Example post","lang":"en-US","frontmatter":{"title":"Example post","article":false},"excerpt":"\\n
\\n

title: Example Post\\nauthor: Daeraxa\\ndate: 2022-11-12\\ncategory:

\\n\\n
\\n

This is the excerpt.

\\n","headers":[],"git":{"updatedTime":1668308645000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":8}]},"readingTime":{"minutes":0.21,"words":63},"filePathRelative":"blog/20221112-Daeraxa-ExamplePost.md"}');export{e as data}; diff --git a/assets/20221127-confused-Techie-SunsetMisadventureBackend.html.181ef131.js b/assets/20221127-confused-Techie-SunsetMisadventureBackend.html.181ef131.js new file mode 100644 index 0000000000..6f8be6241d --- /dev/null +++ b/assets/20221127-confused-Techie-SunsetMisadventureBackend.html.181ef131.js @@ -0,0 +1,4 @@ +import{_ as i,o as s,c as r,e as h,a as e,b as t,d as o,f as n,r as l}from"./app.0e1565ce.js";const d={},c=e("p",null,"What's the natural response to finding out an application you use is being sunset? Spend the next several months creating a complex backend infrastructure of course!",-1),u=e("p",null,"In this blog post, which is my first so apologies if the style is widely inconsistent with everything else, I hope to get a place to talk about the journey that's occurred after the announcement of Atom's Sunset and the larger-than-assumed effect it has had on my life.",-1),m={href:"https://github.blog/2022-06-08-sunsetting-atom/",target:"_blank",rel:"noopener noreferrer"},p=e("p",null,"Now to give some background on where I was, I've never actually looked at the Atom source code. I used it every day, was the first thing I installed on new computers. But I liked it as a tool, and never paid much mind to how it was internally structured. But one of my favourite things was packages. The ability to install a package that can do nearly anything has always been amazing to me. I know many other applications can do this, but with this one feature Atom was everything I had ever wanted out of a text editor, and more so could be anything I ever want in the future.",-1),g=e("p",null,"So I knew with the announcement that this would mean I wouldn't be able to install packages. So right away I did what anyone would, find out how to keep packages going.",-1),w=e("h3",{id:"the-archival",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#the-archival","aria-hidden":"true"},"#"),t(" The Archival")],-1),f={href:"https://github.com/confused-Techie/AtomPackagesArchive",target:"_blank",rel:"noopener noreferrer"},b={href:"https://github.com/atom-community",target:"_blank",rel:"noopener noreferrer"},y=e("p",null,"Now I'll skip over the details about Atom Community, and the eventual split that occurred and focus on what my role was in the programming aspect.",-1),k=e("code",null,"+4 | -1",-1),v={href:"https://github.com/confused-Techie/AtomPackagesArchive/pull/1",target:"_blank",rel:"noopener noreferrer"},_=e("code",null,"confused-Techie/AtomPackagesArchive",-1),I=e("h3",{id:"what-to-do-with-this-data",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#what-to-do-with-this-data","aria-hidden":"true"},"#"),t(" What to do with this Data")],-1),A=e("p",null,"So now armed with a few thousand JSON files of Atom's Archived data, I had to figure out what made their backend tick. And since the Atom Package Repository is seemingly the only part of Atom that wasn't open source, I had nothing to base this on at all. So I just started hammering their servers.",-1),T=e("p",null,"Hitting every single endpoint that was in the documentation with each variance of query parameters and HTTP Methods that I could think of. To see what broke, how it broke, and what it says when it breaks.",-1),S={href:"https://github.com/confused-Techie/atom-community-server-backend",target:"_blank",rel:"noopener noreferrer"},N=e("code",null,"atom-community-server-backend",-1),x=e("code",null,"GET atom.io/api/packages/:invalidName",-1),B=e("code",null,'{ message: "Not Found" }',-1),P=e("em",null,"but",-1),O=e("code",null,"GET atom.io/api/users/:invalidName/stars",-1),j=e("code",null,'{ message: "Not found" }',-1),R=e("code",null,"confused-Techie/atom-community-server-backend-JS",-1),G={href:"https://github.com/confused-Techie/atom-backend",target:"_blank",rel:"noopener noreferrer"},J=e("code",null,"confused-Techie/atom-backend",-1),H=e("h3",{id:"the-misadventures-part",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#the-misadventures-part","aria-hidden":"true"},"#"),t(" The Misadventures Part")],-1),q=e("p",null,"At this point speed was picking up, there was so much to do and so little time. Rapid fire code was being written and things were being tested.",-1),z=e("p",null,"One thing that needed to be done was to take this mess of data I had archived and check it all. The most important thing I wanted to do, checked if everything was still valid, that is see if the GitHub repo each package pointed to still existed.",-1),C=e("p",null,"Now in doing this, I ended up needing to look up a repo on GitHub many times. So many times I accidentally got my IP blocked from them. A huge oversight on my part, and not at all an issue with them, but I remember the stress that was caused when attempting to work together with several others on GitHub while being blocked by GitHub.",-1),E={href:"https://github.com/confused-Techie/atom-package-migrator",target:"_blank",rel:"noopener noreferrer"},L=e("code",null,"confused-Techie/atom-package-migrator",-1),M=n("

Now this of itself was a feat, considering the following:

But from there we now had 11,074 packages that we needed to keep safe and find a way to distribute to any user of the new Atom, wherever that ended up being.

At this point, the other hardest part was that the Atom website for lack of better words, was on its last legs. With constant errors, crashes, and generally just failing to respond, it was clear things were nearing an end. Even worse was that when it would work, it was inundated with thousands of Spam packages. In the days after my initial archive, the original Package Repository had ballooned with spam packages now with their total packages totaling in... well. I just can't quite say. Even now during the time of writing I've spent ten minutes refreshing Atom.io/packages and it won't stop timing out or returning 500 errors. But for the sake of my point, I've kept trying, to confirm that the original Package Repository has ballooned with spam packages now to contain 405,137 Packages. But remember when I did my initial archival there were only 12,470. Just to convey how much spam this is, and how ridiculous it is that the service turned into this immediately after the announcement.

But the last hardest part of this stage was the problems that I created for myself.

Now you see, up until this point I've never had to worry about handling this much data. It wasn't until it was too late did I find out that my methodology was ill-fitting. I had written a nearly complete feature parity Backend for the Atom Package Repository, that expected all of its data to be JSON. Now anyone that's done this before will say that was a dumb choice, but forgive me for only being smaller scale prior. The excitement of being nearly done immediately faded, when I got the new backend running in the cloud, and threw all of my data at it. Every single package.

",6),W=e("code",null,"/api/packages",-1),D={href:"https://github.com/confused-Techie/atom-backend/commit/f792a4975f932f20528b871e189300bd97585dac",target:"_blank",rel:"noopener noreferrer"},V=e("code",null,"f792a4975f932f20528b871e189300bd97585dac",-1),Q=n(`
// One note of concern with chaining all of these together, is that this will potentially loop
+// through the entire array of packages 3 times, resulting in a
+// linear time complexity of O(3). But testing will have to determine how much that is a factor of concern.
+

Now I'm no mathematician and my understanding of Time Complexity is best described as weak, but I knew it could be a bad idea. And boy was it.

Resuming after I had pushed everything to the cloud and hit my endpoint, I got no response. Or rather I thought it was no response. The page took well over 300 seconds before most often it would time out.

So this meant I had to rewrite the entire thing to no longer assume we had JSON data, that's when we had to migrate the entire Backend Codebase to use an SQL server.

The Good News

Once the rewrite was done I posted the following on our Discord server.


But heres some news I'm very excited to see, about the improvements of performance on the iterations of the backend, when querying /api/packages (As in get all packages with default sorts)

Keep in mind these times are from the server itself. So that includes the express server getting the request, filtering query parameters, querying the SQL server over the web, and rebuilding/pruning the JSON response. But this is great to see, and is finally something that seems shippable, even if it can be further optimized


So with that, it was time to finish rewriting the backend, which while the struggles were not over, we had at least gotten over the (as so far) biggest hurdle.

But beyond this, it was at this point we had a proper community. A team of skilled contributors are all willing to pull their weight to make this happen. We all have lives outside this project, we all live in different areas, in different timezones, and speak different languages. But the one thing we have in common is our mission, of keeping an Open Source Project we care about alive.

Throughout all of this, that has been my biggest takeaway. The most important lesson I think is available to learn here, and one I don't want to forget any time soon.


What's the natural response to finding out an application you use is being sunset?

Take pride in the Open-Source community around you, and be thankful we live in a time where all that's needed to build a community and team is the text editor you choose to use.

Thanks for reading, and as I always say thanks for contributing.

`,18);function U(F,K){const a=l("ExternalLinkIcon");return s(),r("div",null,[c,h(" more "),u,e("p",null,[t("Like some others, I'm assuming, on a normal day I was browsing Reddit when I saw the initial announcement. On June 8th of 2022 GitHub officially "),e("a",m,[t("announced"),o(a)]),t(" that they were going to sunset Atom.")]),p,g,w,e("p",null,[t("On June 12th of 2022, I wrote a "),e("a",f,[t("super simple script"),o(a)]),t(", one that would just absolutely hammer Atom's package repository and get everything I possibly could. The only goal here was to archive every single package and all of their versions. Hoping that later I'd figure out what I could do with this data.")]),e("p",null,[t("It was around that same time I started to see "),e("a",b,[t("Atom Community"),o(a)]),t(" mentioned as the most likely fork to succeed after Atom. So of course seeing their GitHub I joined their Discord and got going with a group of other talented volunteers to start seeing what needed to be done.")]),y,e("p",null,[t("After creating this small script, I think what really made all of this feel like an actual team effort was the first PR I received for this project. A small diff, only "),k,t(" over on "),e("a",v,[_,o(a)]),t(" by @DeeDeeG, but what it really meant was that truly there was a community here that cared about keeping this whole ecosystem alive the same way I did. And from there, that's all that was needed to be off to the races.")]),I,A,T,e("p",null,[t("On June 13th of 2022, I started the "),e("a",S,[N,o(a)]),t(", what I thought would be the home of the new Backend for Atom Community, and there I delved into the strange idiosyncrasies of Atom. Still, the strangest one that sticks in my head, is I could tell how strange the codebase was just from the error messages. The best example, "),x,t(" Returns "),B,t(),P,t(" hitting "),O,t(" returns "),j,t(". Meaning that there was completely different code handling not found in one place, and in another. Notice the difference in capitalization.")]),e("p",null,[t("But soon after creating the repo, I realized that no one else on this small team we had was familiar with Golang at all, which is what I had initially written this in. So I switched over to JavaScript, which in the long run was likely a great choice as that is my most familiar language. And that's where I created "),R,t(", a mouthful I know, but was much later renamed just to "),e("a",G,[J,o(a)]),t(".")]),H,q,z,C,e("p",null,[t("In the end, the "),e("a",E,[L,o(a)]),t(" was able to check if every single package was valid, while also removing a list of banned package names that we had put together.")]),M,e("p",null,[t("Then simply I requested the default screen "),W,t(" which, in my poor implementation would scan every single package, then would filter it based on query parameters. Which would be iterating through every single one of them. Now you may hear this and think of course performance was terrible, and trust me it was a concern I had to, on commit "),e("a",D,[V,o(a)]),t(" I wrote the following comment after creating this terrible method.")]),Q])}const Y=i(d,[["render",U],["__file","20221127-confused-Techie-SunsetMisadventureBackend.html.vue"]]);export{Y as default}; diff --git a/assets/20221127-confused-Techie-SunsetMisadventureBackend.html.dad96414.js b/assets/20221127-confused-Techie-SunsetMisadventureBackend.html.dad96414.js new file mode 100644 index 0000000000..15d5250af9 --- /dev/null +++ b/assets/20221127-confused-Techie-SunsetMisadventureBackend.html.dad96414.js @@ -0,0 +1 @@ +const e=JSON.parse(`{"key":"v-0075970e","path":"/blog/20221127-confused-Techie-SunsetMisadventureBackend.html","title":"A Sunset and the Misadventures of a Backend","lang":"en-US","frontmatter":{"title":"A Sunset and the Misadventures of a Backend","author":"confused-Techie","date":"2022-11-27T00:00:00.000Z","category":["dev","log"],"tag":["backend","sunset"]},"excerpt":"

What's the natural response to finding out an application you use is being sunset?\\nSpend the next several months creating a complex backend infrastructure of course!

\\n","headers":[{"level":3,"title":"The Archival","slug":"the-archival","link":"#the-archival","children":[]},{"level":3,"title":"What to do with this Data","slug":"what-to-do-with-this-data","link":"#what-to-do-with-this-data","children":[]},{"level":3,"title":"The Misadventures Part","slug":"the-misadventures-part","link":"#the-misadventures-part","children":[]},{"level":3,"title":"The Good News","slug":"the-good-news","link":"#the-good-news","children":[]}],"git":{"updatedTime":1669648250000,"contributors":[{"name":"confused_techie","email":"dev@lhbasics.com","commits":2},{"name":"confused-Techie","email":"dev@lhbasics.com","commits":1}]},"readingTime":{"minutes":6.49,"words":1948},"filePathRelative":"blog/20221127-confused-Techie-SunsetMisadventureBackend.md","localizedDate":"November 27, 2022"}`);export{e as data}; diff --git a/assets/20221208-Daeraxa-DistroTubeVideo.html.7c68eb7c.js b/assets/20221208-Daeraxa-DistroTubeVideo.html.7c68eb7c.js new file mode 100644 index 0000000000..0e6a6047e1 --- /dev/null +++ b/assets/20221208-Daeraxa-DistroTubeVideo.html.7c68eb7c.js @@ -0,0 +1 @@ +import{_ as n,o as a,c,a as e,b as t,d as r,e as s,r as l}from"./app.0e1565ce.js";const u={},i=e("br",null,null,-1),h={href:"https://www.youtube.com/watch?v=WA1c_S6Zsu4",target:"_blank",rel:"noopener noreferrer"},d=e("p",null,"DistoTube is a channel/website all about free and open source software and one of our members reached out to tell them about the Pulsar project and he decided to feature us in an entire video!",-1),_={href:"https://www.youtube.com/watch?v=WA1c_S6Zsu4",target:"_blank",rel:"noopener noreferrer"},p=e("iframe",{width:"560",height:"315",src:"https://www.youtube.com/embed/WA1c_S6Zsu4",title:"YouTube video player",frameborder:"0",allow:"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture",allowfullscreen:""},null,-1),f={href:"https://odysee.com/@DistroTube:2/announcing-the-pulsar-text-editor:7",target:"_blank",rel:"noopener noreferrer"},m=e("p",null,"And if you see this DT, thank you very much for the feature, we will be taking your comments to heart!",-1);function b(w,y){const o=l("ExternalLinkIcon");return a(),c("div",null,[e("p",null,[t("Today we were featured on the DistroTube YouTube channel!"),i,e("a",h,[t("Watch here"),r(o)])]),s(" more "),d,e("p",null,[t("If you haven't seen it then "),e("a",_,[t("click here"),r(o)]),t(" or watch below:")]),p,e("p",null,[t("Or if you prefer you can watch it "),e("a",f,[t("on Odysee"),r(o)])]),m])}const T=n(u,[["render",b],["__file","20221208-Daeraxa-DistroTubeVideo.html.vue"]]);export{T as default}; diff --git a/assets/20221208-Daeraxa-DistroTubeVideo.html.a94d35bc.js b/assets/20221208-Daeraxa-DistroTubeVideo.html.a94d35bc.js new file mode 100644 index 0000000000..40a154739e --- /dev/null +++ b/assets/20221208-Daeraxa-DistroTubeVideo.html.a94d35bc.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-cfc0df38","path":"/blog/20221208-Daeraxa-DistroTubeVideo.html","title":"Featured on DistroTube!","lang":"en-US","frontmatter":{"title":"Featured on DistroTube!","author":"Daeraxa","date":"2022-12-08T00:00:00.000Z","category":["news"],"tag":["video"]},"excerpt":"

Today we were featured on the DistroTube YouTube channel!
\\nWatch here

\\n","headers":[],"git":{"updatedTime":1670529246000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":0.51,"words":154},"filePathRelative":"blog/20221208-Daeraxa-DistroTubeVideo.md","localizedDate":"December 8, 2022"}');export{e as data}; diff --git a/assets/20221215-confused-Techie-v1.100.1-beta.html.465d9003.js b/assets/20221215-confused-Techie-v1.100.1-beta.html.465d9003.js new file mode 100644 index 0000000000..f046951ecb --- /dev/null +++ b/assets/20221215-confused-Techie-v1.100.1-beta.html.465d9003.js @@ -0,0 +1 @@ +import{_ as o,o as n,c as s,a as e,b as r,d as l,e as a,f as i,r as u}from"./app.0e1565ce.js";const p={},h={href:"https://github.com/pulsar-edit/pulsar/releases/tag/v1.100.0-beta",target:"_blank",rel:"noopener noreferrer"},d=e("h1",{id:"first-beta-tagged-release-of-pulsar-get-it-while-it-s-hot",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#first-beta-tagged-release-of-pulsar-get-it-while-it-s-hot","aria-hidden":"true"},"#"),r(" First (Beta) Tagged Release of Pulsar -- Get it while it's hot!")],-1),c={href:"https://pulsar-edit.dev/blog/20221127-confused-Techie-SunsetMisadventureBackend.html",target:"_blank",rel:"noopener noreferrer"},_={href:"https://github.com/pulsar-edit/package-backend/blob/main/docs/reference/packages.md",target:"_blank",rel:"noopener noreferrer"},f={href:"https://pulsar-edit.dev/download.html",target:"_blank",rel:"noopener noreferrer"},b={href:"https://github.com/pulsar-edit/pulsar/blob/master/CHANGELOG.md",target:"_blank",rel:"noopener noreferrer"},g=i('

Over all, we hope you enjoy the release, take a look, kick the tires, let us know what you think. Report any issues you find, and maybe we'll be able to put together a fix. Likewise, if you have a fix handy, feel free to open a Pull Request. Happy coding, see you among the stars!


Pulsar

',5),m={href:"https://github.com/pulsar-edit/pulsar/pull/220",target:"_blank",rel:"noopener noreferrer"},k=e("code",null,"autocomplete-css",-1),x=e("code",null,"autocomplete-html",-1),v={href:"https://github.com/pulsar-edit/pulsar/pull/212",target:"_blank",rel:"noopener noreferrer"},w=e("code",null,"packages/*/package-lock.json",-1),y={href:"https://github.com/pulsar-edit/pulsar/pull/209",target:"_blank",rel:"noopener noreferrer"},A={href:"https://github.com/pulsar-edit/pulsar/pull/208",target:"_blank",rel:"noopener noreferrer"},T=e("code",null,"package-generator",-1),B={href:"https://github.com/pulsar-edit/pulsar/pull/207",target:"_blank",rel:"noopener noreferrer"},S={href:"https://github.com/pulsar-edit/pulsar/pull/206",target:"_blank",rel:"noopener noreferrer"},z=e("code",null,"dugite",-1),R={href:"https://github.com/pulsar-edit/pulsar/pull/201",target:"_blank",rel:"noopener noreferrer"},F={href:"https://github.com/pulsar-edit/pulsar/pull/198",target:"_blank",rel:"noopener noreferrer"},P={href:"https://github.com/pulsar-edit/pulsar/pull/196",target:"_blank",rel:"noopener noreferrer"},N={href:"https://github.com/pulsar-edit/pulsar/pull/186",target:"_blank",rel:"noopener noreferrer"},D={href:"https://github.com/pulsar-edit/pulsar/pull/185",target:"_blank",rel:"noopener noreferrer"},I=e("code",null,"autocomplete-plus",-1),C={href:"https://github.com/pulsar-edit/pulsar/pull/184",target:"_blank",rel:"noopener noreferrer"},E={href:"https://github.com/pulsar-edit/pulsar/pull/183",target:"_blank",rel:"noopener noreferrer"},G={href:"https://github.com/pulsar-edit/pulsar/pull/180",target:"_blank",rel:"noopener noreferrer"},L={href:"https://github.com/pulsar-edit/pulsar/pull/178",target:"_blank",rel:"noopener noreferrer"},M={href:"https://github.com/pulsar-edit/pulsar/pull/175",target:"_blank",rel:"noopener noreferrer"},U={href:"https://github.com/pulsar-edit/pulsar/pull/171",target:"_blank",rel:"noopener noreferrer"},j={href:"https://github.com/pulsar-edit/pulsar/pull/168",target:"_blank",rel:"noopener noreferrer"},W={href:"https://github.com/pulsar-edit/pulsar/pull/167",target:"_blank",rel:"noopener noreferrer"},O={href:"https://github.com/pulsar-edit/pulsar/pull/166",target:"_blank",rel:"noopener noreferrer"},V={href:"https://github.com/pulsar-edit/pulsar/pull/163",target:"_blank",rel:"noopener noreferrer"},H={href:"https://github.com/pulsar-edit/pulsar/pull/161",target:"_blank",rel:"noopener noreferrer"},q=e("code",null,"welcome",-1),X={href:"https://github.com/pulsar-edit/pulsar/pull/159",target:"_blank",rel:"noopener noreferrer"},J=e("code",null,"ppm",-1),K={href:"https://github.com/pulsar-edit/pulsar/pull/153",target:"_blank",rel:"noopener noreferrer"},Q={href:"https://github.com/pulsar-edit/pulsar/pull/151",target:"_blank",rel:"noopener noreferrer"},Y=e("code",null,"mkdirp",-1),Z={href:"https://github.com/pulsar-edit/pulsar/pull/150",target:"_blank",rel:"noopener noreferrer"},$=e("code",null,"--package",-1),ee={href:"https://github.com/pulsar-edit/pulsar/pull/149",target:"_blank",rel:"noopener noreferrer"},re=e("code",null,"ppm",-1),te={href:"https://github.com/pulsar-edit/pulsar/pull/144",target:"_blank",rel:"noopener noreferrer"},le=e("code",null,"nsole",-1),oe={href:"https://github.com/pulsar-edit/pulsar/pull/142",target:"_blank",rel:"noopener noreferrer"},ne={href:"https://github.com/pulsar-edit/pulsar/pull/140",target:"_blank",rel:"noopener noreferrer"},se=e("code",null,"yarn.lock",-1),ae={href:"https://github.com/pulsar-edit/pulsar/pull/139",target:"_blank",rel:"noopener noreferrer"},ie=e("code",null,"dist",-1),ue=e("code",null,"binaries",-1),pe=e("code",null,"gitignore",-1),he={href:"https://github.com/pulsar-edit/pulsar/pull/138",target:"_blank",rel:"noopener noreferrer"},de=e("code",null,"ppm",-1),ce={href:"https://github.com/pulsar-edit/pulsar/pull/131",target:"_blank",rel:"noopener noreferrer"},_e=e("code",null,"settings-view",-1),fe={href:"https://github.com/pulsar-edit/pulsar/pull/130",target:"_blank",rel:"noopener noreferrer"},be={href:"https://github.com/pulsar-edit/pulsar/pull/128",target:"_blank",rel:"noopener noreferrer"},ge=e("code",null,"ppm",-1),me=e("code",null,"pulsar",-1),ke={href:"https://github.com/pulsar-edit/pulsar/pull/125",target:"_blank",rel:"noopener noreferrer"},xe={href:"https://github.com/pulsar-edit/pulsar/pull/121",target:"_blank",rel:"noopener noreferrer"},ve=e("code",null,"fs-plus",-1),we=e("code",null,"exception-reporting",-1),ye={href:"https://github.com/pulsar-edit/pulsar/pull/118",target:"_blank",rel:"noopener noreferrer"},Ae={href:"https://github.com/pulsar-edit/pulsar/pull/115",target:"_blank",rel:"noopener noreferrer"},Te={href:"https://github.com/pulsar-edit/pulsar/pull/114",target:"_blank",rel:"noopener noreferrer"},Be=e("code",null,"background-tips",-1),Se={href:"https://github.com/pulsar-edit/pulsar/pull/111",target:"_blank",rel:"noopener noreferrer"},ze={href:"https://github.com/pulsar-edit/pulsar/pull/110",target:"_blank",rel:"noopener noreferrer"},Re=e("code",null,"snippets",-1),Fe={href:"https://github.com/pulsar-edit/pulsar/pull/107",target:"_blank",rel:"noopener noreferrer"},Pe={href:"https://github.com/pulsar-edit/pulsar/pull/105",target:"_blank",rel:"noopener noreferrer"},Ne={href:"https://github.com/pulsar-edit/pulsar/pull/101",target:"_blank",rel:"noopener noreferrer"},De=e("code",null,"yarn dist",-1),Ie={href:"https://github.com/pulsar-edit/pulsar/pull/97",target:"_blank",rel:"noopener noreferrer"},Ce=e("code",null,"README.md",-1),Ee={href:"https://github.com/pulsar-edit/pulsar/pull/96",target:"_blank",rel:"noopener noreferrer"},Ge={href:"https://github.com/pulsar-edit/pulsar/pull/82",target:"_blank",rel:"noopener noreferrer"},Le={href:"https://github.com/pulsar-edit/pulsar/pull/78",target:"_blank",rel:"noopener noreferrer"},Me=e("code",null,"NSFW",-1),Ue={href:"https://github.com/pulsar-edit/pulsar/pull/77",target:"_blank",rel:"noopener noreferrer"},je=e("code",null,"settings-view",-1),We={href:"https://github.com/pulsar-edit/pulsar/pull/72",target:"_blank",rel:"noopener noreferrer"},Oe=e("code",null,".nvmrc",-1),Ve={href:"https://github.com/pulsar-edit/pulsar/pull/71",target:"_blank",rel:"noopener noreferrer"},He=e("code",null,"ppm",-1),qe={href:"https://github.com/pulsar-edit/pulsar/pull/68",target:"_blank",rel:"noopener noreferrer"},Xe={href:"https://github.com/pulsar-edit/pulsar/pull/67",target:"_blank",rel:"noopener noreferrer"},Je={href:"https://github.com/pulsar-edit/pulsar/pull/66",target:"_blank",rel:"noopener noreferrer"},Ke={href:"https://github.com/pulsar-edit/pulsar/pull/63",target:"_blank",rel:"noopener noreferrer"},Qe=e("code",null,"async",-1),Ye={href:"https://github.com/pulsar-edit/pulsar/pull/59",target:"_blank",rel:"noopener noreferrer"},Ze={href:"https://github.com/pulsar-edit/pulsar/pull/51",target:"_blank",rel:"noopener noreferrer"},$e={href:"https://github.com/pulsar-edit/pulsar/pull/40",target:"_blank",rel:"noopener noreferrer"},er=e("code",null,"language-c",-1),rr={href:"https://github.com/pulsar-edit/pulsar/pull/33",target:"_blank",rel:"noopener noreferrer"},tr=e("code",null,"electron",-1),lr={href:"https://github.com/pulsar-edit/pulsar/pull/28",target:"_blank",rel:"noopener noreferrer"},or=e("code",null,"yarn install",-1),nr={href:"https://github.com/pulsar-edit/pulsar/pull/16",target:"_blank",rel:"noopener noreferrer"},sr={href:"https://github.com/pulsar-edit/pulsar/pull/14",target:"_blank",rel:"noopener noreferrer"},ar=e("code",null,"autocomplete-html",-1),ir={href:"https://github.com/pulsar-edit/pulsar/pull/14",target:"_blank",rel:"noopener noreferrer"},ur=e("code",null,"tree-sitter",-1),pr={href:"https://github.com/pulsar-edit/pulsar/pull/14",target:"_blank",rel:"noopener noreferrer"},hr={href:"https://github.com/pulsar-edit/pulsar/pull/7",target:"_blank",rel:"noopener noreferrer"},dr=e("code",null,"yarn",-1),cr={href:"https://github.com/pulsar-edit/pulsar/pull/6",target:"_blank",rel:"noopener noreferrer"},_r=e("code",null,"fs-admin",-1),fr={href:"https://github.com/pulsar-edit/pulsar/pull/4",target:"_blank",rel:"noopener noreferrer"},br=e("code",null,"text-buffer",-1),gr={href:"https://github.com/pulsar-edit/pulsar/pull/4",target:"_blank",rel:"noopener noreferrer"},mr={href:"https://github.com/pulsar-edit/pulsar/pull/112",target:"_blank",rel:"noopener noreferrer"},kr={href:"https://github.com/pulsar-edit/pulsar/pull/45",target:"_blank",rel:"noopener noreferrer"},xr={href:"https://github.com/pulsar-edit/pulsar/pull/29",target:"_blank",rel:"noopener noreferrer"},vr={href:"https://github.com/pulsar-edit/pulsar/pull/13",target:"_blank",rel:"noopener noreferrer"},wr={href:"https://github.com/pulsar-edit/pulsar/pull/190",target:"_blank",rel:"noopener noreferrer"},yr={href:"https://github.com/pulsar-edit/pulsar/pull/173",target:"_blank",rel:"noopener noreferrer"},Ar={href:"https://github.com/pulsar-edit/pulsar/pull/172",target:"_blank",rel:"noopener noreferrer"},Tr={href:"https://github.com/pulsar-edit/pulsar/pull/156",target:"_blank",rel:"noopener noreferrer"},Br={href:"https://github.com/pulsar-edit/pulsar/pull/145",target:"_blank",rel:"noopener noreferrer"},Sr={href:"https://github.com/pulsar-edit/pulsar/pull/136",target:"_blank",rel:"noopener noreferrer"},zr={href:"https://github.com/pulsar-edit/pulsar/pull/126",target:"_blank",rel:"noopener noreferrer"},Rr={href:"https://github.com/pulsar-edit/pulsar/pull/123",target:"_blank",rel:"noopener noreferrer"},Fr={href:"https://github.com/pulsar-edit/pulsar/pull/122",target:"_blank",rel:"noopener noreferrer"},Pr={href:"https://github.com/pulsar-edit/pulsar/pull/120",target:"_blank",rel:"noopener noreferrer"},Nr={href:"https://github.com/pulsar-edit/pulsar/pull/103",target:"_blank",rel:"noopener noreferrer"},Dr={href:"https://github.com/pulsar-edit/pulsar/pull/83",target:"_blank",rel:"noopener noreferrer"},Ir={href:"https://github.com/pulsar-edit/pulsar/pull/81",target:"_blank",rel:"noopener noreferrer"},Cr={href:"https://github.com/pulsar-edit/pulsar/pull/65",target:"_blank",rel:"noopener noreferrer"},Er={href:"https://github.com/pulsar-edit/pulsar/pull/58",target:"_blank",rel:"noopener noreferrer"},Gr={href:"https://github.com/pulsar-edit/pulsar/pull/54",target:"_blank",rel:"noopener noreferrer"},Lr={href:"https://github.com/pulsar-edit/pulsar/pull/22",target:"_blank",rel:"noopener noreferrer"},Mr={href:"https://github.com/pulsar-edit/pulsar/pull/17",target:"_blank",rel:"noopener noreferrer"},Ur={href:"https://github.com/pulsar-edit/pulsar/pull/11",target:"_blank",rel:"noopener noreferrer"},jr={href:"https://github.com/pulsar-edit/pulsar/pull/10",target:"_blank",rel:"noopener noreferrer"},Wr={href:"https://github.com/pulsar-edit/pulsar/pull/8",target:"_blank",rel:"noopener noreferrer"},Or={href:"https://github.com/pulsar-edit/pulsar/pull/152",target:"_blank",rel:"noopener noreferrer"},Vr={href:"https://github.com/pulsar-edit/pulsar/pull/141",target:"_blank",rel:"noopener noreferrer"},Hr={href:"https://github.com/pulsar-edit/pulsar/pull/116",target:"_blank",rel:"noopener noreferrer"},qr={href:"https://github.com/pulsar-edit/pulsar/pull/109",target:"_blank",rel:"noopener noreferrer"},Xr={href:"https://github.com/pulsar-edit/pulsar/pull/70",target:"_blank",rel:"noopener noreferrer"},Jr={href:"https://github.com/pulsar-edit/pulsar/pull/50",target:"_blank",rel:"noopener noreferrer"},Kr={href:"https://github.com/pulsar-edit/pulsar/pull/48",target:"_blank",rel:"noopener noreferrer"},Qr={href:"https://github.com/pulsar-edit/pulsar/pull/46",target:"_blank",rel:"noopener noreferrer"},Yr={href:"https://github.com/pulsar-edit/pulsar/pull/42",target:"_blank",rel:"noopener noreferrer"},Zr={href:"https://github.com/pulsar-edit/pulsar/pull/41",target:"_blank",rel:"noopener noreferrer"},$r={href:"https://github.com/pulsar-edit/pulsar/pull/36",target:"_blank",rel:"noopener noreferrer"},et={href:"https://github.com/pulsar-edit/pulsar/pull/35",target:"_blank",rel:"noopener noreferrer"},rt={href:"https://github.com/pulsar-edit/pulsar/pull/18",target:"_blank",rel:"noopener noreferrer"},tt=e("h3",{id:"ppm",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#ppm","aria-hidden":"true"},"#"),r(" ppm")],-1),lt={href:"https://github.com/pulsar-edit/ppm/pull/41",target:"_blank",rel:"noopener noreferrer"},ot=e("code",null,"help",-1),nt={href:"https://github.com/pulsar-edit/ppm/pull/40",target:"_blank",rel:"noopener noreferrer"},st={href:"https://github.com/pulsar-edit/ppm/pull/38",target:"_blank",rel:"noopener noreferrer"},at=e("code",null,"getResourcePath",-1),it={href:"https://github.com/pulsar-edit/ppm/pull/36",target:"_blank",rel:"noopener noreferrer"},ut={href:"https://github.com/pulsar-edit/ppm/pull/34",target:"_blank",rel:"noopener noreferrer"},pt={href:"https://github.com/pulsar-edit/ppm/pull/13",target:"_blank",rel:"noopener noreferrer"},ht={href:"https://github.com/pulsar-edit/ppm/pull/5",target:"_blank",rel:"noopener noreferrer"},dt=e("code",null,"electron",-1),ct={href:"https://github.com/pulsar-edit/ppm/pull/2",target:"_blank",rel:"noopener noreferrer"},_t={href:"https://github.com/pulsar-edit/ppm/pull/12",target:"_blank",rel:"noopener noreferrer"},ft={href:"https://github.com/pulsar-edit/ppm/pull/7",target:"_blank",rel:"noopener noreferrer"},bt={href:"https://github.com/pulsar-edit/ppm/pull/6",target:"_blank",rel:"noopener noreferrer"},gt={href:"https://github.com/pulsar-edit/ppm/pull/39",target:"_blank",rel:"noopener noreferrer"},mt=e("h3",{id:"autocomplete-html",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#autocomplete-html","aria-hidden":"true"},"#"),r(" autocomplete-html")],-1),kt={href:"https://github.com/pulsar-edit/autocomplete-html/pull/1",target:"_blank",rel:"noopener noreferrer"},xt=e("h3",{id:"settings-view",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#settings-view","aria-hidden":"true"},"#"),r(" settings-view")],-1),vt={href:"https://github.com/pulsar-edit/settings-view/pull/12",target:"_blank",rel:"noopener noreferrer"},wt={href:"https://github.com/pulsar-edit/settings-view/pull/6",target:"_blank",rel:"noopener noreferrer"},yt={href:"https://github.com/pulsar-edit/settings-view/pull/2",target:"_blank",rel:"noopener noreferrer"},At={href:"https://github.com/pulsar-edit/settings-view/pull/7",target:"_blank",rel:"noopener noreferrer"},Tt={href:"https://github.com/pulsar-edit/settings-view/pull/3",target:"_blank",rel:"noopener noreferrer"},Bt={href:"https://github.com/pulsar-edit/settings-view/pull/1",target:"_blank",rel:"noopener noreferrer"},St={href:"https://github.com/pulsar-edit/settings-view/pull/10",target:"_blank",rel:"noopener noreferrer"},zt=e("h3",{id:"snippets",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#snippets","aria-hidden":"true"},"#"),r(" snippets")],-1),Rt={href:"https://github.com/pulsar-edit/snippets/pull/4",target:"_blank",rel:"noopener noreferrer"},Ft=e("code",null,"fs-plus",-1),Pt={href:"https://github.com/pulsar-edit/snippets/pull/2",target:"_blank",rel:"noopener noreferrer"},Nt={href:"https://github.com/pulsar-edit/snippets/pull/1",target:"_blank",rel:"noopener noreferrer"},Dt=e("h3",{id:"background-tips",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#background-tips","aria-hidden":"true"},"#"),r(" background-tips")],-1),It=e("code",null,"background-tips",-1),Ct={href:"https://github.com/pulsar-edit/background-tips/pull/4",target:"_blank",rel:"noopener noreferrer"},Et={href:"https://github.com/pulsar-edit/background-tips/pull/5",target:"_blank",rel:"noopener noreferrer"},Gt={href:"https://github.com/pulsar-edit/background-tips/pull/2",target:"_blank",rel:"noopener noreferrer"},Lt={href:"https://github.com/pulsar-edit/background-tips/pull/1",target:"_blank",rel:"noopener noreferrer"};function Mt(Ut,jt){const t=u("ExternalLinkIcon");return n(),s("div",null,[e("p",null,[r("Check out our first GitHub Release for Pulsar! "),e("a",h,[r("Available Now!"),l(t)])]),a(" more "),d,e("p",null,[r("We have some nice changes to give you a good first experience of what Pulsar is going to be like. Mostly, it's Atom, but still functioning. We've replaced the Package Backend (previously closed source) with a brand new one (Open Source!) -- see "),e("a",c,[r("this blog post"),l(t)]),r(" for more details. We have even migrated all original Packages from Atom to Pulsar, so feel free to find and download your favourites! A quick note about packages, if you have any questions or concerns about our migrated packages refer to "),e("a",_,[r("here"),l(t)]),r(".")]),e("p",null,[r("Also new is Electron 12, and binaries for ARM, both on Linux and MacOS (yes, that's an Apple Silicon native build). In general, several new package formats are available, feel free to "),e("a",f,[r("try them out"),l(t)]),r(" and find what suits you.")]),e("p",null,[r("We've gotten some miscellaneous fixes and upgrades in, as well as a (mostly) thorough rebranding from Atom to Pulsar. Feel free to check out the "),e("a",b,[r("Changelog"),l(t)]),r(" for more details.")]),g,e("ul",null,[e("li",null,[r("Added: Incorporate settings-view to core "),e("a",m,[r("@Daeraxa"),l(t)])]),e("li",null,[r("Added: Bundle "),k,r(" && "),x,r(),e("a",v,[r("@confused-Techie"),l(t)])]),e("li",null,[r("Added: add or update "),w,r(),e("a",y,[r("@Sertonix"),l(t)])]),e("li",null,[r("Fixed: Organize our Exclusions/Inclusions "),e("a",A,[r("@confused-Techie"),l(t)])]),e("li",null,[r("Added: Bundle "),T,r(),e("a",B,[r("@confused-Techie"),l(t)])]),e("li",null,[r("Fixed: meta: Don't exclude 'loophole' or 'pegjs' packages "),e("a",S,[r("@DeeDeeG"),l(t)])]),e("li",null,[r("Fixed: Fix "),z,r(),e("a",R,[r("@confused-Techie"),l(t)])]),e("li",null,[r("Bumped: ppm: Update ppm submodule (new Electron headers download URL) "),e("a",F,[r("@DeeDeeG"),l(t)])]),e("li",null,[r('Removed: Revert "Merge pull request #184 from pulsar-edit/bump-autocomplete-plus" '),e("a",P,[r("@confused-Techie"),l(t)])]),e("li",null,[r("Bumped: Bump GitHub package "),e("a",N,[r("@mauricioszabo"),l(t)])]),e("li",null,[r("Fixed: CI (Windows): Use npm (not yarn) to install ppm "),e("a",D,[r("@DeeDeeG"),l(t)])]),e("li",null,[r("Bumped: Bumped "),I,r(),e("a",C,[r("@confused-Techie"),l(t)])]),e("li",null,[r("Added: Adding test runner missing files "),e("a",E,[r("@mauricioszabo"),l(t)])]),e("li",null,[r("Fixed: fix about package test "),e("a",G,[r("@Sertonix"),l(t)])]),e("li",null,[r("Added: Add tar.gz target to electron-builder "),e("a",L,[r("@Daeraxa"),l(t)])]),e("li",null,[r("Fixed: Cleanup/standardize pulsar.sh "),e("a",M,[r("@Spiker985"),l(t)])]),e("li",null,[r("Fixed: Update LICENSE.md "),e("a",U,[r("@Daeraxa"),l(t)])]),e("li",null,[r("Removed: remove old scripts "),e("a",j,[r("@Sertonix"),l(t)])]),e("li",null,[r("Fixed: Fix Codacy Ignore "),e("a",W,[r("@confused-Techie"),l(t)])]),e("li",null,[r("Added: New ChangeLog Format "),e("a",O,[r("@confused-Techie"),l(t)])]),e("li",null,[r("Fixed: shorten task description if too long "),e("a",V,[r("@Sertonix"),l(t)])]),e("li",null,[r("Fixed: Improve Package Tests "),e("a",H,[r("@confused-Techie"),l(t)])]),e("li",null,[r("Removed: Metric docs from "),q,r(),e("a",X,[r("@Sertonix"),l(t)])]),e("li",null,[r("Fixed: PostInstall of "),J,r(),e("a",K,[r("@mauricioszabo"),l(t)])]),e("li",null,[r("Fixed: Unmerged Menus ignoring separators "),e("a",Q,[r("@Sertonix"),l(t)])]),e("li",null,[r("Removed: "),Y,r(),e("a",Z,[r("@Sertonix"),l(t)])]),e("li",null,[r("Fixed: "),$,r(" exiting incorrectly "),e("a",ee,[r("@mauricioszabo"),l(t)])]),e("li",null,[r("Bumped: "),re,r(" submodule "),e("a",te,[r("@mauricioszabo"),l(t)])]),e("li",null,[r("Fixed: undefined "),le,r(),e("a",oe,[r("@jonian"),l(t)])]),e("li",null,[r("Fixed: Git tab in Binaries "),e("a",ne,[r("@benonymus"),l(t)])]),e("li",null,[r("Fixed: "),se,r(" versions "),e("a",ae,[r("@jonian"),l(t)])]),e("li",null,[r("Added: "),ie,r(" & "),ue,r(" to "),pe,r(),e("a",he,[r("@jonain"),l(t)])]),e("li",null,[r("Bumped: "),de,r(" submodule to allow Git Package Install "),e("a",ce,[r("@mauricioszabo"),l(t)])]),e("li",null,[r("Bumped: "),_e,r(" 0.261.9 -> 0.261.10 "),e("a",fe,[r("@mauricioszabo"),l(t)])]),e("li",null,[r("Removed: Unused code fragments from build scripts "),e("a",be,[r("@Sertonix"),l(t)])]),e("li",null,[r("Added: Ability to run "),ge,r(" from "),me,r(" CLI "),e("a",ke,[r("@mauricioszabo"),l(t)])]),e("li",null,[r("Fixed: base16 URL to use WayBack Machine "),e("a",xe,[r("@Sertonix"),l(t)])]),e("li",null,[r("Removed: "),ve,r(" from "),we,r(),e("a",ye,[r("@Sertonix"),l(t)])]),e("li",null,[r("Removed: Benchmark Startup Mode Part 2 "),e("a",Ae,[r("@DeeDeeG"),l(t)])]),e("li",null,[r("Removed: Unused scripts "),e("a",Te,[r("@mauricioszabo"),l(t)])]),e("li",null,[r("Bumped: "),Be,r(" 0.28.0 -> 0.28.1 "),e("a",Se,[r("@confused-Techie"),l(t)])]),e("li",null,[r("Removed: Tooling bloat "),e("a",ze,[r("@confused-Techie"),l(t)])]),e("li",null,[r("Bumped: "),Re,r(" NA -> 1.6.1 "),e("a",Fe,[r("@mauricioszabo"),l(t)])]),e("li",null,[r("Removed: Benchmark Startup mode "),e("a",Pe,[r("@confused-Techie"),l(t)])]),e("li",null,[r("Added: Binaries for Intel Mac & ARM Linux "),e("a",Ne,[r("@mauricioszabo"),l(t)])]),e("li",null,[r("Added: "),De,r(" accepts arguments "),e("a",Ie,[r("@mauricioszabo"),l(t)])]),e("li",null,[r("Fixed: Load core packages "),Ce,r(),e("a",Ee,[r("@Sertonix"),l(t)])]),e("li",null,[r("Fixed: Unlock terminal on Linux "),e("a",Ge,[r("@mauricioszabo"),l(t)])]),e("li",null,[r("Added: Aliases to workflow for link generation "),e("a",Le,[r("@kaosine"),l(t)])]),e("li",null,[r("Fixed: Hooked "),Me,r(" directly "),e("a",Ue,[r("@mauricioszabo"),l(t)])]),e("li",null,[r("Bumped: "),je,r(" 0.261.8 -> 0.261.9 "),e("a",We,[r("@mauricioszabo"),l(t)])]),e("li",null,[r("Bumped: "),Oe,r(" 12.18 -> 16 "),e("a",Ve,[r("@Daeraxa"),l(t)])]),e("li",null,[r("Bumped: "),He,r(" submodule for new backend "),e("a",qe,[r("@confused-Techie"),l(t)])]),e("li",null,[r("Removed: Experimental and internal watchers "),e("a",Xe,[r("@mauricioszabo"),l(t)])]),e("li",null,[r("Fixed: Improvements for windows binaries "),e("a",Je,[r("@mauricioszabo"),l(t)])]),e("li",null,[r("Fixed: Improvements for binary building "),e("a",Ke,[r("@mauricioszabo"),l(t)])]),e("li",null,[r("Bumped: "),Qe,r(" 3.2.0 -> 3.2.4 "),e("a",Ye,[r("@confused-Techie"),l(t)])]),e("li",null,[r("Removed: Mystery/Ghost Submodule "),e("a",Ze,[r("@mauricioszabo"),l(t)])]),e("li",null,[r("Removed: Telemetry and Remote Crash Reports "),e("a",$e,[r("@confused-Techie"),l(t)])]),e("li",null,[r("Added: Bundled "),er,r(" into the editor "),e("a",rr,[r("@mauricioszabo"),l(t)])]),e("li",null,[r("Bumped: "),tr,r(" 11.5.0 -> 12.2.3 "),e("a",lr,[r("@mauricioszabo"),l(t)])]),e("li",null,[r("Fixed: "),or,r(" due to syntax error "),e("a",nr,[r("@Daeraxa"),l(t)])]),e("li",null,[r("Added: Bundled most language grammars into the editor "),e("a",sr,[r("@mauricioszabo"),l(t)])]),e("li",null,[r("Bumped: "),ar,r(" 0.8.8 -> 0.8.9 "),e("a",ir,[r("@mauricioszabo"),l(t)])]),e("li",null,[r("Bumped: "),ur,r(" NA -> 0.20.0 "),e("a",pr,[r("@mauricioszaba"),l(t)])]),e("li",null,[r("Added: Branding Config on Global Atom API "),e("a",hr,[r("@confused-Techie"),l(t)])]),e("li",null,[r("Added: "),dr,r(" as method to build editor. "),e("a",cr,[r("@mauricioszabo"),l(t)])]),e("li",null,[r("Bumped: "),_r,r(" 0.15.0 -> 0.19.0 "),e("a",fr,[r("@kaosine"),l(t)])]),e("li",null,[r("Bumped: "),br,r(" 13.18.5 -> 13.18.6 "),e("a",gr,[r("@kaosine"),l(t)])]),e("li",null,[r("Decaffeinate: Numerous efforts from many contributors to decaffeinate the editor: "),e("ul",null,[e("li",null,[e("a",mr,[r("@Sertonix"),l(t)])]),e("li",null,[e("a",kr,[r("@confused-Techie"),l(t)])]),e("li",null,[e("a",xr,[r("@Spiker985"),l(t)])]),e("li",null,[e("a",vr,[r("@fabianfiorotto"),l(t)])])])]),e("li",null,[r("Rebrand: Numerous efforts from many contributors to rebrand the editor: "),e("ul",null,[e("li",null,[e("a",wr,[r("@Daeraxa"),l(t)])]),e("li",null,[e("a",yr,[r("@mauricioszabo"),l(t)])]),e("li",null,[e("a",Ar,[r("@Daeraxa"),l(t)])]),e("li",null,[e("a",Tr,[r("@Sertonix"),l(t)])]),e("li",null,[e("a",Br,[r("@Daeraxa"),l(t)])]),e("li",null,[e("a",Sr,[r("@mauricioszabo"),l(t)])]),e("li",null,[e("a",zr,[r("@confused-Techie"),l(t)])]),e("li",null,[e("a",Rr,[r("@ElectronicsArchiver"),l(t)])]),e("li",null,[e("a",Fr,[r("@Sertonix"),l(t)])]),e("li",null,[e("a",Pr,[r("@confused-Techie"),l(t)])]),e("li",null,[e("a",Nr,[r("@Sertonix"),l(t)])]),e("li",null,[e("a",Dr,[r("@Daeraxa"),l(t)])]),e("li",null,[e("a",Ir,[r("@Spiker985"),l(t)])]),e("li",null,[e("a",Cr,[r("@kaosine"),l(t)])]),e("li",null,[e("a",Er,[r("@Spiker985"),l(t)])]),e("li",null,[e("a",Gr,[r("@mauricioszabo"),l(t)])]),e("li",null,[e("a",Lr,[r("@mauricioszabo"),l(t)])]),e("li",null,[e("a",Mr,[r("@Spiker985"),l(t)])]),e("li",null,[e("a",Ur,[r("@softcode589"),l(t)])]),e("li",null,[e("a",jr,[r("@LandarXT"),l(t)])]),e("li",null,[e("a",Wr,[r("@confused-Techie"),l(t)])])])]),e("li",null,[r("Tests: Numerous efforts from many contributors to improve our tests: "),e("ul",null,[e("li",null,[e("a",Or,[r("@icecream17"),l(t)])]),e("li",null,[e("a",Vr,[r("@confused-Techie"),l(t)])]),e("li",null,[e("a",Hr,[r("@DeeDeeG"),l(t)])]),e("li",null,[e("a",qr,[r("@Spiker985"),l(t)])]),e("li",null,[e("a",Xr,[r("@confused-Techie"),l(t)])]),e("li",null,[e("a",Jr,[r("@confused-Techie"),l(t)])]),e("li",null,[e("a",Kr,[r("@confused-Techie"),l(t)])]),e("li",null,[e("a",Qr,[r("@confused-Techie"),l(t)])]),e("li",null,[e("a",Yr,[r("@confused-Techie"),l(t)])]),e("li",null,[e("a",Zr,[r("@confused-Techie"),l(t)])]),e("li",null,[e("a",$r,[r("@fabianfiorotto"),l(t)])]),e("li",null,[e("a",et,[r("@fabianfiorotto"),l(t)])]),e("li",null,[e("a",rt,[r("@mauricioszabo"),l(t)])])])])]),tt,e("ul",null,[e("li",null,[r("Fixed: ppm PostInstall "),e("a",lt,[r("@mauricioszabo"),l(t)])]),e("li",null,[r("Added: Better "),ot,r(" command display "),e("a",nt,[r("@mauricioszabo"),l(t)])]),e("li",null,[r("Fixed: Empty Featured Packages "),e("a",st,[r("@jonian"),l(t)])]),e("li",null,[r("Fixed: Use ppm as basename in "),at,r(),e("a",it,[r("@jonain"),l(t)])]),e("li",null,[r("Fixed: Installation from Git "),e("a",ut,[r("@mauricioszabo"),l(t)])]),e("li",null,[r("Added: Ability to define tag to install "),e("a",pt,[r("@mauricioszabo"),l(t)])]),e("li",null,[r("Added: Our new Pulsar Package Repository Backend "),e("a",ht,[r("@confused-Techie"),l(t)])]),e("li",null,[r("Bumped: "),dt,r(" to 12 "),e("a",ct,[r("@mauricioszabo"),l(t)])]),e("li",null,[r("Rebrand: Numerous efforts from many contributors to rebrand ppm: "),e("ul",null,[e("li",null,[e("a",_t,[r("@Sertonix"),l(t)])]),e("li",null,[e("a",ft,[r("@softcode589"),l(t)])]),e("li",null,[e("a",bt,[r("@mauricioszabo"),l(t)])])])]),e("li",null,[r("Tests: Numerous efforts from many contributors to improve our tests: "),e("ul",null,[e("li",null,[e("a",gt,[r("@DeeDeeG"),l(t)])])])])]),mt,e("ul",null,[e("li",null,[r("Fixed: Finding the proper Node version "),e("a",kt,[r("@mauricioszabo"),l(t)])])]),xt,e("ul",null,[e("li",null,[r("Added: Remember Scroll Position "),e("a",vt,[r("@jonian"),l(t)])]),e("li",null,[r("Removed: Support for deprecated packages "),e("a",wt,[r("@Sertonix"),l(t)])]),e("li",null,[r("Added: Better errors when search fails "),e("a",yt,[r("@mauricioszabo"),l(t)])]),e("li",null,[r("Rebrand: Numerous efforts from many contributors to rebrand settings-view: "),e("ul",null,[e("li",null,[e("a",At,[r("@mauricioszabo"),l(t)])]),e("li",null,[e("a",Tt,[r("@softcode589"),l(t)])]),e("li",null,[e("a",Bt,[r("@mauricioszabo"),l(t)])])])]),e("li",null,[r("Tests: Numerous efforts from many contributors to improve our tests: "),e("ul",null,[e("li",null,[e("a",St,[r("@confused-Techie"),l(t)])])])])]),zt,e("ul",null,[e("li",null,[r("Added: Proper Testing "),e("a",Rt,[r("@confused-Techie"),l(t)])]),e("li",null,[r("Removed: "),Ft,r(),e("a",Pt,[r("@Sertonix"),l(t)])]),e("li",null,[r("Fixed: Fix open Snippets URI "),e("a",Nt,[r("@Sertonix"),l(t)])])]),Dt,e("ul",null,[e("li",null,[r("Bumped: "),It,r(" 0.28.0 -> 0.28.1 "),e("a",Ct,[r("@confused-Techie"),l(t)])]),e("li",null,[r("Rebrand: Numerous efforts from many contributors to rebrand background-tips: "),e("ul",null,[e("li",null,[e("a",Et,[r("@Sertonix"),l(t)])]),e("li",null,[e("a",Gt,[r("@Sertonix"),l(t)])]),e("li",null,[e("a",Lt,[r("@Sertonix"),l(t)])])])])])])}const Ot=o(p,[["render",Mt],["__file","20221215-confused-Techie-v1.100.1-beta.html.vue"]]);export{Ot as default}; diff --git a/assets/20221215-confused-Techie-v1.100.1-beta.html.a78b753f.js b/assets/20221215-confused-Techie-v1.100.1-beta.html.a78b753f.js new file mode 100644 index 0000000000..62f718c8e2 --- /dev/null +++ b/assets/20221215-confused-Techie-v1.100.1-beta.html.a78b753f.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-4769b7e2","path":"/blog/20221215-confused-Techie-v1.100.1-beta.html","title":"Our First Release!","lang":"en-US","frontmatter":{"title":"Our First Release!","author":"confused-Techie","date":"2022-12-15T00:00:00.000Z","category":["dev"],"tag":["sunset"]},"excerpt":"

Check out our first GitHub Release for Pulsar! Available Now!

\\n","headers":[{"level":3,"title":"Pulsar","slug":"pulsar","link":"#pulsar","children":[]},{"level":3,"title":"ppm","slug":"ppm","link":"#ppm","children":[]},{"level":3,"title":"autocomplete-html","slug":"autocomplete-html","link":"#autocomplete-html","children":[]},{"level":3,"title":"settings-view","slug":"settings-view","link":"#settings-view","children":[]},{"level":3,"title":"snippets","slug":"snippets","link":"#snippets","children":[]},{"level":3,"title":"background-tips","slug":"background-tips","link":"#background-tips","children":[]}],"git":{"updatedTime":1673835921000,"contributors":[{"name":"confused-Techie","email":"dev@lhbasics.com","commits":2},{"name":"confused_techie","email":"dev@lhbasics.com","commits":1}]},"readingTime":{"minutes":6.8,"words":2039},"filePathRelative":"blog/20221215-confused-Techie-v1.100.1-beta.md","localizedDate":"December 15, 2022"}');export{e as data}; diff --git a/assets/20230114-confused-Techie-v1.101.0-beta.html.2235fad4.js b/assets/20230114-confused-Techie-v1.101.0-beta.html.2235fad4.js new file mode 100644 index 0000000000..74ffd1b69a --- /dev/null +++ b/assets/20230114-confused-Techie-v1.101.0-beta.html.2235fad4.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-59933042","path":"/blog/20230114-confused-Techie-v1.101.0-beta.html","title":"Our Second Beta!","lang":"en-US","frontmatter":{"title":"Our Second Beta!","author":"confused-Techie","date":"2023-01-15T00:00:00.000Z","category":["dev"]},"excerpt":"

Check out our Second GitHub Release for Pulsar! Available Now!

\\n","headers":[{"level":3,"title":"Pulsar","slug":"pulsar","link":"#pulsar","children":[]},{"level":3,"title":"ppm","slug":"ppm","link":"#ppm","children":[]}],"git":{"updatedTime":1679861697000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":3.64,"words":1093},"filePathRelative":"blog/20230114-confused-Techie-v1.101.0-beta.md","localizedDate":"January 15, 2023"}');export{e as data}; diff --git a/assets/20230114-confused-Techie-v1.101.0-beta.html.ba25e8b5.js b/assets/20230114-confused-Techie-v1.101.0-beta.html.ba25e8b5.js new file mode 100644 index 0000000000..87ea44e8be --- /dev/null +++ b/assets/20230114-confused-Techie-v1.101.0-beta.html.ba25e8b5.js @@ -0,0 +1 @@ +import{_ as l,o as n,c as a,a as e,b as r,d as t,e as s,f as i,r as u}from"./app.0e1565ce.js";const d={},p={href:"https://github.com/pulsar-edit/pulsar/releases/tag/v1.101.0-beta",target:"_blank",rel:"noopener noreferrer"},h=i('

Second (Beta) Tagged Release of Pulsar -- Many improvements available!

With the release of Pulsar Beta 1.101.0 we have quite a number of improvements and fixes to various issues that have been identified and logged by our wonderful community.

Have a read of the changelog for the full details but some highlights include fixes to the Pulsar logo on Windows and Linux, a bunch of macOS changes to bring back some of the original functionality with opening files, and the resolution of an annoying issue on Linux that could stop the app from launching at all!

Thank you so much to everyone who has been involved in this release, every comment to our discussions board or Discord server, every issue logged on GitHub and every pull request created have all contributed to getting us where we are today.

And a special thank you for all of the donations we have received, we have some plans to use these wisely for the good of the project so look forward to the next big release!

So please, if you have any comments then please join us on our Discord or discussions page, if you find any bugs or regressions then let us know via a GitHub issue (and if you can fix it (or anything else) then feel free to open a pull request).

And as always, happy coding, see you among the stars!


Pulsar

',11),c=e("code",null,"pulsar",-1),g=e("code",null,"ppm",-1),_={href:"https://github.com/pulsar-edit/pulsar/pull/297",target:"_blank",rel:"noopener noreferrer"},f={href:"https://github.com/pulsar-edit/pulsar/pull/289",target:"_blank",rel:"noopener noreferrer"},b=e("code",null,"packages/README.md",-1),m={href:"https://github.com/pulsar-edit/pulsar/pull/317",target:"_blank",rel:"noopener noreferrer"},k={href:"https://github.com/pulsar-edit/pulsar/pull/318",target:"_blank",rel:"noopener noreferrer"},x={href:"https://github.com/pulsar-edit/pulsar/pull/309",target:"_blank",rel:"noopener noreferrer"},v=e("code",null,"package-lock.json",-1),w={href:"https://github.com/pulsar-edit/pulsar/pull/308",target:"_blank",rel:"noopener noreferrer"},F={href:"https://github.com/pulsar-edit/pulsar/pull/315",target:"_blank",rel:"noopener noreferrer"},y={href:"https://github.com/pulsar-edit/pulsar/pull/305",target:"_blank",rel:"noopener noreferrer"},A={href:"https://github.com/pulsar-edit/pulsar/pull/314",target:"_blank",rel:"noopener noreferrer"},T=e("code",null,"about",-1),R={href:"https://github.com/pulsar-edit/pulsar/pull/310",target:"_blank",rel:"noopener noreferrer"},S={href:"https://github.com/pulsar-edit/pulsar/pull/302",target:"_blank",rel:"noopener noreferrer"},D=e("code",null,"language-html",-1),q={href:"https://github.com/pulsar-edit/pulsar/pull/300",target:"_blank",rel:"noopener noreferrer"},P=e("code",null,"language-javascript",-1),C={href:"https://github.com/pulsar-edit/pulsar/pull/299",target:"_blank",rel:"noopener noreferrer"},B=e("code",null,"image-view",-1),G={href:"https://github.com/pulsar-edit/pulsar/pull/293",target:"_blank",rel:"noopener noreferrer"},M={href:"https://github.com/pulsar-edit/pulsar/pull/292",target:"_blank",rel:"noopener noreferrer"},j={href:"https://github.com/pulsar-edit/pulsar/pull/296",target:"_blank",rel:"noopener noreferrer"},E={href:"https://github.com/pulsar-edit/pulsar/pull/280",target:"_blank",rel:"noopener noreferrer"},I=e("code",null,"archive-view",-1),L={href:"https://github.com/pulsar-edit/pulsar/pull/294",target:"_blank",rel:"noopener noreferrer"},z={href:"https://github.com/pulsar-edit/pulsar/pull/279",target:"_blank",rel:"noopener noreferrer"},W={href:"https://github.com/pulsar-edit/pulsar/pull/290",target:"_blank",rel:"noopener noreferrer"},N={href:"https://github.com/pulsar-edit/pulsar/pull/291",target:"_blank",rel:"noopener noreferrer"},O={href:"https://github.com/pulsar-edit/pulsar/pull/285",target:"_blank",rel:"noopener noreferrer"},V={href:"https://github.com/pulsar-edit/pulsar/pull/288",target:"_blank",rel:"noopener noreferrer"},H={href:"https://github.com/pulsar-edit/pulsar/pull/282",target:"_blank",rel:"noopener noreferrer"},U={href:"https://github.com/pulsar-edit/pulsar/pull/273",target:"_blank",rel:"noopener noreferrer"},J={href:"https://github.com/pulsar-edit/pulsar/pull/276",target:"_blank",rel:"noopener noreferrer"},K={href:"https://github.com/pulsar-edit/pulsar/pull/265",target:"_blank",rel:"noopener noreferrer"},Q={href:"https://github.com/pulsar-edit/pulsar/pull/243",target:"_blank",rel:"noopener noreferrer"},X={href:"https://github.com/pulsar-edit/pulsar/pull/267",target:"_blank",rel:"noopener noreferrer"},Y={href:"https://github.com/pulsar-edit/pulsar/pull/269",target:"_blank",rel:"noopener noreferrer"},Z={href:"https://github.com/pulsar-edit/pulsar/pull/271",target:"_blank",rel:"noopener noreferrer"},$={href:"https://github.com/pulsar-edit/pulsar/pull/263",target:"_blank",rel:"noopener noreferrer"},ee={href:"https://github.com/pulsar-edit/pulsar/pull/252",target:"_blank",rel:"noopener noreferrer"},re={href:"https://github.com/pulsar-edit/pulsar/pull/228",target:"_blank",rel:"noopener noreferrer"},oe={href:"https://github.com/pulsar-edit/pulsar/pull/232",target:"_blank",rel:"noopener noreferrer"},te=e("code",null,"--no-sandbox",-1),le={href:"https://github.com/pulsar-edit/pulsar/pull/262",target:"_blank",rel:"noopener noreferrer"},ne={href:"https://github.com/pulsar-edit/pulsar/pull/219",target:"_blank",rel:"noopener noreferrer"},ae={href:"https://github.com/pulsar-edit/pulsar/pull/245",target:"_blank",rel:"noopener noreferrer"},se={href:"https://github.com/pulsar-edit/pulsar/pull/244",target:"_blank",rel:"noopener noreferrer"},ie={href:"https://github.com/pulsar-edit/pulsar/pull/250",target:"_blank",rel:"noopener noreferrer"},ue=e("code",null,"packages/README.md",-1),de={href:"https://github.com/pulsar-edit/pulsar/pull/248",target:"_blank",rel:"noopener noreferrer"},pe={href:"https://github.com/pulsar-edit/pulsar/pull/169",target:"_blank",rel:"noopener noreferrer"},he=e("code",null,"underscore-plus",-1),ce={href:"https://github.com/pulsar-edit/pulsar/pull/218",target:"_blank",rel:"noopener noreferrer"},ge=e("h3",{id:"ppm",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#ppm","aria-hidden":"true"},"#"),r(" ppm")],-1),_e={href:"https://github.com/pulsar-edit/ppm/pull/47",target:"_blank",rel:"noopener noreferrer"},fe={href:"https://github.com/pulsar-edit/ppm/pull/43",target:"_blank",rel:"noopener noreferrer"};function be(me,ke){const o=u("ExternalLinkIcon");return n(),a("div",null,[e("p",null,[r("Check out our Second GitHub Release for Pulsar! "),e("a",p,[r("Available Now!"),t(o)])]),s(" more "),h,e("ul",null,[e("li",null,[r("Added: script: Clean up "),c,r(" and "),g,r(" on uninstall "),e("a",_,[r("@DeeDeeG"),t(o)])]),e("li",null,[r("Added: increase search query delay "),e("a",f,[r("@Sertonix"),t(o)])]),e("li",null,[r("Fixed: update "),b,r(),e("a",m,[r("@Sertonix"),t(o)])]),e("li",null,[r("Fixed: Fix Windows Icon being cut in half "),e("a",k,[r("@confused-Techie"),t(o)])]),e("li",null,[r("Removed: remove unused json "),e("a",x,[r("@Sertonix"),t(o)])]),e("li",null,[r("Added: add ignored "),v,r(" to packages "),e("a",w,[r("@Sertonix"),t(o)])]),e("li",null,[r("Rebrand: Rebrand AppUserModelID - Ensure Pulsar is separated as its own App Icon on Windows "),e("a",F,[r("@confused-Techie"),t(o)])]),e("li",null,[r("Removed: remove fs-plus from image-view package "),e("a",y,[r("@Sertonix"),t(o)])]),e("li",null,[r("Added: Additional Bundling of Core Packages "),e("a",A,[r("@confused-Techie"),t(o)])]),e("li",null,[r("Fixed: Resolve some "),T,r(" package tests (6 Resolved Tests) "),e("a",R,[r("@confused-Techie"),t(o)])]),e("li",null,[r("Fixed: Fix Package Test Cache Issue "),e("a",S,[r("@confused-Techie"),t(o)])]),e("li",null,[r("Fixed: Resolve all Tests within "),D,r(" (Resolves 2 Failing Tests) "),e("a",q,[r("@confused-Techie"),t(o)])]),e("li",null,[r("Fixed: Resolve all Tests within "),P,r(" (Resolves 24 Failing Tests) "),e("a",C,[r("@confused-Techie"),t(o)])]),e("li",null,[r("Fixed: Resolve 40 Failing "),B,r(" Tests "),e("a",G,[r("@confused-Techie"),t(o)])]),e("li",null,[r("Added: Added changelog entries that we missed "),e("a",M,[r("@mauricioszabo"),t(o)])]),e("li",null,[r("Removed: meta: Delete preinstall script from package.json "),e("a",j,[r("@DeeDeeG"),t(o)])]),e("li",null,[r("Added: Improve MacOS Builds "),e("a",E,[r("@confused-Techie"),t(o)])]),e("li",null,[r("Fixed: Fix "),I,r(),e("a",L,[r("@confused-Techie"),t(o)])]),e("li",null,[r("Added: Improved Windows Builds "),e("a",z,[r("@confused-Techie"),t(o)])]),e("li",null,[r("Added: More Bundles "),e("a",W,[r("@confused-Techie"),t(o)])]),e("li",null,[r("Fixed: Fix macos open without window "),e("a",N,[r("@mauricioszabo"),t(o)])]),e("li",null,[r("Removed: delete workflow from language-java "),e("a",O,[r("@Sertonix"),t(o)])]),e("li",null,[r("Removed: Remove handlers for opening things on Mac "),e("a",V,[r("@mauricioszabo"),t(o)])]),e("li",null,[r("Rebrand: Rebranding and relinking to new site "),e("a",H,[r("@Daeraxa"),t(o)])]),e("li",null,[r("Added: script: symlink ppm in post-install.sh (for .deb and .rpm packages) "),e("a",U,[r("@DeeDeeG"),t(o)])]),e("li",null,[r("Added: Add --no-sandbox to start script "),e("a",J,[r("@Daeraxa"),t(o)])]),e("li",null,[r("Added: exclude directories from build "),e("a",K,[r("@Sertonix"),t(o)])]),e("li",null,[r("Added: add warning when settings-view is disabled "),e("a",Q,[r("@Sertonix"),t(o)])]),e("li",null,[r("Fixed: Fix typo "),e("a",X,[r("@snowcatridge10"),t(o)])]),e("li",null,[r("Fixed: Fix install on packaged code "),e("a",Y,[r("@mauricioszabo"),t(o)])]),e("li",null,[r("Fixed: Fix Logo weirdness "),e("a",Z,[r("@mauricioszabo"),t(o)])]),e("li",null,[r("Fixed: Fix installing shell commands to path (macOS) "),e("a",$,[r("@DeeDeeG"),t(o)])]),e("li",null,[r("Fixed: \u{1F34E} Fix wrong app name resolution in pulsar.sh on Mac "),e("a",ee,[r("@soupertonic"),t(o)])]),e("li",null,[r("Fixed: Postinstall error with rm usr/bin/pulsar "),e("a",re,[r("@Spiker985"),t(o)])]),e("li",null,[r("Added: Made changes to the main.js file. "),e("a",oe,[r("@CatPerson136"),t(o)])]),e("li",null,[r("Added: Add "),te,r(" to Linux Launch "),e("a",le,[r("@confused-Techie"),t(o)])]),e("li",null,[r("Removed: removed unused files "),e("a",ne,[r("@Sertonix"),t(o)])]),e("li",null,[r("Rebrand: rebrand package publish domain "),e("a",ae,[r("@Sertonix"),t(o)])]),e("li",null,[r("Removed: remove metrics code from welcome package "),e("a",se,[r("@Sertonix"),t(o)])]),e("li",null,[r("Fixed: Deep cache for settings view "),e("a",ie,[r("@mauricioszabo"),t(o)])]),e("li",null,[r("Fixed: fix syntax error in "),ue,r(),e("a",de,[r("@Sertonix"),t(o)])]),e("li",null,[r("Removed: remove package.json dependencies "),e("a",pe,[r("@Sertonix"),t(o)])]),e("li",null,[r("Added: "),he,r(" to dependencies "),e("a",ce,[r("@Sertonix"),t(o)])])]),ge,e("ul",null,[e("li",null,[r("Added: Convert body params to query params "),e("a",_e,[r("@Spiker985"),t(o)])]),e("li",null,[r("Fixed: src: Update Electron header download URL "),e("a",fe,[r("@DeeDeeG"),t(o)])])])])}const ve=l(d,[["render",be],["__file","20230114-confused-Techie-v1.101.0-beta.html.vue"]]);export{ve as default}; diff --git a/assets/20230201-Daeraxa-FebUpdate.html.3f2403f8.js b/assets/20230201-Daeraxa-FebUpdate.html.3f2403f8.js new file mode 100644 index 0000000000..cbd18da7bf --- /dev/null +++ b/assets/20230201-Daeraxa-FebUpdate.html.3f2403f8.js @@ -0,0 +1 @@ +import{_ as a,o as r,c as s,e as i,a as e,b as t,d as n,r as h}from"./app.0e1565ce.js";const l={},d=e("p",null,"What has the Pulsar team and community been up to lately?",-1),c=e("h1",{id:"community-update",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#community-update","aria-hidden":"true"},"#"),t(" Community Update")],-1),u=e("p",null,[t("Hi everyone, welcome to our first post in what we hope will be a regular (or at least semi-regular) set of updates from the Pulsar team to let you know what is going on in the background. We see updates, work and improvements to the entire Pulsar ecosystem nearly every single day but many of these either aren't very obvious or requires a very close following of the various communication channels."),e("br"),t(" This update format is designed to let people know of our major wins and exciting updates of things we are working on so you can share in the progress we are making."),e("br"),t(" We hope you enjoy reading this and getting to know what is going on with the project and as ever we will see you among the stars!")],-1),p=e("h2",{id:"tree-sitter",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#tree-sitter","aria-hidden":"true"},"#"),t(" Tree-sitter")],-1),g={href:"https://tree-sitter.github.io/tree-sitter/",target:"_blank",rel:"noopener noreferrer"},f=e("p",null,"Ultimately Tree-sitter moved on from Atom leaving it with a different implementation and one which is difficult to use with the changes in newer Electron versions.",-1),m={href:"https://github.com/tree-sitter/tree-sitter/blob/master/lib/binding_web/README.md",target:"_blank",rel:"noopener noreferrer"},w={href:"https://github.com/mauricioszabo",target:"_blank",rel:"noopener noreferrer"},b=e("h2",{id:"package-repository",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#package-repository","aria-hidden":"true"},"#"),t(" Package repository")],-1),_=e("p",null,"There has been an awful lot of work going on in this area and it is mostly work that, in theory, should be pretty invisible to most users of Pulsar if all goes well.",-1),y={href:"https://github.com/confused-Techie",target:"_blank",rel:"noopener noreferrer"},k={href:"https://github.com/Digitalone1",target:"_blank",rel:"noopener noreferrer"},v={href:"https://github.com/spiker985",target:"_blank",rel:"noopener noreferrer"},x={href:"https://api.pulsar-edit.dev/swagger-ui/",target:"_blank",rel:"noopener noreferrer"},T={href:"https://github.com/confused-Techie",target:"_blank",rel:"noopener noreferrer"},A={href:"https://github.com/pulsar-edit/package-backend/blob/main/ARCHITECTURE.md",target:"_blank",rel:"noopener noreferrer"},E=e("p",null,"In terms of some package related housekeeping, one of our main goals was to preserve as many of the Atom packages as possible. So when we found that some packages had some ambiguous or missing licences we have made the effort to contact the owner of each package and ask for permission to host them. The good news is that this has been well received and we have managed to preserve more than a few of them.",-1),S=e("h2",{id:"donations-and-binary-signing",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#donations-and-binary-signing","aria-hidden":"true"},"#"),t(" Donations and binary signing")],-1),C=e("p",null,[t("We have had an awful lot of generous donations sent to us recently. In particular we have to give a massive shout out to "),e("strong",null,[e("em",null,"@SubAtomic")]),t(" who has donated more money to the project alone than we thought we get in a whole year.")],-1),D=e("strong",null,[e("em",null,"@anonCoffee")],-1),P={href:"https://github.com/sponsors/pulsar-edit",target:"_blank",rel:"noopener noreferrer"},j={href:"https://opencollective.com/pulsar-edit",target:"_blank",rel:"noopener noreferrer"},M=e("p",null,[t("We also now have a webhook set up for the GitHub sponsors so now when we get a donation we can properly thank them as it pops up on our Discord "),e("code",null,"#donations"),t(" channel.")],-1),I=e("p",null,"A huge thank you to literally all of our other donators on both platforms, we appreciate everything you give and make so much of what we want to do, possible.",-1),W={href:"https://github.com/Meadowsys",target:"_blank",rel:"noopener noreferrer"},U=e("h2",{id:"matrix-space",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#matrix-space","aria-hidden":"true"},"#"),t(" Matrix space")],-1),H={href:"https://github.com/kaosine",target:"_blank",rel:"noopener noreferrer"},O={href:"https://matrix.org/",target:"_blank",rel:"noopener noreferrer"},B={href:"https://discord.gg/7aEbB9dGRT",target:"_blank",rel:"noopener noreferrer"};function N(R,V){const o=h("ExternalLinkIcon");return r(),s("div",null,[d,i(" more "),c,u,p,e("p",null,[t("Those who have been keeping up with the development of Pulsar know that one of our major goals is to upgrade to newer versions of Electron. Unfortunately this comes with quite a few challenges, one of which is the implementation of "),e("a",g,[t("Tree-sitter"),n(o)]),t(", a library used for syntax highlighting who's history is entwined with Atom's.")]),f,e("p",null,[t("So instead of trying to fix the old one, the current goal is to look at using the "),e("a",m,[t("WASM version"),n(o)]),t(" and "),e("a",w,[t("@Maur\xEDcio Szabo"),n(o)]),t(` has been doing some fantastic work on getting this implemented into Pulsar. This post isn't for the specifics of this work save to say we have seen "interesting" issues and challenges with this but the work is looking very promising and if all works out then our main barrier to a major Electron upgrade will be out of our way.`)]),b,_,e("p",null,[e("a",y,[t("@confused-techie"),n(o)]),t(" and "),e("a",k,[t("@Digitalone1"),n(o)]),t(" have been doing some amazing work to improve this area with updates to the way it handles versioning and a refactoring of the git and github interactions so hopefully we might support other platforms in the future.")]),e("p",null,[e("a",v,[t("@Spiker985"),n(o)]),t(" has been creating and updating the "),e("a",x,[t("Swagger/OpenAPI definition"),n(o)]),t(" for the backend API so we have a proper definition and validation of it and "),e("a",T,[t("@confused-techie"),n(o)]),t(" started the "),e("a",A,[t("Architecture document"),n(o)]),t(" which explains how everything works at a high level to allow people to more easily contribute to the project. We hope to include more of these in other repositories as time goes on.")]),E,S,C,e("p",null,[D,t(" also gave us a very generous donation and was also the first sponsor on our new "),e("a",P,[t("GitHub Sponsorship"),n(o)]),t(" which is an alternative should you want to donate by a platform other than our existing "),e("a",j,[t("Open Collective"),n(o)]),t(".")]),M,I,e("p",null,[t("Our first big expenditure we voted for (outside of ongoing hosting costs) will be to pay for Code Signing Certificates from Apple and Microsoft so that we can finally sign our binaries to prevent the issue of new users being told that they are broken, dangerous or may contain harmful code. "),e("a",W,[t("@Meadowsys"),n(o)]),t(" has already started on the macOS process of this so with any luck the need to run a special command just to run our software will be a thing of the past.")]),U,e("p",null,[e("a",H,[t("@kaosine"),n(o)]),t(" has been working to set us up a "),e("a",O,[t("Matrix"),n(o)]),t(" space for an alternative for people who would prefer to use an open source alternative to our main "),e("a",B,[t("Discord Server"),n(o)]),t(". However, we do not want to split the community in this way so before we start publicising it and providing links we would rather get our Discord bridge set up first so that all messages can be seen on both platforms seamlessly. Once it is up and live we will update the community with all the info that could be needed.")])])}const G=a(l,[["render",N],["__file","20230201-Daeraxa-FebUpdate.html.vue"]]);export{G as default}; diff --git a/assets/20230201-Daeraxa-FebUpdate.html.78289db0.js b/assets/20230201-Daeraxa-FebUpdate.html.78289db0.js new file mode 100644 index 0000000000..75d5cd5835 --- /dev/null +++ b/assets/20230201-Daeraxa-FebUpdate.html.78289db0.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-3a050031","path":"/blog/20230201-Daeraxa-FebUpdate.html","title":"Community Update","lang":"en-US","frontmatter":{"title":"Community Update","author":"Daeraxa","date":"2023-02-01T00:00:00.000Z","category":["news","log"],"tag":["update"]},"excerpt":"

What has the Pulsar team and community been up to lately?

\\n","headers":[{"level":2,"title":"Tree-sitter","slug":"tree-sitter","link":"#tree-sitter","children":[]},{"level":2,"title":"Package repository","slug":"package-repository","link":"#package-repository","children":[]},{"level":2,"title":"Donations and binary signing","slug":"donations-and-binary-signing","link":"#donations-and-binary-signing","children":[]},{"level":2,"title":"Matrix space","slug":"matrix-space","link":"#matrix-space","children":[]}],"git":{"updatedTime":1675286848000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":8}]},"readingTime":{"minutes":3.16,"words":948},"filePathRelative":"blog/20230201-Daeraxa-FebUpdate.md","localizedDate":"February 1, 2023"}');export{e as data}; diff --git a/assets/20230209-mauricioszabo-tree-sitter-part-1.html.580bcde1.js b/assets/20230209-mauricioszabo-tree-sitter-part-1.html.580bcde1.js new file mode 100644 index 0000000000..8f5f339f98 --- /dev/null +++ b/assets/20230209-mauricioszabo-tree-sitter-part-1.html.580bcde1.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-5fdcc4a1","path":"/blog/20230209-mauricioszabo-tree-sitter-part-1.html","title":"Tales of Tree-Sitter part 1: the start of a tale","lang":"en-US","frontmatter":{"title":"Tales of Tree-Sitter part 1: the start of a tale","author":"Maur\xEDcio","date":"2023-02-09T00:00:00.000Z","category":["dev"],"tag":["modernization","tree-sitter"]},"excerpt":"

How did I decide to start working on tree-sitter, and all preparations to\\nmodernize it on Pulsar

\\n","headers":[{"level":2,"title":"Current Pulsar implementation","slug":"current-pulsar-implementation","link":"#current-pulsar-implementation","children":[]},{"level":2,"title":"Modernizing the current implementation","slug":"modernizing-the-current-implementation","link":"#modernizing-the-current-implementation","children":[]}],"git":{"updatedTime":1676229228000,"contributors":[{"name":"Maur\xEDcio Szabo","email":"mauricio.szabo@gmail.com","commits":2},{"name":"Maur\xEDcio Szabo","email":"mauricioszabo@users.noreply.github.com","commits":2}]},"readingTime":{"minutes":2.96,"words":889},"filePathRelative":"blog/20230209-mauricioszabo-tree-sitter-part-1.md","localizedDate":"February 9, 2023"}');export{e as data}; diff --git a/assets/20230209-mauricioszabo-tree-sitter-part-1.html.80ed8667.js b/assets/20230209-mauricioszabo-tree-sitter-part-1.html.80ed8667.js new file mode 100644 index 0000000000..0b1f516881 --- /dev/null +++ b/assets/20230209-mauricioszabo-tree-sitter-part-1.html.80ed8667.js @@ -0,0 +1 @@ +import{_ as e,o as t,c as a,e as i,a as o,f as r}from"./app.0e1565ce.js";const n={},s=o("p",null,"How did I decide to start working on tree-sitter, and all preparations to modernize it on Pulsar",-1),h=r('

Tree-Sitter

First, what is tree-sitter? Tree-sitter is basically a library that reads the source code and parses into an abstract syntax tree. That tree can then be used to find indentation rules or highlight elements like local variables, symbols, tags and everything.

So, it's an alternative for TextMate grammars, but more powerful and faster. It also supports "incremental parsing", meaning that if you have a huge file that takes some time to highlight, when you make changes to that document you only need to wait for a fraction of time to parse the contents.

Current Pulsar implementation

The way that tree-sitter works right now is basically is the same way that it was implemented in Atom a long time ago: it basically tries to match the elements of the AST and convert that into TextMate scopes. So, supposing that it finds, for example, a left right assignment (something like a = 10)- it will basically try to convert the "left token" to a variable.

That's where the problems begin. The new versions of tree-sitter are changing the AST in a way that Atom (or Pulsar) does not support. The second problem is this remapping: currently, it uses some pattern in CSON files, with some kind of "CSS selector"-based metalanguage. CSON is basically a YAML version of JSON and it only works in Atom (and now in Pulsar)...

Basically - it means that all this remapping only works on Pulsar, and have no other editors that supports this; which, again, means that every version bump for grammars will have to keep remapping things over and over again (considering that all tree elements and nodes are considered "internal state" of each grammar) which is far from ideal (and does not scale, specially considering that we're a small team). Finally, it means that we need a huge number of tests that are copies of Tree-Sitter and TextMate just to be sure we're doing the same job that others are doing (again, far from ideal, specially considering that programming languages evolve, and we need to keep track of all this evolution, for all languages that Pulsar supports - a work that, basically, these grammars are already doing, and will probably do better than us).

So, we would like to reuse what other editors use, and have the exact same experience without having to, from time to time, revisit all these "remappings" and that is where the queries come into action:

Modern tree-sitter uses queries, that are basically a scheme-inspired file that basically maps AST to syntax elements (for highlighting), tags elements as local variables (also to help highlighting) and basically is more powerfull than the metalanguage we use currently in Pulsar.

Modernizing the current implementation

So recently, I was surprised with some very rare free time, and decided to work on modernizing tree-sitter on the editor. The main reason is also because, when we upgraded Pulsar to use Electron 12, we also have to update both tree-sitter and grammars, some of these are now not highlighting as they should. That, combined with the fact that the version of tree-sitter we're using (the native one, that have bindings to Node.JS) will stop working after electron 14 (that is, two versions for now) made me try to implement a WASM-based version.

And that where the problems begin.

Basically, nobody knows exactly how a "modern implementation" of tree-sitter looks like. I tried to talk with different plugin authors on different editor and they all gave me different responses. Most people rewrite the queries that come with tree-sitter, for example... questions that I asked, like for example, if there's a pattern for tokens, were answered with "if there's a pattern nobody actually learned that or even documented it"; also about queries, most people answered me that "that the query result is up to interpretation" which is not fine for my approaches...

And so, without any official guide, I decided to try to match what the tree-sitter command-line interface does. Tree-sitter has a CLI that allows to highlight files. You run with tree-sitter highlight <name-of-file>, basically, and I found that it uses a new library that's basically not yet complete and does not have bindings for the browser (WASM) or Node. So first, I tried to analyze if it was going to be possible to expose this library to JS, but I didn't find a way to make "incremental parsing" (a huge interesting feature of tree-sitter where you basically inform to the highlighter what changed instead of the whole tree, and the library will only re-tokenize that specific fragment instead).

Then, I decided to do what some every sane person would probably not do - implement everything by hand, improvising where things didn't work. Tokenize with the CLI, and try to match the same structure in Pulsar, fixing things where that didn't work... and that's part one.

',15);function l(d,m){return t(),a("div",null,[s,i(" more "),h])}const c=e(n,[["render",l],["__file","20230209-mauricioszabo-tree-sitter-part-1.html.vue"]]);export{c as default}; diff --git a/assets/20230215-Daeraxa-v1.102.0.html.5122191c.js b/assets/20230215-Daeraxa-v1.102.0.html.5122191c.js new file mode 100644 index 0000000000..dd2f51689d --- /dev/null +++ b/assets/20230215-Daeraxa-v1.102.0.html.5122191c.js @@ -0,0 +1 @@ +import{_ as o,o as a,c as n,a as e,b as t,d as l,e as s,f as i,r as d}from"./app.0e1565ce.js";const u={},p={href:"https://github.com/pulsar-edit/pulsar/releases/tag/v1.102.0",target:"_blank",rel:"noopener noreferrer"},c=i('

The next Pulsar Release 1.102.0!

With the release of Pulsar 1.102.0 we have packed it full of improvements!

With a huge focus on testing this time around we hope that this will result in a more stable and functional editor in all future releases.

We've also added new icons for macOS and updated many of Pulsar and core packages dependencies.

But for the big news, our macOS releases are now signed! Meaning no fancy commands need to be run prior to installation and you get a guarantee that Pulsar is made and published by us. Also as you might've noticed, this version has no beta in its name. We are now adopting a Rolling Release model and this can be considered the first of many stable releases. Take a look at our website for more information!

And again, special thank you to all of our wonderful community members that have helped us write code, log issues, respond to everyone, and donate to the project. We truly could not do this without each and every one of you.

And as always, happy coding, see you among the stars!


Pulsar

',11),h={href:"https://github.com/pulsar-edit/pulsar/pull/4",target:"_blank",rel:"noopener noreferrer"},_={href:"https://github.com/pulsar-edit/pulsar/pull/387",target:"_blank",rel:"noopener noreferrer"},g=e("code",null,"python",-1),f=e("code",null,"3.10",-1),b={href:"https://github.com/pulsar-edit/pulsar/pull/384",target:"_blank",rel:"noopener noreferrer"},m=e("code",null,"ppm",-1),k=e("code",null,"a46537c0b7f0eaaef5404ef88003951fdc988c65",-1),x={href:"https://github.com/pulsar-edit/pulsar/pull/383",target:"_blank",rel:"noopener noreferrer"},y={href:"https://github.com/pulsar-edit/pulsar/pull/372",target:"_blank",rel:"noopener noreferrer"},v={href:"https://github.com/pulsar-edit/pulsar/pull/378",target:"_blank",rel:"noopener noreferrer"},D={href:"https://github.com/pulsar-edit/pulsar/pull/375",target:"_blank",rel:"noopener noreferrer"},w={href:"https://github.com/pulsar-edit/pulsar/pull/238",target:"_blank",rel:"noopener noreferrer"},F={href:"https://github.com/pulsar-edit/pulsar/pull/376",target:"_blank",rel:"noopener noreferrer"},S={href:"https://github.com/pulsar-edit/pulsar/pull/369",target:"_blank",rel:"noopener noreferrer"},A={href:"https://github.com/pulsar-edit/pulsar/pull/373",target:"_blank",rel:"noopener noreferrer"},U=e("code",null,"coffeescript",-1),T={href:"https://github.com/pulsar-edit/pulsar/pull/361",target:"_blank",rel:"noopener noreferrer"},P={href:"https://github.com/pulsar-edit/pulsar/pull/362",target:"_blank",rel:"noopener noreferrer"},M=e("code",null,"autocomplete-plus",-1),R={href:"https://github.com/pulsar-edit/pulsar/pull/358",target:"_blank",rel:"noopener noreferrer"},N={href:"https://github.com/pulsar-edit/pulsar/pull/354",target:"_blank",rel:"noopener noreferrer"},C=e("code",null,"pulsar",-1),G={href:"https://github.com/pulsar-edit/pulsar/pull/340",target:"_blank",rel:"noopener noreferrer"},O=e("code",null,"right-clicked",-1),W={href:"https://github.com/pulsar-edit/pulsar/pull/368",target:"_blank",rel:"noopener noreferrer"},B={href:"https://github.com/pulsar-edit/pulsar/pull/371",target:"_blank",rel:"noopener noreferrer"},E={href:"https://github.com/pulsar-edit/pulsar/pull/367",target:"_blank",rel:"noopener noreferrer"},I=e("code",null,"tabs",-1),V={href:"https://github.com/pulsar-edit/pulsar/pull/357",target:"_blank",rel:"noopener noreferrer"},z={href:"https://github.com/pulsar-edit/pulsar/pull/366",target:"_blank",rel:"noopener noreferrer"},L={href:"https://github.com/pulsar-edit/pulsar/pull/356",target:"_blank",rel:"noopener noreferrer"},j={href:"https://github.com/pulsar-edit/pulsar/pull/352",target:"_blank",rel:"noopener noreferrer"},J={href:"https://github.com/pulsar-edit/pulsar/pull/307",target:"_blank",rel:"noopener noreferrer"},$={href:"https://github.com/pulsar-edit/pulsar/pull/341",target:"_blank",rel:"noopener noreferrer"},q={href:"https://github.com/pulsar-edit/pulsar/pull/337",target:"_blank",rel:"noopener noreferrer"},H={href:"https://github.com/pulsar-edit/pulsar/pull/336",target:"_blank",rel:"noopener noreferrer"},K=e("code",null,"let",-1),Q=e("code",null,"const",-1),X={href:"https://github.com/pulsar-edit/pulsar/pull/326",target:"_blank",rel:"noopener noreferrer"},Y={href:"https://github.com/pulsar-edit/pulsar/pull/335",target:"_blank",rel:"noopener noreferrer"},Z={href:"https://github.com/pulsar-edit/pulsar/pull/332",target:"_blank",rel:"noopener noreferrer"},ee=e("h3",{id:"ppm",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#ppm","aria-hidden":"true"},"#"),t(" ppm")],-1),te=e("code",null,"master",-1),re={href:"https://github.com/pulsar-edit/ppm/pull/56",target:"_blank",rel:"noopener noreferrer"},le={href:"https://github.com/pulsar-edit/ppm/pull/54",target:"_blank",rel:"noopener noreferrer"},oe={href:"https://github.com/pulsar-edit/ppm/pull/52",target:"_blank",rel:"noopener noreferrer"},ae={href:"https://github.com/pulsar-edit/ppm/pull/48",target:"_blank",rel:"noopener noreferrer"},ne=e("h3",{id:"github",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#github","aria-hidden":"true"},"#"),t(" github")],-1),se=e("code",null,"git",-1),ie={href:"https://github.com/pulsar-edit/github/pull/13",target:"_blank",rel:"noopener noreferrer"},de=e("code",null,"master",-1),ue={href:"https://github.com/pulsar-edit/github/pull/12",target:"_blank",rel:"noopener noreferrer"};function pe(ce,he){const r=d("ExternalLinkIcon");return a(),n("div",null,[e("p",null,[t("Check out our newest Regular Release for Pulsar! "),e("a",p,[t("Available Now!"),l(r)])]),s(" more "),c,e("ul",null,[e("li",null,[t("Added: implement signing and notarizing for macOS, PR "),e("a",h,[t("#4"),l(r)]),t(" lol "),e("a",_,[t("@Meadowsys"),l(r)])]),e("li",null,[t("Fixed: Pin "),g,t(" brew installation to "),f,t(" during MacOS Intel Cirrus Build "),e("a",b,[t("@confused-Techie"),l(r)])]),e("li",null,[t("Update: Bump "),m,t(" to "),k,t(),e("a",x,[t("@confused-Techie"),l(r)])]),e("li",null,[t("Added: Add new macOS icon "),e("a",y,[t("@mdibella-dev"),l(r)])]),e("li",null,[t("Fixed: type $ as # "),e("a",v,[t("@Meadowsys"),l(r)])]),e("li",null,[t("Update: deps: Update github to v0.36.14-pretranspiled-take-2 "),e("a",D,[t("@DeeDeeG"),l(r)])]),e("li",null,[t("Added: add style to selected text by default "),e("a",w,[t("@Sertonix"),l(r)])]),e("li",null,[t("Added: Set Max Concurrent Package Tests "),e("a",F,[t("@confused-Techie"),l(r)])]),e("li",null,[t("Fixed: c++ fixes "),e("a",S,[t("@icecream17"),l(r)])]),e("li",null,[t("Update: deps: Update github to v0.36.14-pretranspiled "),e("a",A,[t("@DeeDeeG"),l(r)])]),e("li",null,[t("Update "),U,t(),e("a",T,[t("@confused-Techie"),l(r)])]),e("li",null,[t("Updated: Misc Dependency Updates "),e("a",P,[t("@confused-Techie"),l(r)])]),e("li",null,[t("Added: Bundle "),M,t(),e("a",R,[t("@confused-Techie"),l(r)])]),e("li",null,[t("Fixed: Add LICENSE.md to extra resources (resourcesPath) "),e("a",N,[t("@Daeraxa"),l(r)])]),e("li",null,[t("Fixed: Get Windows "),C,t(" Working "),e("a",G,[t("@confused-Techie"),l(r)])]),e("li",null,[t("Fixed: Restore "),O,t(" class on a right-clicked tab "),e("a",W,[t("@savetheclocktower"),l(r)])]),e("li",null,[t("Updated: ppm: Update submodule to commit 4645ba2905747897b0 "),e("a",B,[t("@DeeDeeG"),l(r)])]),e("li",null,[t("Added: Machine decaf tabs spec "),e("a",E,[t("@confused-Techie"),l(r)])]),e("li",null,[t("Added: Manually Decaf "),I,t(" package Specs "),e("a",V,[t("@confused-Techie"),l(r)])]),e("li",null,[t("Fixed: Uncomment and fix a settings-view package test "),e("a",z,[t("@DeeDeeG"),l(r)])]),e("li",null,[t("Added: Decaf Changes from Manual and Machine Decaf to Main "),e("a",L,[t("@confused-Techie"),l(r)])]),e("li",null,[t("Added: Manual decafe tabs "),e("a",j,[t("@confused-Techie"),l(r)])]),e("li",null,[t("Added: Organize failing tests "),e("a",J,[t("@mauricioszabo"),l(r)])]),e("li",null,[t("Fixed: autocomplete-snippets: Fix repo URL "),e("a",$,[t("@DeeDeeG"),l(r)])]),e("li",null,[t("Updated: update apm message to pulsar -p "),e("a",q,[t("@Daeraxa"),l(r)])]),e("li",null,[t("Fixed: Replace incorrect spellings of 'macOS' with the correct one "),e("a",H,[t("@mdibella-dev"),l(r)])]),e("li",null,[t("Changed: use "),K,t(" and "),Q,t(" in js snippets "),e("a",X,[t("@Sertonix"),l(r)])]),e("li",null,[t("Fixed: Fix URI to correct address "),e("a",Y,[t("@mdibella-dev"),l(r)])]),e("li",null,[t("Updated: update copyright year (2023) "),e("a",Z,[t("@icecream17"),l(r)])])]),ee,e("ul",null,[e("li",null,[t("Fixed: fix: Don't assume "),te,t(" when checking git packages for upgrades "),e("a",re,[t("@savetheclocktower"),l(r)])]),e("li",null,[t("Fixed: meta: Normalize package.json and lockfile line endings "),e("a",le,[t("@DeeDeeG"),l(r)])]),e("li",null,[t("Update: spec: Fixtures Node v10.20.1 --> Electron v12.2.3 "),e("a",oe,[t("@DeeDeeG"),l(r)])]),e("li",null,[t("Fixed: Fix .com links, pulsar rebranding and rebranding readme "),e("a",ae,[t("@Daeraxa"),l(r)])])]),ne,e("ul",null,[e("li",null,[t("Fixed: lib: Rebrand getAtomAppName() function (fix shelling out to "),se,t(" on macOS) "),e("a",ie,[t("@DeeDeeG"),l(r)])]),e("li",null,[t('Fixed: meta: Revert "main" to "./lib/index", no dist (fix package on '),de,t(" branch) "),e("a",ue,[t("@DeeDeeG"),l(r)])])])])}const ge=o(u,[["render",pe],["__file","20230215-Daeraxa-v1.102.0.html.vue"]]);export{ge as default}; diff --git a/assets/20230215-Daeraxa-v1.102.0.html.6a8c9a26.js b/assets/20230215-Daeraxa-v1.102.0.html.6a8c9a26.js new file mode 100644 index 0000000000..3eb4155fba --- /dev/null +++ b/assets/20230215-Daeraxa-v1.102.0.html.6a8c9a26.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-f88b1c54","path":"/blog/20230215-Daeraxa-v1.102.0.html","title":"New Regular Release (v1.102.0)","lang":"en-US","frontmatter":{"title":"New Regular Release (v1.102.0)","author":"Daeraxa","date":"2023-02-15T00:00:00.000Z","category":["dev"],"tag":["release"]},"excerpt":"

Check out our newest Regular Release for Pulsar! Available Now!

\\n","headers":[{"level":3,"title":"Pulsar","slug":"pulsar","link":"#pulsar","children":[]},{"level":3,"title":"ppm","slug":"ppm","link":"#ppm","children":[]},{"level":3,"title":"github","slug":"github","link":"#github","children":[]}],"git":{"updatedTime":1676602727000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":3}]},"readingTime":{"minutes":2.89,"words":867},"filePathRelative":"blog/20230215-Daeraxa-v1.102.0.md","localizedDate":"February 15, 2023"}');export{e as data}; diff --git a/assets/20230216-Daeraxa-ReleaseStrategyUpdate.html.2cea6acb.js b/assets/20230216-Daeraxa-ReleaseStrategyUpdate.html.2cea6acb.js new file mode 100644 index 0000000000..2983ef2ab1 --- /dev/null +++ b/assets/20230216-Daeraxa-ReleaseStrategyUpdate.html.2cea6acb.js @@ -0,0 +1 @@ +import{_ as n,o as i,c as r,e as l,a as e,b as t,d as o,f as s,r as h}from"./app.0e1565ce.js";const d={},u=e("p",null,"We recently decided to change our release strategy to reflect what we are actually doing and address some potential inaccuracies in our existing terminology.",-1),c=e("h1",{id:"why-are-we-changing-our-release-strategy",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#why-are-we-changing-our-release-strategy","aria-hidden":"true"},"#"),t(" Why are we changing our release strategy?")],-1),m=e("p",null,"As many of you might already have noticed we have decided to make an adjustment in our release terminology and focus. Realistically nothing has actually changed, we have just made what has become our de-facto standard our official standard.",-1),p={href:"https://discord.com/channels/992103415163396136/1073778283407224852/1073778283407224852",target:"_blank",rel:"noopener noreferrer"},f=s("

Essentially we felt that the terminology we were using was at best, unintentionally mildly misleading and at worst, incorrect so with this change we hope to address these issues.

The previous model was as follows:

However it became clear that this wasn't what we were actually doing. What ended up happening was that our "stable" releases never got a proper milestone criteria agreed upon and quickly became out of date compared to our "Cirrus CI" releases.
We also found that because all the fixes going into our master branch were so thoroughly reviewed and tested by both the Pulsar team and wider community, these ended up being more stable than the "stable" releases which quickly meant that, whilst our intention was correct, the reality was either incorrect or misleading.

More than a few people commented on the use of the beta tag meaning it was unfinished or unstable software (even more so with the alpha and dev terms used on the Cirrus builds) so were avoiding Pulsar entirely whilst they waited for a proper, stable, release which often required us to manually explain the concepts - and those are just the vocal people we heard from so we don't even know how many people got turned off from the project entirely based on this which was never our intention.
We also found that the very concept of the Cirrus CI binaries confused some people, particularly due to the name, so this also needed to be addressed.

So after some lengthy discussions and the aforementioned poll, we decided to address these issues by reflecting what is actually going on in practice.

",6),w={href:"https://pulsar-edit.dev/download.html",target:"_blank",rel:"noopener noreferrer"},g=s('

Rolling Release

The "Rolling Release" is the new name for what we previously called "Cirrus CI Binaries" and we have promoted this to the top of our downloads page to make it more obvious as we have a (soft) preference for its use - after all this is the "hyper-hackable text editor" and this seems to fit our mission statement nicely.

This type of release has a number of benefits for everyone:

Of course there are some potential drawbacks:

Regular Releases

This is the new name for the section previously just labelled as "Releases". These have been moved down on the page but remain a valid option for those that want to use a more regular release model.
Currently we are sticking to a release date of the 15th of every month (which initially was just a happy coincidence but we found we liked it and makes for easy organisation) but this not guaranteed and may change as needed.

This also has some benefits for some over the rolling release:

Of course there are also some potential drawbacks:

What can I expect going forwards?

',13),y={href:"https://pulsar-edit.dev/community.html",target:"_blank",rel:"noopener noreferrer"},b=e("p",null,[t("One thing that we haven't addressed yet is the concept of automated updates. These come in a few different flavours - in-app updates and package repository updates. At the moment to update you have to manually download every application binary from the website and re-install (if using a type that requires installing of course) but we hope to somewhat automate this."),e("br"),t(' We hope to publish Pulsar to a number of software repositories which will allow the package managers to actually deal with the updates themselves. We will likely need to create two "channels" for the Rolling and Regular releases to reflect our normal process and make the whole thing unified.')],-1),v=e("p",null,'For the "in-app" updates, we first need to get this functional which is currently on our radar. We have had discussions and ideas from the community as to how we could do this including "on the fly" switching between the Rolling and Regular release channels in the application itself.',-1),_=e("p",null,"Overall we think this is a positive step forward for the community, it may be a little unorthodox compared to what you may be used to from software releases but it has been working very well for us so far so we think it makes sense to finally make it official.",-1),k=e("p",null,"We hope that you enjoyed this update, we want to keep the wider community kept as updated as much as possible outside of our closer communication channels and to be as transparent as possible for our reasoning behind making such changes, especially for changes like this.",-1),x=e("p",null,"As ever, happy coding, see you among the stars!",-1);function q(R,T){const a=h("ExternalLinkIcon");return i(),r("div",null,[u,l(" more "),c,m,e("p",null,[t("This change was recently voted on in a poll on our "),e("a",p,[t("Discord server"),o(a)]),t(' to change our focus from a "point release" model to a "rolling release" one. There were a number of factors involved in this decision which had been discussed numerous times since our first release but this is the first time we had an official consensus on it.')]),f,e("p",null,[t("So what does this mean for you? In terms of the application and releases themselves? Nothing. The biggest change is that the links and headers on our "),e("a",w,[t("downloads page"),o(a)]),t(" have been reorganised and renamed to reflect how we want to go forward with our releases. The sections below will go into detail on each type.")]),g,e("p",null,[t("We don't see this strategy changing in the near future but, as prompted this change in the first place, circumstances change and if we need to change our release model again in the future then we have the flexibility to do so. Nothing is ever set in stone and you are more than welcome to comment on and discuss this change this via any of our "),e("a",y,[t("community areas"),o(a)]),t(".")]),b,v,_,k,x])}const I=n(d,[["render",q],["__file","20230216-Daeraxa-ReleaseStrategyUpdate.html.vue"]]);export{I as default}; diff --git a/assets/20230216-Daeraxa-ReleaseStrategyUpdate.html.97dbb122.js b/assets/20230216-Daeraxa-ReleaseStrategyUpdate.html.97dbb122.js new file mode 100644 index 0000000000..bd5530c8d2 --- /dev/null +++ b/assets/20230216-Daeraxa-ReleaseStrategyUpdate.html.97dbb122.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-5999d3d4","path":"/blog/20230216-Daeraxa-ReleaseStrategyUpdate.html","title":"Changes to our release strategy","lang":"en-US","frontmatter":{"title":"Changes to our release strategy","author":"Daeraxa","date":"2023-02-16T00:00:00.000Z","category":["news"],"tag":["releases","rolling","regular"]},"excerpt":"

We recently decided to change our release strategy to reflect what we are actually doing and address some potential inaccuracies in our existing terminology.

\\n","headers":[{"level":2,"title":"Rolling Release","slug":"rolling-release","link":"#rolling-release","children":[]},{"level":2,"title":"Regular Releases","slug":"regular-releases","link":"#regular-releases","children":[]},{"level":2,"title":"What can I expect going forwards?","slug":"what-can-i-expect-going-forwards","link":"#what-can-i-expect-going-forwards","children":[]}],"git":{"updatedTime":1679861697000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":5.32,"words":1596},"filePathRelative":"blog/20230216-Daeraxa-ReleaseStrategyUpdate.md","localizedDate":"February 16, 2023"}');export{e as data}; diff --git a/assets/20230227-Daeraxa-Survey1.html.cce17cec.js b/assets/20230227-Daeraxa-Survey1.html.cce17cec.js new file mode 100644 index 0000000000..6ebde10d55 --- /dev/null +++ b/assets/20230227-Daeraxa-Survey1.html.cce17cec.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-5b193c62","path":"/blog/20230227-Daeraxa-Survey1.html","title":"Survey: How did you hear about us?","lang":"en-US","frontmatter":{"title":"Survey: How did you hear about us?","author":"Daeraxa","date":"2023-02-27T00:00:00.000Z","category":["survey"],"tag":["community","socials"]},"excerpt":"

A short survey asking how you heard about the Pulsar project as well as feedback\\non our community and social presence.

\\n","headers":[],"git":{"updatedTime":1677465101000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":0.42,"words":125},"filePathRelative":"blog/20230227-Daeraxa-Survey1.md","localizedDate":"February 27, 2023"}');export{e as data}; diff --git a/assets/20230227-Daeraxa-Survey1.html.fb8ea37d.js b/assets/20230227-Daeraxa-Survey1.html.fb8ea37d.js new file mode 100644 index 0000000000..254b902a90 --- /dev/null +++ b/assets/20230227-Daeraxa-Survey1.html.fb8ea37d.js @@ -0,0 +1 @@ +import{_ as t,o as r,c as s,e as a,a as e,b as o,d as c,r as l}from"./app.0e1565ce.js";const i={},u=e("p",null,"A short survey asking how you heard about the Pulsar project as well as feedback on our community and social presence.",-1),d=e("p",null,"We are curious to see where people are finding out about the project and also where people are looking for news, updates and information.",-1),p=e("p",null,"If this goes well we may look at doing further community surveys on other aspects of Pulsar.",-1),_=e("p",null,"There is no need to sign into any account to respond to this survey and no personal data is being collected (emails, names etc.).",-1),h={href:"https://docs.google.com/forms/d/e/1FAIpQLScxbP1gkvjOJ3JkdSiQm5AvBg2tqpnXZO3GxW0J6cDRlV6PJw/viewform?usp=sf_link",target:"_blank",rel:"noopener noreferrer"};function m(f,g){const n=l("ExternalLinkIcon");return r(),s("div",null,[u,a(" more "),d,p,_,e("p",null,[e("a",h,[o("You can find the Survey (Google Forms) here"),c(n)]),o(".")])])}const v=t(i,[["render",m],["__file","20230227-Daeraxa-Survey1.html.vue"]]);export{v as default}; diff --git a/assets/20230301-Daeraxa-MarUpdate.html.0a811ee1.js b/assets/20230301-Daeraxa-MarUpdate.html.0a811ee1.js new file mode 100644 index 0000000000..6c8c2d3094 --- /dev/null +++ b/assets/20230301-Daeraxa-MarUpdate.html.0a811ee1.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-29fd1295","path":"/blog/20230301-Daeraxa-MarUpdate.html","title":"Community Update","lang":"en-US","frontmatter":{"title":"Community Update","author":"Daeraxa","date":"2023-03-01T00:00:00.000Z","category":["news","log"],"tag":["update"]},"excerpt":"

What has the Pulsar team and community been up to lately? Find out here!

\\n","headers":[{"level":2,"title":"Community Spotlight - HTML Tim on YouTube","slug":"community-spotlight-html-tim-on-youtube","link":"#community-spotlight-html-tim-on-youtube","children":[]},{"level":2,"title":"macOS Binary Signing","slug":"macos-binary-signing","link":"#macos-binary-signing","children":[]},{"level":2,"title":"Tree-sitter Modernization","slug":"tree-sitter-modernization","link":"#tree-sitter-modernization","children":[]},{"level":2,"title":"Autocomplete CSS/HTML Automatic Updates","slug":"autocomplete-css-html-automatic-updates","link":"#autocomplete-css-html-automatic-updates","children":[]},{"level":2,"title":"Backend Version Updates","slug":"backend-version-updates","link":"#backend-version-updates","children":[]},{"level":2,"title":"Snippets Package Updates","slug":"snippets-package-updates","link":"#snippets-package-updates","children":[]},{"level":2,"title":"action-pulsar-dependency Stabilization Updates","slug":"action-pulsar-dependency-stabilization-updates","link":"#action-pulsar-dependency-stabilization-updates","children":[]}],"git":{"updatedTime":1677632078000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":5}]},"readingTime":{"minutes":2.7,"words":810},"filePathRelative":"blog/20230301-Daeraxa-MarUpdate.md","localizedDate":"March 1, 2023"}');export{e as data}; diff --git a/assets/20230301-Daeraxa-MarUpdate.html.f5a92e03.js b/assets/20230301-Daeraxa-MarUpdate.html.f5a92e03.js new file mode 100644 index 0000000000..46f97a84a0 --- /dev/null +++ b/assets/20230301-Daeraxa-MarUpdate.html.f5a92e03.js @@ -0,0 +1 @@ +import{_ as n,o as r,c as s,e as i,a as t,b as e,d as a,r as l}from"./app.0e1565ce.js";const h={},c=t("p",null,"What has the Pulsar team and community been up to lately? Find out here!",-1),d=t("h1",{id:"welcome-to-the-march-community-update",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#welcome-to-the-march-community-update","aria-hidden":"true"},"#"),e(" Welcome to the March community update")],-1),u=t("br",null,null,-1),p={href:"https://pulsar-edit.dev/blog/20230215-Daeraxa-v1.102.0.html",target:"_blank",rel:"noopener noreferrer"},m={href:"https://pulsar-edit.dev/blog/20220216-Daeraxa-ReleaseStrategyUpdate.html",target:"_blank",rel:"noopener noreferrer"},g={href:"https://pulsar-edit.dev/blog/20230227-Daeraxa-Survey1.html",target:"_blank",rel:"noopener noreferrer"},f=t("p",null,"With that said - onto the updates!",-1),b=t("h2",{id:"community-spotlight-html-tim-on-youtube",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#community-spotlight-html-tim-on-youtube","aria-hidden":"true"},"#"),e(" Community Spotlight - HTML Tim on YouTube")],-1),_={href:"https://www.youtube.com/@htmltim",target:"_blank",rel:"noopener noreferrer"},w=t("br",null,null,-1),y=t("br",null,null,-1),k={href:"https://www.youtube.com/@htmltim",target:"_blank",rel:"noopener noreferrer"},v={href:"http://htmltim.com/",target:"_blank",rel:"noopener noreferrer"},x=t("h2",{id:"macos-binary-signing",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#macos-binary-signing","aria-hidden":"true"},"#"),e(" macOS Binary Signing")],-1),S={href:"https://github.com/Meadowsys",target:"_blank",rel:"noopener noreferrer"},T=t("h2",{id:"tree-sitter-modernization",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#tree-sitter-modernization","aria-hidden":"true"},"#"),e(" Tree-sitter Modernization")],-1),P={href:"https://github.com/mauricioszabo",target:"_blank",rel:"noopener noreferrer"},M={href:"https://tree-sitter.github.io/tree-sitter/",target:"_blank",rel:"noopener noreferrer"},z=t("br",null,null,-1),H={href:"https://pulsar-edit.dev/blog/20230209-mauricioszabo-tree-sitter-part-1.html",target:"_blank",rel:"noopener noreferrer"},W=t("p",null,"With the new implementation of tree-sitter, we're gaining a better understanding of Atom's tokenizer. With this, we can simplify some of the work that will open the door to more interesting experiments, like packages that contribute to syntax elements, such as semantic highlighting.",-1),C=t("h2",{id:"autocomplete-css-html-automatic-updates",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#autocomplete-css-html-automatic-updates","aria-hidden":"true"},"#"),e(" Autocomplete CSS/HTML Automatic Updates")],-1),D=t("br",null,null,-1),I={href:"https://github.com/confused-Techie",target:"_blank",rel:"noopener noreferrer"},L=t("br",null,null,-1),U=t("h2",{id:"backend-version-updates",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#backend-version-updates","aria-hidden":"true"},"#"),e(" Backend Version Updates")],-1),A={href:"https://github.com/confused-Techie",target:"_blank",rel:"noopener noreferrer"},V={href:"https://github.com/Digitalone1",target:"_blank",rel:"noopener noreferrer"},B=t("br",null,null,-1),O=t("h2",{id:"snippets-package-updates",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#snippets-package-updates","aria-hidden":"true"},"#"),e(" Snippets Package Updates")],-1),j={href:"https://github.com/savetheclocktower",target:"_blank",rel:"noopener noreferrer"},N=t("br",null,null,-1),E=t("br",null,null,-1),F=t("h2",{id:"action-pulsar-dependency-stabilization-updates",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#action-pulsar-dependency-stabilization-updates","aria-hidden":"true"},"#"),e(),t("code",null,"action-pulsar-dependency"),e(" Stabilization Updates")],-1),G={href:"https://github.com/pulsar-edit/action-pulsar-dependency",target:"_blank",rel:"noopener noreferrer"},Y={href:"https://github.com/spiker985",target:"_blank",rel:"noopener noreferrer"};function R(q,J){const o=l("ExternalLinkIcon");return r(),s("div",null,[c,i(" more "),d,t("p",null,[e("Hi everyone, welcome to the March edition of our regular update posts about what is going on with the project, small wins and other details you may have missed."),u,e(" Obviously in the last month we have made some more obviously big updates such as the "),t("a",p,[e("v1.102.0 release"),a(o)]),e(" and the changes to our "),t("a",m,[e("release strategy"),a(o)]),e(" but this post is about the things that you might otherwise not have seen but still deserve to be known about.")]),t("p",null,[e("We also launched our first "),t("a",g,[e("community survey"),a(o)]),e(" which, as of time of writing, is still open. If this goes well (and depending on feedback) then we will be creating more in the future such as asking about new features, platform/use case surveys and general application feedback.")]),f,b,t("p",null,[e("First of all I want to let everyone know about "),t("a",_,[e("@htmltim"),a(o)]),e(" on YouTube. I came across his channel the other day and found a whole host of videos being made on Pulsar."),w,e(" He has covered a bunch of topics in his videos such as general overview of Pulsar, migrating from other editors, a look at a bunch of useful community packages and advice on HTML writing & editing."),y,e(" Definitely give his "),t("a",k,[e("videos"),a(o)]),e(" or "),t("a",v,[e("website"),a(o)]),e(" a look if you want to know how to get the most out of Pulsar!")]),x,t("p",null,[e("We finally have signed binaries on macOS thanks to "),t("a",S,[e("@meadowsys"),a(o)]),e(". This was mentioned as being underway last month but this has now been achieved so no misleading messages on macOS when trying to install Pulsar. From the v1.102.0 release onwards (and including all rolling releases) you will no longer have to execute a terminal command to get this to run.")]),T,t("p",null,[t("a",P,[e("@maur\xEDcio szabo"),a(o)]),e(" is still pushing ahead to get a modern implementation of "),t("a",M,[e("tree-sitter"),a(o)]),e(" working on Pulsar."),z,e(" See "),t("a",H,[e("Maur\xEDcio's blog post"),a(o)]),e(" for more info on the topic but recent wins include getting code folding working which is no small achievement.")]),W,C,t("p",null,[e("It was found that two of our core packages for HTML and CSS autocompletion had out of date or missing references. The project that was supplying the data for these completions was no longer being maintained so a new source of data had to be found."),D,e(" However it seems a like-for-like replacement from a reliable source is hard to find so "),t("a",I,[e("@confused-techie"),a(o)]),e(" has been working on this to produce a reliable replacement to get this functionality working properly again so completions remain up to date with current standards."),L,e(" These changes will hopefully be implemented shortly and included in an forthcoming update.")]),U,t("p",null,[e("A refactoring on the Pulsar package backed for a new versioning system has been underway by "),t("a",A,[e("@confused-techie"),a(o)]),e(" and "),t("a",V,[e("@digitalone1"),a(o)]),e(". The update is intended to make the system more permissive for package authors, provide easier version management and allow for the concept of release channels."),B,e(' There is also a change to add a new "VCS service system" which will allow for publishing of channels from other systems than just GitHub so authors can instead publish from the platform of their choice.')]),O,t("p",null,[t("a",j,[e("@savetheclocktower"),a(o)]),e(" has recently been heavily contributing to the project. As somebody originally involved (and experienced) with the atom/snippets package he has been adding new functionality and fixing existing issues."),N,e(" Particularly exciting updates here include the ability to map a snippet to a command as well as introducing the concept of snippet variables to Pulsar."),E,e(" Look forward to these updates in an upcoming version of Pulsar.")]),F,t("p",null,[t("a",G,[e("action-pulsar-dependency"),a(o)]),e(" is a GitHub action used for testing Pulsar packages. "),t("a",Y,[e("@spiker985"),a(o)]),e(" has been making some updates here to ensure Windows path registration and macOS symlinking is working correctly. Any GitHub CI is now able to autonomously install the most recent Pulsar rolling release for package testing.")])])}const Q=n(h,[["render",R],["__file","20230301-Daeraxa-MarUpdate.html.vue"]]);export{Q as default}; diff --git a/assets/20230315-Daeraxa-v1.103.0.html.9a66cb6e.js b/assets/20230315-Daeraxa-v1.103.0.html.9a66cb6e.js new file mode 100644 index 0000000000..ac01c21779 --- /dev/null +++ b/assets/20230315-Daeraxa-v1.103.0.html.9a66cb6e.js @@ -0,0 +1 @@ +const e=JSON.parse(`{"key":"v-6fcbebb6","path":"/blog/20230315-Daeraxa-v1.103.0.html","title":"It's that time again, Pulsar 1.103.0 is available now!","lang":"en-US","frontmatter":{"title":"It's that time again, Pulsar 1.103.0 is available now!","author":"Daeraxa","date":"2023-03-15T00:00:00.000Z","category":["dev"],"tag":["release"]},"excerpt":"

Check out our newest Regular Release for Pulsar! Available Now!

\\n","headers":[{"level":3,"title":"Pulsar","slug":"pulsar","link":"#pulsar","children":[]},{"level":3,"title":"Snippets","slug":"snippets","link":"#snippets","children":[]},{"level":3,"title":"Github","slug":"github","link":"#github","children":[]},{"level":3,"title":"PPM","slug":"ppm","link":"#ppm","children":[]}],"git":{"updatedTime":1679076746000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":2.25,"words":676},"filePathRelative":"blog/20230315-Daeraxa-v1.103.0.md","localizedDate":"March 15, 2023"}`);export{e as data}; diff --git a/assets/20230315-Daeraxa-v1.103.0.html.aceca816.js b/assets/20230315-Daeraxa-v1.103.0.html.aceca816.js new file mode 100644 index 0000000000..085db0a64f --- /dev/null +++ b/assets/20230315-Daeraxa-v1.103.0.html.aceca816.js @@ -0,0 +1 @@ +import{_ as l,o as n,c as a,a as e,b as t,d as o,e as s,f as i,r as d}from"./app.0e1565ce.js";const p={},u={href:"https://github.com/pulsar-edit/pulsar/releases/tag/v1.103.0",target:"_blank",rel:"noopener noreferrer"},h=e("h1",{id:"what-is-new-in-1-103-0",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#what-is-new-in-1-103-0","aria-hidden":"true"},"#"),t(" What is new in 1.103.0?")],-1),c=e("p",null,"With this release, we have a number of quality-of-life updates to make things just that little bit easier.",-1),_={href:"https://github.com/orgs/pulsar-edit/discussions/150",target:"_blank",rel:"noopener noreferrer"},f=i('

We have some updates to our snippets, autocomplete-css, and github packages, which you can read about in the change log, that bring about a whole host of improvements from updated UI to new functionality to really improve your coding experience!

Of course, no update would be complete without a massive thank you to our wonderful community, especially our contributors and donors who are making all this possible.

And as ever, happy coding. See you among the stars!


Pulsar

',7),g={href:"https://github.com/pulsar-edit/pulsar/pull/416",target:"_blank",rel:"noopener noreferrer"},m={href:"https://github.com/pulsar-edit/pulsar/pull/421",target:"_blank",rel:"noopener noreferrer"},b=e("code",null,"autocomplete-css",-1),k=e("code",null,"completions.json",-1),w={href:"https://github.com/pulsar-edit/pulsar/pull/398",target:"_blank",rel:"noopener noreferrer"},v={href:"https://github.com/pulsar-edit/pulsar/pull/403",target:"_blank",rel:"noopener noreferrer"},y={href:"https://github.com/pulsar-edit/pulsar/pull/418",target:"_blank",rel:"noopener noreferrer"},A={href:"https://github.com/pulsar-edit/pulsar/pull/415",target:"_blank",rel:"noopener noreferrer"},x={href:"https://github.com/pulsar-edit/pulsar/pull/412",target:"_blank",rel:"noopener noreferrer"},D=e("code",null,"snippets",-1),S={href:"https://github.com/pulsar-edit/pulsar/pull/408",target:"_blank",rel:"noopener noreferrer"},T={href:"https://github.com/pulsar-edit/pulsar/pull/404",target:"_blank",rel:"noopener noreferrer"},B={href:"https://github.com/pulsar-edit/pulsar/pull/396",target:"_blank",rel:"noopener noreferrer"},P=e("code",null,"markdown-preview",-1),G=e("code",null,"styleguide",-1),N=e("code",null,"wrap-guide",-1),U={href:"https://github.com/pulsar-edit/pulsar/pull/374",target:"_blank",rel:"noopener noreferrer"},F={href:"https://github.com/pulsar-edit/pulsar/pull/400",target:"_blank",rel:"noopener noreferrer"},I={href:"https://github.com/pulsar-edit/pulsar/pull/399",target:"_blank",rel:"noopener noreferrer"},V={href:"https://github.com/pulsar-edit/pulsar/pull/380",target:"_blank",rel:"noopener noreferrer"},R=e("h3",{id:"snippets",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#snippets","aria-hidden":"true"},"#"),t(" Snippets")],-1),j=e("code",null,"command",-1),C={href:"https://github.com/pulsar-edit/snippets/pull/10",target:"_blank",rel:"noopener noreferrer"},W=e("code",null,"g",-1),E={href:"https://github.com/pulsar-edit/snippets/pull/7",target:"_blank",rel:"noopener noreferrer"},O={href:"https://github.com/pulsar-edit/snippets/pull/6",target:"_blank",rel:"noopener noreferrer"},L={href:"https://github.com/pulsar-edit/snippets/pull/5",target:"_blank",rel:"noopener noreferrer"},M=e("h3",{id:"github",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#github","aria-hidden":"true"},"#"),t(" Github")],-1),q={href:"https://github.com/pulsar-edit/github/pull/17",target:"_blank",rel:"noopener noreferrer"},z={href:"https://github.com/pulsar-edit/github/pull/15",target:"_blank",rel:"noopener noreferrer"},H=e("h3",{id:"ppm",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#ppm","aria-hidden":"true"},"#"),t(" PPM")],-1),J={href:"https://github.com/pulsar-edit/ppm/pull/49",target:"_blank",rel:"noopener noreferrer"},K={href:"https://github.com/pulsar-edit/ppm/pull/53",target:"_blank",rel:"noopener noreferrer"},Q={href:"https://github.com/pulsar-edit/ppm/pull/58",target:"_blank",rel:"noopener noreferrer"},X={href:"https://github.com/pulsar-edit/ppm/pull/59",target:"_blank",rel:"noopener noreferrer"},Y={href:"https://github.com/pulsar-edit/ppm/pull/60",target:"_blank",rel:"noopener noreferrer"};function Z($,ee){const r=d("ExternalLinkIcon");return n(),a("div",null,[e("p",null,[t("Check out our newest Regular Release for Pulsar! "),e("a",u,[t("Available Now!"),o(r)])]),s(" more "),h,c,e("p",null,[t("One big change is a new settings search page! No longer will you have to trawl through the settings and packages to find that one pesky bit of config, now everything is just a few keystrokes away! This feature is still experimental and will have more updates and tweaks coming, but please feel free to "),e("a",_,[t("provide feedback"),o(r)]),t(" so we can continue to improve it.")]),f,e("ul",null,[e("li",null,[t("Added: feature: Implement Search Settings Ability "),e("a",g,[t("@confused-Techie"),o(r)])]),e("li",null,[t("Added: Show Settings Icon in Status Bar "),e("a",m,[t("@confused-Techie"),o(r)])]),e("li",null,[t("Added: Add Automated updated of "),b,t(),k,t(),e("a",w,[t("@confused-Techie"),o(r)])]),e("li",null,[t("Bumped: ppm: Update submodule to 9af239277180f2a9ee9e86714 "),e("a",v,[t("@Spiker985"),o(r)])]),e("li",null,[t("Bumped: ppm: Update submodule to 915cbf6e5f9ea1141ef5dcaf8 "),e("a",y,[t("@DeeDeeG"),o(r)])]),e("li",null,[t("Bumped: deps: Bump github to v0.36.15-pretranspiled "),e("a",A,[t("@DeeDeeG"),o(r)])]),e("li",null,[t("Added: actually cache based on sha "),e("a",x,[t("@Meadowsys"),o(r)])]),e("li",null,[t("Bumped: Bump "),D,t(" to bb00f9 "),e("a",S,[t("@savetheclocktower"),o(r)])]),e("li",null,[t("Added: [skip-ci] Small Readme Touchup "),e("a",T,[t("@confused-Techie"),o(r)])]),e("li",null,[t("Added: json language - add .har extension "),e("a",B,[t("@wesinator"),o(r)])]),e("li",null,[t("Added: Bundle "),P,t(", "),G,t(", "),N,t(),e("a",U,[t("@confused-Techie"),o(r)])]),e("li",null,[t("Added: Add GitHub Token to Doc CI "),e("a",F,[t("@Spiker985"),o(r)])]),e("li",null,[t("Added: Add Setup Node to Package Tests "),e("a",I,[t("@confused-Techie"),o(r)])]),e("li",null,[t("Added: feat: add dev.pulsar_edit.Pulsar.metainfo.xml "),e("a",V,[t("@cat-master21"),o(r)])])]),R,e("ul",null,[e("li",null,[t("Added: Add "),j,t(" property that registers a command name for a snippet "),e("a",C,[t("@savetheclocktower"),o(r)])]),e("li",null,[t("Removed: Remove implicit "),W,t(" flag from snippet transformations "),e("a",E,[t("@savetheclocktower"),o(r)])]),e("li",null,[t("Fixed: Fix failing specs "),e("a",O,[t("@mauricioszabo"),o(r)])]),e("li",null,[t("Added: cleanup and rename "),e("a",L,[t("@Sertonix"),o(r)])])]),M,e("ul",null,[e("li",null,[t("Added: rebrand git-tab-view "),e("a",q,[t("@icecream17"),o(r)])]),e("li",null,[t("Added: lib: Update login instructions for PATs, not OAuth "),e("a",z,[t("@DeeDeeG"),o(r)])])]),H,e("ul",null,[e("li",null,[t("Fixed: src: Update default Pulsar install paths "),e("a",J,[t("@DeeDeeG"),o(r)])]),e("li",null,[t("Bumped: deps: Upgrade npm to 6.14.18 "),e("a",K,[t("@DeeDeeG"),o(r)])]),e("li",null,[t("Fixed: Fix installing with yarn on Windows "),e("a",Q,[t("@DeeDeeG"),o(r)])]),e("li",null,[t("Fixed: Fix inability to notice newer versions of git-installed packages "),e("a",X,[t("@savetheclocktower"),o(r)])]),e("li",null,[t("Added: meta: Actually sync yarn.lock "),e("a",Y,[t("@DeeDeeG"),o(r)])])])])}const re=l(p,[["render",Z],["__file","20230315-Daeraxa-v1.103.0.html.vue"]]);export{re as default}; diff --git a/assets/20230319-confused-Techie-HowLicenseNoneDeletedPackages.html.64b9bafc.js b/assets/20230319-confused-Techie-HowLicenseNoneDeletedPackages.html.64b9bafc.js new file mode 100644 index 0000000000..5791d1eaf7 --- /dev/null +++ b/assets/20230319-confused-Techie-HowLicenseNoneDeletedPackages.html.64b9bafc.js @@ -0,0 +1 @@ +const e=JSON.parse(`{"key":"v-7f7a1a1a","path":"/blog/20230319-confused-Techie-HowLicenseNoneDeletedPackages.html","title":"How 'license: none' Deleted Packages","lang":"en-US","frontmatter":{"title":"How 'license: none' Deleted Packages","author":"confused-Techie","date":"2023-03-19T00:00:00.000Z","category":["dev"],"tag":["backend"]},"excerpt":"

How setting license: 'none' removed almost 100 Packages from the Pulsar Package Registry.

\\n","headers":[{"level":2,"title":"Licenses","slug":"licenses","link":"#licenses","children":[]},{"level":2,"title":"Packages on Pulsar","slug":"packages-on-pulsar","link":"#packages-on-pulsar","children":[]},{"level":2,"title":"Why This Matters","slug":"why-this-matters","link":"#why-this-matters","children":[]},{"level":2,"title":"How did we Handle This","slug":"how-did-we-handle-this","link":"#how-did-we-handle-this","children":[]},{"level":2,"title":"The Actual Hard Part","slug":"the-actual-hard-part","link":"#the-actual-hard-part","children":[]},{"level":2,"title":"Respecting A License","slug":"respecting-a-license","link":"#respecting-a-license","children":[]}],"git":{"updatedTime":1679354480000,"contributors":[{"name":"confused-Techie","email":"dev@lhbasics.com","commits":2},{"name":"confused_techie","email":"dev@lhbasics.com","commits":1}]},"readingTime":{"minutes":6.96,"words":2089},"filePathRelative":"blog/20230319-confused-Techie-HowLicenseNoneDeletedPackages.md","localizedDate":"March 19, 2023"}`);export{e as data}; diff --git a/assets/20230319-confused-Techie-HowLicenseNoneDeletedPackages.html.ea402cf4.js b/assets/20230319-confused-Techie-HowLicenseNoneDeletedPackages.html.ea402cf4.js new file mode 100644 index 0000000000..59de69f33d --- /dev/null +++ b/assets/20230319-confused-Techie-HowLicenseNoneDeletedPackages.html.ea402cf4.js @@ -0,0 +1,16 @@ +import{_ as s,o as i,c as l,e as r,a as e,b as t,d as a,f as n,r as h}from"./app.0e1565ce.js";const c="/assets/sql-filter-abomination.88955aa2.png",d={},u=e("p",null,[t("How setting "),e("code",null,"license: 'none'"),t(" removed almost 100 Packages from the Pulsar Package Registry.")],-1),p=e("h2",{id:"licenses",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#licenses","aria-hidden":"true"},"#"),t(" Licenses")],-1),g=e("p",null,"Something you're sure to have heard about if you've been around open source software for any length of time is licenses.",-1),m=e("p",null,"Licensing your software is a fantastic way to tell the world simply and easily how they can use your code. A license can specify that your code is free and available to everyone, that people can charge for your code, or even that nobody is allowed to touch it except you.",-1),f={href:"https://choosealicense.com/licenses/",target:"_blank",rel:"noopener noreferrer"},y={href:"https://choosealicense.com/licenses/mit/",target:"_blank",rel:"noopener noreferrer"},w=e("ul",null,[e("li",null,"Allows Commercial use, Distribution, Modification, or even Private use.")],-1),k={href:"https://choosealicense.com/licenses/gpl-3.0/",target:"_blank",rel:"noopener noreferrer"},b=e("ul",null,[e("li",null,"Allows Commercial use, Distribution, Modification, Patent use, and Private use.")],-1),v={href:"https://choosealicense.com/licenses/apache-2.0/",target:"_blank",rel:"noopener noreferrer"},_=e("ul",null,[e("li",null,"Allows Commercial use, Distribution, Modification, Patent use, and Private use.")],-1),P=e("p",null,"A license is an agreed upon binding contract between the code author and anyone that finds themselves using it. That is if it's used properly.",-1),A=e("h2",{id:"packages-on-pulsar",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#packages-on-pulsar","aria-hidden":"true"},"#"),t(" Packages on Pulsar")],-1),I=e("p",null,"Now often times when a developer first begins working in the open source world licenses might be confusing, or seen as a hassle they don't want to deal with.",-1),x=e("p",null,"Maybe they don't feel like researching which one is the right one to choose, or don't want to be locked into the wrong choice.",-1),L=e("code",null,"MIT",-1),T={href:"https://github.com/pulsar-edit/pulsar-edit.github.io/blob/main/package.json",target:"_blank",rel:"noopener noreferrer"},N=e("code",null,"package.json",-1),B=e("p",null,"But while developers may not want to take the time to understand a license, the tools they use do their best to help them.",-1),E=e("code",null,"npm init",-1),S=e("code",null,"package.json",-1),j={href:"https://opensource.org/license/isc-license-txt/",target:"_blank",rel:"noopener noreferrer"},q=e("code",null,"ISC",-1),C={href:"https://github.com/pulsar-edit/pulsar/tree/master/packages/package-generator",target:"_blank",rel:"noopener noreferrer"},M=e("code",null,"package-generator",-1),W=e("code",null,"MIT",-1),D=e("p",null,"But even then, of course, developers are able to and should modify their package's licenses to however they see fit.",-1),H=e("h2",{id:"why-this-matters",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#why-this-matters","aria-hidden":"true"},"#"),t(" Why This Matters")],-1),G={href:"https://pulsar-edit.dev/blog/20221127-confused-Techie-SunsetMisadventureBackend.html",target:"_blank",rel:"noopener noreferrer"},F=n('

When this was originally done though, we had no concerns over the license of a package, at least at first.

But it wasn't long after talking to someone much more familiar with digital law, and Intellectual Property laws than anyone else on the team, we realized that re-hosting this content (even though it was just the metadata of the packages themselves, as the Pulsar Package Registry doesn't actually host the code of a package) counts as distribution, and redistribution. Which, the permission to do so is entirely governed by the package license, and the will of the author.

Now when a developer had published something to the Atom package registry it doesn't matter the license they choose. As by act of them publishing the package there, they are giving permission to Atom for distributing that content. And the same is true when a package is purposefully published to Pulsar. But because the developers of the packages we had at first had never had implicitly given us their permission, all we had was the license of their package to go off of.

How did we Handle This

The night after learning of this potential break of authors licenses, me and many others on the Pulsar team began the painstaking process of verifying the license of every single package on the Pulsar package registry. Where we had to manually check the license field of 12,470 individual package's package.json.

Since luckily at this point everything was in a database I was able to filter packages by the licenses they had. But even then on the initial evaluation there was 149 unique values across every single package. Where many of these simply listed License in LICENSE.md which meant we couldn't only rely on the database, we would have to check the repository where this code lived to properly review the license itself.

',6),O={href:"https://spdx.org/licenses/",target:"_blank",rel:"noopener noreferrer"},R=e("code",null,"MIT",-1),V=e("code",null,"mit",-1),z=e("p",null,[t("Doing this I was able to reduce or unique license variations to "),e("code",null,"116"),t(" licenses. But then came the time for the more obscure ones listed. For example we had a lot that simply said "),e("code",null,"GNU AGPL"),t(", "),e("code",null,"EPL"),t(", "),e("code",null,"GNU LGPGL"),t(", or "),e("code",null,"Apache"),t(". But those licenses all had multiple versions, and with no version specified we had to read through the terms of every single version to determine if any of them prohibited re-distribution, since if any single version did, then we had to remove the package.")],-1),Y={href:"https://tldrlegal.com/",target:"_blank",rel:"noopener noreferrer"},U={href:"https://opensource.apple.com/apsl/",target:"_blank",rel:"noopener noreferrer"},J=e("blockquote",null,[e("p",null,"Externally Deploy verbatim, unmodified copies of the Original Code, for commercial or non-commercial purposes")],-1),Q={href:"https://tldrlegal.com/license/do-what-the-f*ck-you-want-to-public-license-(wtfpl)",target:"_blank",rel:"noopener noreferrer"},X=n('

Then finally, after 3 non-stop hours of reading the terms of more licenses than I ever knew existed, I had reached the first finish line, leaving me with 30 unique variations of licenses across many packages that were either a completely non-existent license, an invalid license, or simply a variation of "License in LICENSE.md"

Below you can see the abomination of a filter I wrote as I slowly went through all of the possible licenses.

SQL Filter Abomination

And yes I'm aware that I could define an array and filter by that, but in my defense it was 1:39 AM after a few days of having my family over.

At this point there was very few unique license left. With our only entries being:

Starting at 11:06 PM that night, when it hit 3:17 AM I was dead tired and left with the following results:

',8),K=e("code",null,"[object Object]",-1),Z={href:"https://docs.npmjs.com/cli/v9/configuring-npm/package-json#license",target:"_blank",rel:"noopener noreferrer"},$=n(`

The Actual Hard Part

From here, we had our list of packages to manually verify by checking the license in their GitHub repo. Which luckily I had a huge amount of help from @Daeraxa and @Sertonix in doing this.

While this was a long and arduous process, we got through it quickly, and in the end were left with 101 packages were we would have to contact the package owner to find the next best steps, since they had no valid package for us to check.

It was at this point we littered GitHub far and wide with the following issues to every repo we had to:

Hello, this is confused-Techie from the Pulsar Team.
+
+Pulsar is an actively developed fork of Atom and one of our major efforts is to preserve the ecosystem of Community Packages that were previously published on Atom.io
+
+While we want to list every single package that existed on Atom's Package Repository, we wanted to respect the license of each package within and ensure to only list packages that either the author has agreed for us to or packages that have a license that specify that redistribution is okay.
+
+That is exactly why we are contacting you today, it seems your package <package-name> has a license that makes it hard for us to determine if we should be listing it for redistribution.
+
+All that needs to happen for you as a package maintainer is for you to give authorization for us to list your package. Just reply to this issue either way and we will respect your wishes.
+
+Otherwise within 3 weeks, if there is no response, we will assume the answer is no and remove your package from the Pulsar Package Registry.
+
+And if you've missed the deadline we have and still want your package to be published, feel free to republish to Pulsar.
+
+Thanks a ton for your time, hope you have a great day!
+

At first it was exciting to see how many package maintainers responded quickly, receiving excited and encouraging responses like:

Hello! Thanks for visiting my package, I am fine with Pulsar listing my package for redistribution.

You can publish native-ui with Pulsar. Happy if somebody is still using it!

You can publish it if you want, but to my knowledge no one uses it any more.

Hi, I'm already aware of the Pulsar project and I follow your progress... if want to redistribute to Pulsar Package REgistry now - do not hesitate. Best regards, SB

This was amazing to see, and gave us hope that we wouldn't have to remove many packages, since as you know, my biggest goal has been to ensure nothing is lost.

But, alas, when our three week self-determined timeline came to an end many issues were left unanswered. Which makes sense, when you consider some packages had not seen any updates in 8 years or more. But was still sad to see, to say the least.

But now came the time to abide by the deadline and properly respect everyone's licenses.

Respecting A License

After quickly drafting up a script to preform a mass deletion of packages, I simply ran npm run tool:delete array and watched as many of the packages we worked to archive were lost.

`,15),ee={href:"https://github.com/pulsar-edit/package-backend/blob/main/docs/reference/Admin_Actions.md",target:"_blank",rel:"noopener noreferrer"},te=e("p",null,[t("While some of the licenses that belonged to removed packages were unique, such as the authors name, the school they attended, their email, or referring to a license file that didn't exist. The majority simply said "),e("code",null,"none"),t(" or literally didn't exist, or said "),e("code",null,'"license": ""'),t(".")],-1),oe=e("p",null,`To answer one package's license of "I need one?" all I can say is that if 73 packages saying "none" had decided they did, they would still be able to provide the utility they had originally set out to.`,-1),ae=e("p",null,"With all of this said though, I hope to see a populous future of packages on Pulsar, and can only hope some of these removed packages get republished or reimplemented. And to all of the package maintainers that had responded to our issues, and to the wonderful team of developers with Pulsar that helped me with this project, I can't appreciate you all enough.",-1),ne=e("p",null,"But as always, thanks for reading, and thanks for contributing.",-1),se=e("p",null,"confused-Techie",-1);function ie(le,re){const o=h("ExternalLinkIcon");return i(),l("div",null,[u,r(" more "),p,g,m,e("p",null,[t("A quick refresher for anybody less familiar, some of the most popular open source licenses (as taken from "),e("a",f,[t("ChooseALicense.com"),a(o)]),t("):")]),e("ul",null,[e("li",null,[e("p",null,[e("a",y,[t("MIT"),a(o)])]),w]),e("li",null,[e("p",null,[e("a",k,[t("GNU GPLv3"),a(o)])]),b]),e("li",null,[e("p",null,[e("a",v,[t("Apache License 2.0"),a(o)])]),_])]),P,A,I,x,e("p",null,[t("Now for us at Pulsar we often times prefer to go with the "),L,t(" license, as it's one of the more permissive choices, and was what all the core developers could agree on. You can even see that listed in the "),e("a",T,[N,a(o)]),t(" of the code that hosts this blog.")]),B,e("p",null,[t("With NPM when you run "),E,t(" (to automatically make a "),S,t(" on your system) will automatically choose the "),e("a",j,[q,a(o)]),t(" as the default license.")]),e("p",null,[t("Or even Pulsar's own "),e("a",C,[M,a(o)]),t(" package defaulting to the "),W,t(" license when setting up a package for developers.")]),D,H,e("p",null,[t("Now, for anyone that's read my "),e("a",G,[t("previous blog post"),a(o)]),t(" about initially creating the Pulsar package registry, you'll know that we had taken every single package from the original Atom package registry, and hosted it for Pulsar. Because like I say there, the package ecosystem is one of my major driving factors of using and loving Pulsar/Atom.")]),F,e("p",null,[t("From there we started filtering all of the licenses as we read through them, by properly written "),e("a",O,[t("SPDX License IDs"),a(o)]),t(". Being able to outright exclude results that contained "),R,t(" or "),V,t(" and variations for all the other licenses listed in the above link for anything that allowed redistribution.")]),z,e("p",null,[t("And let me tell you, as someone unfamiliar with legalese, reading through the terms of these obscure licenses that had no easy breakdown on "),e("a",Y,[t("tl;drLegal"),a(o)]),t(" it wasn't easy reading material. For example, it meant decoding lines like this from the "),e("a",U,[t("'Apple Public Source License 2.0'"),a(o)]),t(":")]),J,e("p",null,[t("Although I think the most fun discovery made through trawling so many different possible licenses is discovering gems like "),e("a",Q,[t("'WTFPL (Do What The F*ck You Want To Public License)'"),a(o)]),t(".")]),X,e("p",null,[t("Funnily enough after this most other Core developers concerns were on our "),K,t(" license. As @mauricioszabo jokingly called the 'Materialistic License'. But was the time I discovered the now depreciated "),e("a",Z,[t("Object License"),a(o)]),t(" and learned we needed a way to properly decode those as well on the backend.")]),$,e("p",null,[t("In the end we had to remove 82 individual packages from the Pulsar package registry. All of which have of course have been documented in the "),e("a",ee,[t("Backend Admin Actions Log"),a(o)]),t(".")]),te,oe,ae,ne,se])}const ce=s(d,[["render",ie],["__file","20230319-confused-Techie-HowLicenseNoneDeletedPackages.html.vue"]]);export{ce as default}; diff --git a/assets/20230326-Daeraxa-Survey1-Results.html.c3f15d90.js b/assets/20230326-Daeraxa-Survey1-Results.html.c3f15d90.js new file mode 100644 index 0000000000..14cfc51612 --- /dev/null +++ b/assets/20230326-Daeraxa-Survey1-Results.html.c3f15d90.js @@ -0,0 +1 @@ +import{_ as s,o as n,c as i,e as r,a as e,b as o,d as a,r as h}from"./app.0e1565ce.js";const l="/assets/survey1-infographic1.6eaa37f0.png",u="/assets/survey1-infographic2.9389690a.png";const d={},c=e("p",null,"Here we have the results and analysis of our first community survey (and an infographic for those who like that kind of thing).",-1),p=e("p",null,"A big thank you to everyone who participated in our recent social media survey. This really helped answer some questions that we had in order to be able to make sure we were reaching as much of the community as possible. We were honestly surprised at some of the results and we will definitely be taking lots of it on board.",-1),m={href:"https://github.com/Daeraxa",target:"_blank",rel:"noopener noreferrer"},f=e("div",{class:"image-preview"},[e("img",{src:l}),e("img",{src:u})],-1),w=e("p",null,"And now onto the analysis.",-1),_=e("h2",{id:"how-did-you-hear-about-us",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#how-did-you-hear-about-us","aria-hidden":"true"},"#"),o(" How did you hear about us?")],-1),b=e("p",null,'The first question on the survey was "How did you hear about us"? The idea with this one was to see where people had heard about Pulsar from to see if this aligned with some of our earlier efforts to publicise the project.',-1),g=e("p",null,"By far the biggest result here was simply people searching the web for an Atom replacement, I don't think there were any shockers here as this is how many of us got involved in the first place.",-1),y={href:"https://www.reddit.com/r/atom",target:"_blank",rel:"noopener noreferrer"},k={href:"https://www.youtube.com/@DistroTube",target:"_blank",rel:"noopener noreferrer"},v={href:"https://pulsar-edit.dev/blog/20221208-Daeraxa-DistroTubeVideo.html",target:"_blank",rel:"noopener noreferrer"},x=e("p",null,"We had a couple of other interesting results here such as word of mouth from colleagues, various web articles, alternative software sites and even the Atom Wikipedia page.",-1),D=e("h2",{id:"which-community-area-do-you-check-for-updates",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#which-community-area-do-you-check-for-updates","aria-hidden":"true"},"#"),o(" Which community area do you check for updates?")],-1),T=e("p",null,"This was a very interesting question and one we thought especially important to make sure we were placing the right amount of focus on our various community areas. The last thing we want is for anyone to be left out of important news and updates because of choosing one community area over another.",-1),W=e("p",null,"What took us a little by surprise was just how many people do actually check the website - 66% of people check the website in one form or another, either by itself or along with other media channels. It is clear that we need to make sure that we put a lot of effort into this to make sure this is as good as it can be.",-1),A={href:"https://discord.gg/7aEbB9dGRT",target:"_blank",rel:"noopener noreferrer"},H={href:"https://www.reddit.com/r/pulsaredit/",target:"_blank",rel:"noopener noreferrer"},I={href:"https://github.com/orgs/pulsar-edit/discussions",target:"_blank",rel:"noopener noreferrer"},S={href:"https://fosstodon.org/@pulsaredit",target:"_blank",rel:"noopener noreferrer"},j=e("h2",{id:"other-comments-on-social-media-presence",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#other-comments-on-social-media-presence","aria-hidden":"true"},"#"),o(" Other comments on social media presence")],-1),q=e("p",null,"This was deliberately a very freeform question and was really designed as a way for people to say what they wanted on the topic of our social media. We had a bunch of really lovely messages from people thanking us and giving us encouragement so thank you so much for those comments.",-1),B={href:"https://fosstodon.org/@pulsaredit",target:"_blank",rel:"noopener noreferrer"},R={href:"https://github.com/orgs/pulsar-edit/discussions",target:"_blank",rel:"noopener noreferrer"},V={href:"https://www.reddit.com/r/pulsaredit/",target:"_blank",rel:"noopener noreferrer"},E=e("h2",{id:"summary",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#summary","aria-hidden":"true"},"#"),o(" Summary")],-1),N=e("p",null,"We also had a bunch of comments about Pulsar itself and around its development, whilst we are listening and will take those comments on board, we won't be focusing on them in this post - that is info we will likely ask in a future poll. And on the topic of future polls it is clear that this was a really rather successful experiment, we got a good number of replies and feedback which will hopefully help us improve our community communication.",-1),G={href:"https://github.com/pulsar-edit/.github/tree/main/surveys/20230227-HowDidYouHearAboutUs",target:"_blank",rel:"noopener noreferrer"},C=e("p",null,"We will absolutely be using this format again as it seems to be a great way for people to give their feedback and comments, especially for those who may not use our busier social channels. So thank you again for all those who took part and look forward to the next one!",-1);function F(L,M){const t=h("ExternalLinkIcon");return n(),i("div",null,[c,r(" more "),p,e("p",null,[o("First of all here is an infographic "),e("a",m,[o("@Daeraxa"),a(t)]),o(" put together on the data that was provided.")]),r(" markdownlint-disable "),f,r(" markdownlint-restore "),w,_,b,g,e("p",null,[o("A slightly more surprising result was the number of people who found out via Reddit, mostly "),e("a",y,[o("/r/atom"),a(t)]),o(" but also from a number of programming or other related subreddits.")]),e("p",null,[o("We also had a lot of people come from seeing a YouTube video on the project and it seems a considerable number of those were from the "),e("a",k,[o("DistroTube channel"),a(t)]),o(" (which we mentioned in a "),e("a",v,[o("previous blog"),a(t)]),o("). It really goes a long way to show the effect that community content creators in this space can have on a project and its popularity.")]),x,D,T,W,e("p",null,[o("I think there was no surprise to see a large number of people check the "),e("a",A,[o("Discord"),a(t)]),o(" server, this is after all the area in which most of our active community members tend to hang out so if you want to pop in and say hi then this would be the place to do so.")]),e("p",null,[o("What took us aback slightly was just how popular the "),e("a",H,[o("/r/pulsaredit"),a(t)]),o(" subreddit, "),e("a",I,[o("GitHub Discussions"),a(t)]),o(" forum and "),e("a",S,[o("Mastodon"),a(t)]),o(" account are. I think we expected to see these a little lower on the popularity but instead it is clear that we need to invest a little more time and effort into these platforms to make them feel equal.")]),j,q,e("p",null,[o("Some comments we will definitely be taking to heart are the ones about improving our presence on some of our other channels (i.e. "),e("a",B,[o("Mastodon"),a(t)]),o(", "),e("a",R,[o("GitHub Discussions"),a(t)]),o(", "),e("a",V,[o("/r/pulsaredit"),a(t)]),o(" subreddit) as well as making announcements, releases and new blog posts more immediately obvious on our website.")]),E,N,e("p",null,[o("For anybody who wants to see the raw data (don't worry we went through everything to make sure there was definitely no identifiable data or anything unsavoury) then you can see it on our "),e("a",G,[o("organization repo"),a(t)]),o(".")]),C])}const Y=s(d,[["render",F],["__file","20230326-Daeraxa-Survey1-Results.html.vue"]]);export{Y as default}; diff --git a/assets/20230326-Daeraxa-Survey1-Results.html.e8bb3de9.js b/assets/20230326-Daeraxa-Survey1-Results.html.e8bb3de9.js new file mode 100644 index 0000000000..1f04b393ee --- /dev/null +++ b/assets/20230326-Daeraxa-Survey1-Results.html.e8bb3de9.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-91e3038e","path":"/blog/20230326-Daeraxa-Survey1-Results.html","title":"Survey Results: How did you hear about us?","lang":"en-US","frontmatter":{"title":"Survey Results: How did you hear about us?","author":"Daeraxa","date":"2023-03-26T00:00:00.000Z","category":["survey"],"tag":["community","socials"]},"excerpt":"

Here we have the results and analysis of our first community survey (and an infographic for those who like that kind of thing).

\\n","headers":[{"level":2,"title":"How did you hear about us?","slug":"how-did-you-hear-about-us","link":"#how-did-you-hear-about-us","children":[]},{"level":2,"title":"Which community area do you check for updates?","slug":"which-community-area-do-you-check-for-updates","link":"#which-community-area-do-you-check-for-updates","children":[]},{"level":2,"title":"Other comments on social media presence","slug":"other-comments-on-social-media-presence","link":"#other-comments-on-social-media-presence","children":[]},{"level":2,"title":"Summary","slug":"summary","link":"#summary","children":[]}],"git":{"updatedTime":1679935625000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":3}]},"readingTime":{"minutes":3.26,"words":977},"filePathRelative":"blog/20230326-Daeraxa-Survey1-Results.md","localizedDate":"March 26, 2023"}');export{e as data}; diff --git a/assets/20230401-Daeraxa-AprUpdate.html.cdce7d87.js b/assets/20230401-Daeraxa-AprUpdate.html.cdce7d87.js new file mode 100644 index 0000000000..90b5ca9026 --- /dev/null +++ b/assets/20230401-Daeraxa-AprUpdate.html.cdce7d87.js @@ -0,0 +1 @@ +import{_ as n}from"./tree-sitter.6a39e323.js";import{_ as s}from"./chocolatey.6923f762.js";import{_ as i,o as h,c as l,e as c,a as t,b as e,d as a,f as r,r as d}from"./app.0e1565ce.js";const p="/assets/i18n.77f15fa4.png",u="/assets/webassembly.dbe6d3f6.png",f="/assets/backend-badge.f36006d1.png",m="/assets/backend-badge-made.716d80d6.png",g="/assets/network.00727d52.png",b="/assets/node-package.b9535f33.png",_={},w=t("p",null,"Another dose of our regular monthly community update!",-1),y=t("h1",{id:"welcome-to-the-april-community-update-don-t-worry-no-fooling-going-on-here",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#welcome-to-the-april-community-update-don-t-worry-no-fooling-going-on-here","aria-hidden":"true"},"#"),e(" Welcome to the April Community Update (don't worry, no fooling going on here)!")],-1),k=t("p",null,"Hi everyone and welcome to the April installment of our monthly update that keeps you informed about what is going on in the background of Pulsar, work in progress, community contributions and more!",-1),v={href:"https://pulsar-edit.dev/blog/20230326-Daeraxa-Survey1-Results.html",target:"_blank",rel:"noopener noreferrer"},x={href:"https://pulsar-edit.dev/community.html",target:"_blank",rel:"noopener noreferrer"},A=r('

Now that has been dealt with, onto the updates!

i18n (internationalization) Efforts

[1]

',3),P={href:"https://github.com/Meadowsys",target:"_blank",rel:"noopener noreferrer"},T={href:"https://github.com/confused-Techie",target:"_blank",rel:"noopener noreferrer"},M={href:"https://crowdin.pulsar-edit.dev/",target:"_blank",rel:"noopener noreferrer"},C=r('

We will make a bigger announcement on this feature once it is ready.

Tree-sitter Modernization

[2]

',3),W={href:"https://github.com/mauricioszabo",target:"_blank",rel:"noopener noreferrer"},z={href:"https://github.com/savetheclocktower",target:"_blank",rel:"noopener noreferrer"},S={href:"https://tree-sitter.github.io/tree-sitter/using-parsers#pattern-matching-with-queries",target:"_blank",rel:"noopener noreferrer"},I=t("p",null,`Finally, we also moved away from the "binary" version of tree-sitter (instead using the WASM version). This means that we can migrate the editor to newer Electron versions without trouble, and it's also more future-proof - WASM is a technology that runs on browsers and it's not a Node.JS-only thing. It also makes our compilation process easier, more reliable, and faster (we don't need to compile all grammars and tree-sitter itself on Linux, Windows, and Mac, and for Intel and ARM processors).`,-1),q={href:"https://pulsar-edit.dev/blog/20230209-mauricioszabo-tree-sitter-part-1.html",target:"_blank",rel:"noopener noreferrer"},N=r('

TextMate Grammar Library & Superstring Migration to WASM

[3]

',2),B={href:"https://github.com/pulsar-edit/first-mate",target:"_blank",rel:"noopener noreferrer"},E={href:"https://github.com/pulsar-edit/node-oniguruma/",target:"_blank",rel:"noopener noreferrer"},L={href:"https://github.com/mauricioszabo",target:"_blank",rel:"noopener noreferrer"},O={href:"https://github.com/savetheclocktower",target:"_blank",rel:"noopener noreferrer"},F=t("code",null,"second-mate",-1),V={href:"https://github.com/microsoft/vscode-oniguruma",target:"_blank",rel:"noopener noreferrer"},j={href:"https://github.com/pulsar-edit/superstring",target:"_blank",rel:"noopener noreferrer"},H=t("code",null,"tree-sitter",-1),R=t("code",null,"first-mate",-1),U=t("h2",{id:"package-badges",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#package-badges","aria-hidden":"true"},"#"),e(" Package Badges")],-1),D={href:"https://github.com/confused-Techie",target:"_blank",rel:"noopener noreferrer"},G={href:"https://github.com/confused-Techie",target:"_blank",rel:"noopener noreferrer"},Y={href:"https://pulsar-edit.dev/tag/backend/",target:"_blank",rel:"noopener noreferrer"},J=t("p",null,[t("img",{src:f,alt:"backend-badge.png"})],-1),$=t("code",null,"Outdated",-1),K={href:"https://web.pulsar-edit.dev/packages/hydrogen",target:"_blank",rel:"noopener noreferrer"},Q=t("code",null,"ppm/pulsar -p",-1),X={href:"https://pulsar-edit.dev/docs/launch-manual/sections/using-pulsar/#github-or-git-remotes",target:"_blank",rel:"noopener noreferrer"},Z=t("p",null,[e("Not all badges are intended to be negative. We plan to offer badges to help the community such as "),t("code",null,"Looking for Maintainers"),e(" if a package author wishes for some help or to hand over the package maintenance entirely. We have already added a "),t("code",null,"Made for Pulsar!"),e(" badge to indicate packages which have been published or updated to the Pulsar backend to help people work out which packages are current and being updated as well as help prevent problems with installing very old and unmaintained Atom packages.")],-1),ee=t("p",null,[t("img",{src:m,alt:"backend-badge-made.png"})],-1),te={href:"https://github.com/pulsar-edit/package-backend/blob/main/docs/reference/badge-spec.md",target:"_blank",rel:"noopener noreferrer"},oe=r('

Badges are not currently available in Pulsar itself but we are working on it!

Package Service Filtering

[4]

An interesting update to our backend that allows for new queries to be made for finding out what packages provide or consume a given service. This can be used in a number of ways, users can easily filter by package functionality rather than just keywords and developers/packages authors can search to see what packages may provide or consume a given service to help create a more interconnected set of packages.

',4),ae={href:"https://web.pulsar-edit.dev/packages?service=terminal&serviceType=provided",target:"_blank",rel:"noopener noreferrer"},re=t("code",null,"terminal",-1),ne=t("code",null,"terminal",-1),se=r('

This functionality is currently only available via the API and website query but we plan to offer some interesting and (hopefully) useful features on the website (and potentially Pulsar itself) to display these services.

Bundling ppm Within Pulsar

[5]

As you may know, ppm is the Pulsar Package Manager \u2014 the thing you interact with when you want to install or update packages. Most people will probably only use this by interacting with the packages menu inside Pulsar but ppm is also supplied as a command line applications accessible via either ppm or pulsar -p. One issue with this approach is that we have to account for all of the different OSs and packaging methods which means adding it to $PATH correctly on each OS etc. (This was even an issue with Atom which had a special macOS only command to install the apm command line again).

With this change we hopefully avoid all of this as it is simply part of the main pulsar executable so we only have to account for the details of one application and not two. This also means we can do some stuff we weren't able to easily do before, like expose the ppm commands within Pulsar itself without having to use a terminal at all. This update has the potential to improve a lot of things so watch this space!

Chocolatey

',7),ie={href:"https://chocolatey.org/",target:"_blank",rel:"noopener noreferrer"},he=t("hr",{class:"footnotes-sep"},null,-1),le={class:"footnotes"},ce={class:"footnotes-list"},de={id:"footnote1",class:"footnote-item"},pe={href:"https://openclipart.org/detail/303534/translation-icon",target:"_blank",rel:"noopener noreferrer"},ue=t("a",{href:"#footnote-ref1",class:"footnote-backref"},"\u21A9\uFE0E",-1),fe={id:"footnote2",class:"footnote-item"},me={href:"https://tree-sitter.github.io/tree-sitter/",target:"_blank",rel:"noopener noreferrer"},ge=t("a",{href:"#footnote-ref2",class:"footnote-backref"},"\u21A9\uFE0E",-1),be={id:"footnote3",class:"footnote-item"},_e={href:"https://github.com/carlosbaraza/web-assembly-logo",target:"_blank",rel:"noopener noreferrer"},we=t("a",{href:"#footnote-ref3",class:"footnote-backref"},"\u21A9\uFE0E",-1),ye={id:"footnote4",class:"footnote-item"},ke={href:"https://openclipart.org/detail/169025/redes",target:"_blank",rel:"noopener noreferrer"},ve=t("a",{href:"#footnote-ref4",class:"footnote-backref"},"\u21A9\uFE0E",-1),xe={id:"footnote5",class:"footnote-item"},Ae={href:"https://vectorified.com/node-js-icon",target:"_blank",rel:"noopener noreferrer"},Pe=t("a",{href:"#footnote-ref5",class:"footnote-backref"},"\u21A9\uFE0E",-1);function Te(Me,Ce){const o=d("ExternalLinkIcon");return h(),l("div",null,[w,c(" more "),y,k,t("p",null,[e("One thing to quickly note in this opening ramble, a big thank you for everyone who responded to our recent community survey, if you want to see the results and analysis then have a look at the previous "),t("a",v,[e("blog post"),a(o)]),e(" we wrote. If you missed it then don't worry, there will be plenty more examples to have your say as we plan to create more surveys in the future.")]),t("p",null,[e("You will notice I've tried to jazz the post up a little with some images this time around, please "),t("a",x,[e("let us know"),a(o)]),e(" if you have any comments on this addition.")]),A,t("p",null,[t("a",P,[e("@meadowsys"),a(o)]),e(" and "),t("a",T,[e("@confused-techie"),a(o)]),e(" have been making great progress on the efforts to provide native i18n functionality within Pulsar. This means you will be able to select your own locale/language and Pulsar will be able to translate the various menus and items for you. Of course the big caveat here is that currently none of it is translated so if you have the ability to then we will be asking the community to provide translations wherever possible. To do this we are currently planning to use "),t("a",M,[e("Crowdin"),a(o)]),e(".")]),C,t("p",null,[e("Another month and more progress has been made on this front, particularly by "),t("a",W,[e("@maur\xEDcio szabo"),a(o)]),e(" and "),t("a",z,[e("@savetheclocktower"),a(o)]),e(", to get our tree-sitter implementation modernized. Some users noticed that opening some languages on Pulsar, like C++, Java, and Ruby, makes some syntax highlighting tokens different from what they were in Atom, and most of the time, highlighting is either wrong or some tokens are simply missing.")]),t("p",null,[e("With this rewrite, we're updating Atom\u2019s tree-sitter support to use a newer approach for mapping tree nodes to scope names: using "),t("a",S,[e("queries"),a(o)]),e('. This means more accurate mapping of scopes. We can also use queries to define indentation rules, code folding boundaries, and other features. It further means we won\u2019t be stuck with a "different" implementation of tree-sitter from all other editors, and we can keep up-to-date with the recent developments and advancements on tree-sitter.')]),I,t("p",null,[e("For more background info see some of the previous updates or "),t("a",q,[e("Maur\xEDcio's blog post"),a(o)]),e(" on the topic.")]),N,t("p",null,[e("Atom and current versions of Pulsar use a library called "),t("a",B,[e("first-mate"),a(o)]),e(" that uses "),t("a",E,[e("node-oniguruma"),a(o)]),e(" to parse the legacy TextMate grammars (the original ones before Atom moved to tree-sitter as its primary grammar choice). Like tree-sitter and superstring, this is something that is preventing our migration to modern versions of Electron so "),t("a",L,[e("@maur\xEDcio szabo"),a(o)]),e(" and "),t("a",O,[e("@savetheclocktower"),a(o)]),e(" have been working on this to instead migrate to a new library we are calling "),F,e(" which uses "),t("a",V,[e("vscode-oniguruma"),a(o)]),e(" instead which is WASM based. Funny how things eventually come back around to borrowing from VSC instead...")]),t("p",null,[e("Work has also been going on to migrate the existing "),t("a",j,[e("superstring"),a(o)]),e(" library to WASM. This, along with "),H,e(" and "),R,e(" mentioned above, is blocking our progress to modern Electron versions. Superstring is the library at the heart of the editor itself so this is quite a big change that will require a good amount of work and testing before it is ready but we are making progress!")]),U,t("p",null,[t("a",D,[e("@confused-techie"),a(o)]),e(" has made some changes to the package backend and website to support badges which can be applied to packages. Currently these can only be added by the Pulsar team but we will hopefully be looking to roll out something to package authors later. Essentially this allows us to add some additional metadata to a package to give information to Pulsar users. This is particularly important for Pulsar as a fork of Atom because we brought with us the vast majority of the Atom packages that were created over the years (see "),t("a",G,[e("@confused-techie"),a(o)]),e("'s previous "),t("a",Y,[e("blog posts"),a(o)]),e(" on this subject for more info').")]),J,t("p",null,[e("Currently we have rolled out an "),$,e(" badge - this is designed so that we can help Pulsar users with additional info about particular packages which we know are being actively developed but have not been updated by the authors to the Pulsar backend. For example the "),t("a",K,[e("hydrogen package"),a(o)]),e(" (shown above) has some problems working with Pulsar, changes have been added to it to make it work but as these changes have not been pushed to the backend by the package authors the only way of getting it is to install them using "),Q,e(" using the "),t("a",X,[e("GitHub/Git Remotes"),a(o)]),e(" functionality. To see more info on the specific reason a badge was added, you can click the badge and it will bring you to a document of our backend admin actions which include the reason for the addition as well as info to help.")]),Z,ee,t("p",null,[e("You can read the "),t("a",te,[e("badge specification"),a(o)]),e(" for more info.")]),oe,t("p",null,[e("For example this query "),t("a",ae,[e("https://web.pulsar-edit.dev/packages?service=terminal&serviceType=provided"),a(o)]),e(" will search for all packages that provide the "),re,e(" service so if somebody wishes to find packages that provide or consume "),ne,e(" then it is now easy to do so.")]),se,t("p",null,[e("For those unfamiliar, "),t("a",ie,[e("Chocolately"),a(o)]),e(" is a package manager for Windows and something we have been wanting to officially support for a while now. Whilst we have not got anything available yet (don't worry we will make an announcement once it is) we have a bunch of community members who have been working on this and helping out so a big thank you to community members @HighHarmonics, @il_mix and @COLAMAroro for their contributions here.")]),he,t("section",le,[t("ol",ce,[t("li",de,[t("p",null,[e("Image from "),t("a",pe,[e("https://openclipart.org/"),a(o)]),e(),ue])]),t("li",fe,[t("p",null,[e("Image from "),t("a",me,[e("https://tree-sitter.github.io/tree-sitter/"),a(o)]),e(" - Copyright (c) 2018-2021 Max Brunsfeld "),ge])]),t("li",be,[t("p",null,[e("Image from "),t("a",_e,[e("https://github.com/carlosbaraza/web-assembly-logo"),a(o)]),e(" - CC0 1.0 Universal "),we])]),t("li",ye,[t("p",null,[e("Image from "),t("a",ke,[e("https://openclipart.org/"),a(o)]),e(),ve])]),t("li",xe,[t("p",null,[e("Image from "),t("a",Ae,[e("https://vectorified.com"),a(o)]),e(" - CC BY-NC 4.0 Licence "),Pe])])])])])}const Ie=i(_,[["render",Te],["__file","20230401-Daeraxa-AprUpdate.html.vue"]]);export{Ie as default}; diff --git a/assets/20230401-Daeraxa-AprUpdate.html.ef4f2fd8.js b/assets/20230401-Daeraxa-AprUpdate.html.ef4f2fd8.js new file mode 100644 index 0000000000..7b782851b9 --- /dev/null +++ b/assets/20230401-Daeraxa-AprUpdate.html.ef4f2fd8.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-e1c35862","path":"/blog/20230401-Daeraxa-AprUpdate.html","title":"Community Update","lang":"en-US","frontmatter":{"title":"Community Update","author":"Daeraxa","date":"2023-04-01T00:00:00.000Z","category":["news","log"],"tag":["update"]},"excerpt":"

Another dose of our regular monthly community update!

\\n","headers":[{"level":2,"title":"i18n (internationalization) Efforts","slug":"i18n-internationalization-efforts","link":"#i18n-internationalization-efforts","children":[]},{"level":2,"title":"Tree-sitter Modernization","slug":"tree-sitter-modernization","link":"#tree-sitter-modernization","children":[]},{"level":2,"title":"TextMate Grammar Library & Superstring Migration to WASM","slug":"textmate-grammar-library-superstring-migration-to-wasm","link":"#textmate-grammar-library-superstring-migration-to-wasm","children":[]},{"level":2,"title":"Package Badges","slug":"package-badges","link":"#package-badges","children":[]},{"level":2,"title":"Package Service Filtering","slug":"package-service-filtering","link":"#package-service-filtering","children":[]},{"level":2,"title":"Bundling ppm Within Pulsar","slug":"bundling-ppm-within-pulsar","link":"#bundling-ppm-within-pulsar","children":[]},{"level":2,"title":"Chocolatey","slug":"chocolatey","link":"#chocolatey","children":[]}],"git":{"updatedTime":1680223867000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":9}]},"readingTime":{"minutes":5.91,"words":1774},"filePathRelative":"blog/20230401-Daeraxa-AprUpdate.md","localizedDate":"April 1, 2023"}');export{e as data}; diff --git a/assets/20230401-confused-Techie-PON.html.937b0317.js b/assets/20230401-confused-Techie-PON.html.937b0317.js new file mode 100644 index 0000000000..cbc241ab14 --- /dev/null +++ b/assets/20230401-confused-Techie-PON.html.937b0317.js @@ -0,0 +1,24 @@ +import{_ as a,o,c as i,e as l,a as e,b as n,d as s,f as r,r as p}from"./app.0e1565ce.js";const c={},u=e("p",null,"The release of PON!",-1),d=r(`

One day during discussions amoung the Pulsar dev team, we came to the topic of what file types to support for user's config files. While it was mostly a question of how support would go with allowing anything from JSON, JSON5, TOML, YAML, CSON, and anything else we could think of, in the end we couldn't agree on a solution.

We kept finding downsides to every possibility, and concerns for support with so many. So we decided it only made sense, to take the true Atom approch, and reinvent the wheel with an in-house solution.

What we created is PON (Pulsar Object Notation)!

This new file format takes the best thing of every language and combines them into one.

Taking all of their strengths, and none of their weaknesses. Pulsar Object Notation has a more expansive feature set than any other object file format than has ever existed!

Best of all PON is easy to learn, and easy to use!

Compare the following JSON:

{
+	"core": {
+		"projectHome": "/home/dae/pulsar",
+		"welcome": {
+			"showOnStartup": true,
+			"showChangeLog": false
+		}
+	}
+}
+

The above JSON is flat out ugly. Plus, it has no support for comments, and it's nearly impossible to see which object a key belongs to without painstakingly counting each and every bracket; really JSON is an outdated, and hard to read format.

But from the impossible to use JSON above, here is the same thing in PON!

Dim core As Object
+Dim projectHome As String
+Dim welcome As Object
+Dim showOnStartup As Boolean
+Dim showChangeLog As Boolean
+
+10 # <<core>> [
+  10.10 ## projectHome:= <</home/dae/pulsar>>
+  10.20 ## <<welcome>> [
+    <\xBF--<<An easy to type comment!>>--?>
+    10.20.10 ### showOnStartup:= <<unfalse>>
+    10.20.20 ### showChangeLog:= <<untrue>>
+  ];
+];
+
`,12),h={href:"https://github.com/confused-Techie/pon/blob/main/spec/1.0.0-specification.md",target:"_blank",rel:"noopener noreferrer"},m=e("p",null,"As you might expect, PON already has full support in Pulsar!",-1),v={href:"https://web.pulsar-edit.dev/packages/language-pon",target:"_blank",rel:"noopener noreferrer"},g={href:"https://github.com/confused-Techie/pon",target:"_blank",rel:"noopener noreferrer"},b=e("p",null,"With the above module, we are happy to announce that the next release of Pulsar will automatically convert all configuration files it finds on your system (Not just Pulsar ones) into PON. We are hoping to do this system wide to help increase adoption of PON as a data structure.",-1),f=e("p",null,"So stay tuned for further updates, and when to expect all system files to be converted!",-1),y=e("p",null,"Happy stargazing!",-1),k=e("p",null,[n("Important note: this is a "),e("strong",null,"joke"),n(". But if you do like PON, the resources above are fully functional and available to use. Happy April Fools everyone.")],-1);function _(w,O){const t=p("ExternalLinkIcon");return o(),i("div",null,[u,l(" more "),d,e("p",null,[n("Look at that! As you can see, anyone can pick it up without even reading a manual! But if you do need one, make sure to read the full "),e("a",h,[n("Specification 1.0.0"),s(t)])]),m,e("p",null,[n("You can install the syntax highlighting for Pulsar via "),e("a",v,[n("Language PON"),s(t)]),n(" on the Pulsar Package Registry.")]),e("p",null,[n("Plus, by using the JavaScript Module "),e("a",g,[n("PON"),s(t)]),n(" we can convert any JSON data into a proper PON object, and read any PON object form the filesystem into a JavaScript Object.")]),b,f,y,k])}const P=a(c,[["render",_],["__file","20230401-confused-Techie-PON.html.vue"]]);export{P as default}; diff --git a/assets/20230401-confused-Techie-PON.html.f6d970d6.js b/assets/20230401-confused-Techie-PON.html.f6d970d6.js new file mode 100644 index 0000000000..fddb4b665d --- /dev/null +++ b/assets/20230401-confused-Techie-PON.html.f6d970d6.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-0af04e6e","path":"/blog/20230401-confused-Techie-PON.html","title":"Meet PON (Pulsar Object Notation)","lang":"en-US","frontmatter":{"title":"Meet PON (Pulsar Object Notation)","author":"confused-Techie","date":"2023-04-01T00:00:00.000Z","category":["dev"],"tag":["joke"]},"excerpt":"

The release of PON!

\\n","headers":[],"git":{"updatedTime":1680393842000,"contributors":[{"name":"confused_techie","email":"dev@lhbasics.com","commits":8},{"name":"confused-Techie","email":"dev@lhbasics.com","commits":1}]},"readingTime":{"minutes":1.75,"words":525},"filePathRelative":"blog/20230401-confused-Techie-PON.md","localizedDate":"April 1, 2023"}');export{e as data}; diff --git a/assets/20230418-Daeraxa-v1.104.0.html.498d1fde.js b/assets/20230418-Daeraxa-v1.104.0.html.498d1fde.js new file mode 100644 index 0000000000..7cdd9b17d5 --- /dev/null +++ b/assets/20230418-Daeraxa-v1.104.0.html.498d1fde.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-3a723159","path":"/blog/20230418-Daeraxa-v1.104.0.html","title":"Get Ready for another fantastically essential release, Pulsar 1.104.0 is now available!","lang":"en-US","frontmatter":{"title":"Get Ready for another fantastically essential release, Pulsar 1.104.0 is now available!","author":"Daeraxa","date":"2023-04-15T00:00:00.000Z","category":["dev"],"tag":["release"]},"excerpt":"

Check out our newest Regular Release for Pulsar! Available Now!

\\n","headers":[{"level":3,"title":"Pulsar","slug":"pulsar","link":"#pulsar","children":[]},{"level":3,"title":"second-mate","slug":"second-mate","link":"#second-mate","children":[]},{"level":3,"title":"autosave","slug":"autosave","link":"#autosave","children":[]},{"level":3,"title":"bracket-matcher","slug":"bracket-matcher","link":"#bracket-matcher","children":[]},{"level":3,"title":"timecop","slug":"timecop","link":"#timecop","children":[]},{"level":3,"title":"keybinding-resolver","slug":"keybinding-resolver","link":"#keybinding-resolver","children":[]}],"git":{"updatedTime":1681777709000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":2.51,"words":753},"filePathRelative":"blog/20230418-Daeraxa-v1.104.0.md","localizedDate":"April 15, 2023"}');export{e as data}; diff --git a/assets/20230418-Daeraxa-v1.104.0.html.85facd51.js b/assets/20230418-Daeraxa-v1.104.0.html.85facd51.js new file mode 100644 index 0000000000..6931e3c307 --- /dev/null +++ b/assets/20230418-Daeraxa-v1.104.0.html.85facd51.js @@ -0,0 +1 @@ +import{_ as o,o as n,c as l,a as e,b as t,d as a,e as s,f as i,r as d}from"./app.0e1565ce.js";const u={},p={href:"https://github.com/pulsar-edit/pulsar/releases/tag/v1.104.0",target:"_blank",rel:"noopener noreferrer"},h=i('

What is new in 1.104.0?

This release contains many internal changes and upgrades, focusing on preparing the editor for much bigger changes to come.

One technical change is that we have internally patched WebComponents (document.registerElement), which will potentially fix many of our community packages that rely on this (deprecated) functionality.

We've also started our first migrations to WASM based packages (which is required for new NodeJS Versions). Any issues found here will be important to address, in order to ensure future updates go smoothly, and to unblock the road to compatibility with newer Electron versions. Please let us know if you find anything broken due to these updates!

We also have updates such as improved whitespace in a certain PHP snippet, and bleeding edge HTML completions. And with more decaf work, we should be seeing some slightly faster startup speeds.

Last but not least, we can expect to see this release being available on Chocolatey for Windows; Thank you HighHarmonics, il_mix, and COLAMAroro!

But as always we want to say a huge thanks to all those that contribute and donate to Pulsar, making it possible for us to continually release these improvements. And we want to give a special thanks to the new faces we are seeing in this update, with some brand new contributors!

As always we appreciate every single one of you, happy coding, and see you among the stars!


Pulsar

',12),c=e("code",null,"settings-view",-1),m={href:"https://github.com/pulsar-edit/pulsar/pull/451",target:"_blank",rel:"noopener noreferrer"},_={href:"https://github.com/pulsar-edit/pulsar/pull/488",target:"_blank",rel:"noopener noreferrer"},g={href:"https://github.com/pulsar-edit/pulsar/pull/483",target:"_blank",rel:"noopener noreferrer"},f=e("code",null,"wrap-guide",-1),b={href:"https://github.com/pulsar-edit/pulsar/pull/443",target:"_blank",rel:"noopener noreferrer"},k={href:"https://github.com/pulsar-edit/pulsar/pull/424",target:"_blank",rel:"noopener noreferrer"},w={href:"https://github.com/pulsar-edit/pulsar/pull/454",target:"_blank",rel:"noopener noreferrer"},v={href:"https://github.com/pulsar-edit/pulsar/pull/471",target:"_blank",rel:"noopener noreferrer"},x={href:"https://github.com/pulsar-edit/pulsar/pull/463",target:"_blank",rel:"noopener noreferrer"},y={href:"https://github.com/pulsar-edit/pulsar/pull/406",target:"_blank",rel:"noopener noreferrer"},A={href:"https://github.com/pulsar-edit/pulsar/pull/446",target:"_blank",rel:"noopener noreferrer"},S=e("code",null,"autocomplete-html",-1),C=e("code",null,"completions.json",-1),P={href:"https://github.com/pulsar-edit/pulsar/pull/405",target:"_blank",rel:"noopener noreferrer"},T={href:"https://github.com/pulsar-edit/pulsar/pull/450",target:"_blank",rel:"noopener noreferrer"},M={href:"https://github.com/pulsar-edit/pulsar/pull/438",target:"_blank",rel:"noopener noreferrer"},U={href:"https://github.com/pulsar-edit/pulsar/pull/435",target:"_blank",rel:"noopener noreferrer"},W={href:"https://github.com/pulsar-edit/pulsar/pull/440",target:"_blank",rel:"noopener noreferrer"},E={href:"https://github.com/pulsar-edit/pulsar/pull/414",target:"_blank",rel:"noopener noreferrer"},F={href:"https://github.com/pulsar-edit/pulsar/pull/434",target:"_blank",rel:"noopener noreferrer"},R={href:"https://github.com/pulsar-edit/pulsar/pull/239",target:"_blank",rel:"noopener noreferrer"},z={href:"https://github.com/pulsar-edit/pulsar/pull/432",target:"_blank",rel:"noopener noreferrer"},B=e("h3",{id:"second-mate",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#second-mate","aria-hidden":"true"},"#"),t(" second-mate")],-1),D={href:"https://github.com/pulsar-edit/second-mate/pull/1",target:"_blank",rel:"noopener noreferrer"},N=e("h3",{id:"autosave",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#autosave","aria-hidden":"true"},"#"),t(" autosave")],-1),V={href:"https://github.com/pulsar-edit/autosave/pull/2",target:"_blank",rel:"noopener noreferrer"},H={href:"https://github.com/pulsar-edit/autosave/pull/1",target:"_blank",rel:"noopener noreferrer"},L=e("h3",{id:"bracket-matcher",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#bracket-matcher","aria-hidden":"true"},"#"),t(" bracket-matcher")],-1),I={href:"https://github.com/pulsar-edit/bracket-matcher/pull/3",target:"_blank",rel:"noopener noreferrer"},j={href:"https://github.com/pulsar-edit/bracket-matcher/pull/2",target:"_blank",rel:"noopener noreferrer"},O={href:"https://github.com/pulsar-edit/bracket-matcher/pull/1",target:"_blank",rel:"noopener noreferrer"},q=e("h3",{id:"timecop",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#timecop","aria-hidden":"true"},"#"),t(" timecop")],-1),G={href:"https://github.com/pulsar-edit/timecop/pull/1",target:"_blank",rel:"noopener noreferrer"},J=e("h3",{id:"keybinding-resolver",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#keybinding-resolver","aria-hidden":"true"},"#"),t(" keybinding-resolver")],-1),Y={href:"https://github.com/pulsar-edit/keybinding-resolver/pull/1",target:"_blank",rel:"noopener noreferrer"};function K(Q,X){const r=d("ExternalLinkIcon");return n(),l("div",null,[e("p",null,[t("Check out our newest Regular Release for Pulsar! "),e("a",p,[t("Available Now!"),a(r)])]),s(" more "),h,e("ul",null,[e("li",null,[t("Added: "),c,t(" Support for Badges "),e("a",m,[t("@confused-Techie"),a(r)])]),e("li",null,[t("Removed: remove weird duplicate accented fixture file (hopefully?) "),e("a",_,[t("@Meadowsys"),a(r)])]),e("li",null,[t("Added: Add optional entitlements monkey-patch "),e("a",g,[t("@confused-Tecie"),a(r)])]),e("li",null,[t("Added: Decaf "),f,t(),e("a",b,[t("@confused-Techie"),a(r)])]),e("li",null,[t("Added: Additional Bundling of Core Packages "),e("a",k,[t("@confused-Techie"),a(r)])]),e("li",null,[t("Added: add allow-jit entitlement (fixes Apple Silicon builds) "),e("a",w,[t("@Meadowsys"),a(r)])]),e("li",null,[t('Removed: Revert "Create i18n API" '),e("a",v,[t("@mauricioszabo"),a(r)])]),e("li",null,[t("Added: Build first, and test later "),e("a",x,[t("@mauricioszabo"),a(r)])]),e("li",null,[t("Update: [settings-view] Update package snippets view to reflect new features "),e("a",y,[t("@savetheclocktower"),a(r)])]),e("li",null,[t("Added: Create i18n API "),e("a",A,[t("@Meadowsys"),a(r)])]),e("li",null,[t("Added: Add Automated updating of "),S,t(),C,t(),e("a",P,[t("@confused-Techie"),a(r)])]),e("li",null,[t("Fixed: docs: fix markdown links in packages README "),e("a",T,[t("@oakmac"),a(r)])]),e("li",null,[t("Fixed: Patch document register element "),e("a",M,[t("@mauricioszabo"),a(r)])]),e("li",null,[t('Added: Using "second-mate" '),e("a",U,[t("@mauricioszabo"),a(r)])]),e("li",null,[t(`Fixed: Fix spacing of PHP's "for ..." snippet `),e("a",W,[t("@machitgarha"),a(r)])]),e("li",null,[t("Update: Update resources metadata "),e("a",E,[t("@Spiker985"),a(r)])]),e("li",null,[t("Fixed: Cirrus: Windows: install ppm deps with Yarn "),e("a",F,[t("@DeeDeeG"),a(r)])]),e("li",null,[t("Added: made cirrus build scripts consistent "),e("a",R,[t("@Sertonix"),a(r)])]),e("li",null,[t("Update: Update package.json author "),e("a",z,[t("@Daeraxa"),a(r)])])]),B,e("ul",null,[e("li",null,[t("Added: Migrate to vscode-oniguruma "),e("a",D,[t("@mauricioszabo"),a(r)])])]),N,e("ul",null,[e("li",null,[t("Removed: removed fs-plus dependency "),e("a",V,[t("@Sertonix"),a(r)])]),e("li",null,[t("Update: Cleanup and rename "),e("a",H,[t("@Sertonix"),a(r)])])]),L,e("ul",null,[e("li",null,[t("Fixed: Fixing test that need to run locally "),e("a",I,[t("@mauricioszabo"),a(r)])]),e("li",null,[t("Update: cleanup .md and rename repo url "),e("a",j,[t("@Sertonix"),a(r)])]),e("li",null,[t("Update: Rename A[a]tom -> P[p]ulsar "),e("a",O,[t("@Spiker985"),a(r)])])]),q,e("ul",null,[e("li",null,[t("Update: cleanup and rename "),e("a",G,[t("@Sertonix"),a(r)])])]),J,e("ul",null,[e("li",null,[t("Update: Cleanup and rename "),e("a",Y,[t("@Sertonix"),a(r)])])])])}const $=o(u,[["render",K],["__file","20230418-Daeraxa-v1.104.0.html.vue"]]);export{$ as default}; diff --git a/assets/20230430-Daeraxa-Survey2.html.0177dabb.js b/assets/20230430-Daeraxa-Survey2.html.0177dabb.js new file mode 100644 index 0000000000..f66c6cbf68 --- /dev/null +++ b/assets/20230430-Daeraxa-Survey2.html.0177dabb.js @@ -0,0 +1 @@ +import{_ as t,o as s,c as a,e as r,a as e,b as o,d as c,r as l}from"./app.0e1565ce.js";const i={},u=e("p",null,"A short survey asking for community feedback on a couple of aspects of the project",-1),d=e("p",null,"Due to the success of the first survey we have decided to use this format again to seek community opinion on a couple of elements of the project.",-1),m=e("p",null,"In particular we are looking for feedback on some Pulsar UI changes, our documentation and publishing community packages.",-1),p=e("p",null,[e("strong",null,[e("em",null,"There is no need to sign into any account to respond to this survey and no personal data is being collected (emails, names etc.).")])],-1),_={href:"https://docs.google.com/forms/d/e/1FAIpQLSfnEEAQHUJbRVcH2NlNvLxmK0ZUcb5xuOCt1pYOGytvne0kJw/viewform?usp=sf_link",target:"_blank",rel:"noopener noreferrer"};function f(h,g){const n=l("ExternalLinkIcon");return s(),a("div",null,[u,r(" more "),d,m,p,e("p",null,[e("a",_,[o("You can find the Survey (Google Forms) here"),c(n)]),o(".")])])}const v=t(i,[["render",f],["__file","20230430-Daeraxa-Survey2.html.vue"]]);export{v as default}; diff --git a/assets/20230430-Daeraxa-Survey2.html.570aacd8.js b/assets/20230430-Daeraxa-Survey2.html.570aacd8.js new file mode 100644 index 0000000000..f27c136185 --- /dev/null +++ b/assets/20230430-Daeraxa-Survey2.html.570aacd8.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-1d4a9cb2","path":"/blog/20230430-Daeraxa-Survey2.html","title":"Survey: Project Feedback","lang":"en-US","frontmatter":{"title":"Survey: Project Feedback","author":"Daeraxa","date":"2023-04-30T00:00:00.000Z","category":["survey"],"tag":["community","socials"]},"excerpt":"

A short survey asking for community feedback on a couple of aspects of the project

\\n","headers":[],"git":{"updatedTime":1682905301000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":2}]},"readingTime":{"minutes":0.4,"words":120},"filePathRelative":"blog/20230430-Daeraxa-Survey2.md","localizedDate":"April 30, 2023"}');export{e as data}; diff --git a/assets/20230501-Daeraxa-MayUpdate.html.40844741.js b/assets/20230501-Daeraxa-MayUpdate.html.40844741.js new file mode 100644 index 0000000000..ee3e43c063 --- /dev/null +++ b/assets/20230501-Daeraxa-MayUpdate.html.40844741.js @@ -0,0 +1 @@ +const e=JSON.parse(`{"key":"v-638b3e6c","path":"/blog/20230501-Daeraxa-MayUpdate.html","title":"Community Update","lang":"en-US","frontmatter":{"title":"Community Update","author":"Daeraxa","date":"2023-05-01T00:00:00.000Z","category":["news","log"],"tag":["update"]},"excerpt":"

Is it a bird? Is it a plane? No it's the May community update!

\\n","headers":[{"level":2,"title":"Octicons iconography update","slug":"octicons-iconography-update","link":"#octicons-iconography-update","children":[]},{"level":2,"title":"Symbols view improvements","slug":"symbols-view-improvements","link":"#symbols-view-improvements","children":[]},{"level":2,"title":"CI Testing speed upgrade","slug":"ci-testing-speed-upgrade","link":"#ci-testing-speed-upgrade","children":[]},{"level":2,"title":"Backend webhook","slug":"backend-webhook","link":"#backend-webhook","children":[]},{"level":2,"title":"Package website readme links overhaul","slug":"package-website-readme-links-overhaul","link":"#package-website-readme-links-overhaul","children":[]},{"level":2,"title":"Decoupling http handling for the package backend","slug":"decoupling-http-handling-for-the-package-backend","link":"#decoupling-http-handling-for-the-package-backend","children":[]}],"git":{"updatedTime":1683245063000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":13}]},"readingTime":{"minutes":6.49,"words":1947},"filePathRelative":"blog/20230501-Daeraxa-MayUpdate.md","localizedDate":"May 1, 2023"}`);export{e as data}; diff --git a/assets/20230501-Daeraxa-MayUpdate.html.f0636831.js b/assets/20230501-Daeraxa-MayUpdate.html.f0636831.js new file mode 100644 index 0000000000..5478f8b930 --- /dev/null +++ b/assets/20230501-Daeraxa-MayUpdate.html.f0636831.js @@ -0,0 +1,23 @@ +import{_ as o,o as i,c as r,e as l,a as e,b as t,d as s,f as a,r as c}from"./app.0e1565ce.js";const h="/assets/octicons1.9b0fa408.png",p="/assets/octicons2.69cc8255.png",d="/assets/octicons3.a3bf7b53.png",u="/assets/symbols-view.91132e73.png",m="/assets/ghtests.c6b1b477.png",g="/assets/package-webhook.7e0f3c29.png",f="/assets/package-frontend.a8bc39dd.png",k="/assets/sunsetting.aedab22a.png",b="/assets/flight-manual.36cb1750.png",v={},_=e("p",null,"Is it a bird? Is it a plane? No it's the May community update!",-1),w=e("h1",{id:"welcome-to-the-may-community-update",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#welcome-to-the-may-community-update","aria-hidden":"true"},"#"),t(" Welcome to the May Community Update")],-1),y=e("p",null,[t("Hello again and a warm welcome to our May community update! In store for you this time are Pulsar iconography updates, "),e("code",null,"symbols-view"),t(" improvements, CI upgrades and bunch of upgrades to our package backend and website.")],-1),x={href:"https://docs.google.com/forms/d/e/1FAIpQLSfnEEAQHUJbRVcH2NlNvLxmK0ZUcb5xuOCt1pYOGytvne0kJw/viewform?usp=sf_link",target:"_blank",rel:"noopener noreferrer"},j=e("p",null,"Now without further waffling, onto the updates!",-1),T=e("h2",{id:"octicons-iconography-update",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#octicons-iconography-update","aria-hidden":"true"},"#"),t(" Octicons iconography update")],-1),I={href:"https://github.com/pulsar-edit/pulsar/pull/509",target:"_blank",rel:"noopener noreferrer"},E={href:"https://github.com/confused-Techie",target:"_blank",rel:"noopener noreferrer"},S={href:"https://primer.style/octicons/",target:"_blank",rel:"noopener noreferrer"},O=e("em",null,"really",-1),A=e("p",null,"You can see examples of the new Octicons in the below image:",-1),N=e("img",{src:h},null,-1),P=e("p",null,"One of the initial problems to work around was that Octicons no longer ships with font files, only the svg images. This meant that in order to use them they first had to be created as a font. This also meant modifying the font to make sure that the various codes aligned with the old ones so that they could, where possible, be a drop in replacement for the old version without breaking the naming scheme from upstream Octicons for use as reference material.",-1),q=e("img",{src:p},null,-1),L=e("p",null,[t("We have a couple of different goals here. The main one is simply to give more options to ourselves and users in terms of icon variation so that we can make better choices for icon usage. We would of course retain the "),e("code",null,"4.4.0"),t(" set (just as "),e("code",null,"2.1.2"),t(" has also been retained).")],-1),C={href:"https://docs.google.com/forms/d/e/1FAIpQLSfnEEAQHUJbRVcH2NlNvLxmK0ZUcb5xuOCt1pYOGytvne0kJw/viewform?usp=sf_link",target:"_blank",rel:"noopener noreferrer"},J=e("img",{src:d},null,-1),D=e("p",null,"There is still some work to go here, particularly on making sure that where icons are used we are still using the appropriate ones or selecting a more appropriate icon now it is available in the new set.",-1),H={href:"https://github.com/pulsar-edit/pulsar/blob/octicons-v18.3.0/static/icons/README.md",target:"_blank",rel:"noopener noreferrer"},U=e("h2",{id:"symbols-view-improvements",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#symbols-view-improvements","aria-hidden":"true"},"#"),t(" Symbols view improvements")],-1),W=e("img",{src:u},null,-1),F={href:"https://ctags.io/",target:"_blank",rel:"noopener noreferrer"},M=e("em",null,"at all",-1),R={href:"https://github.com/savetheclocktower",target:"_blank",rel:"noopener noreferrer"},V={href:"https://tree-sitter.github.io/tree-sitter/code-navigation-systems",target:"_blank",rel:"noopener noreferrer"},B={href:"https://microsoft.github.io/language-server-protocol/",target:"_blank",rel:"noopener noreferrer"},G=e("code",null,"symbol-view",-1),Y=e("p",null,[t("So to make steps towards achieving this we would need to work on "),e("code",null,"symbols-view"),t(" to be more generic and then update Pulsar and its packages to take advantage of the changes.")],-1),Q=e("p",null,"This has the potential to really upgrade the functionality and make navigating files and projects far more intuitive. It isn't ready just yet but don't worry, we will have more information to share on this feature in the coming weeks.",-1),K=e("h2",{id:"ci-testing-speed-upgrade",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#ci-testing-speed-upgrade","aria-hidden":"true"},"#"),t(" CI Testing speed upgrade")],-1),Z=e("img",{src:m},null,-1),z={href:"https://github.com/DeeDeeG",target:"_blank",rel:"noopener noreferrer"},X={href:"https://github.com/pulsar-edit/pulsar/pull/492",target:"_blank",rel:"noopener noreferrer"},$=e("h2",{id:"backend-webhook",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#backend-webhook","aria-hidden":"true"},"#"),t(" Backend webhook")],-1),ee=e("img",{src:g},null,-1),te={href:"https://github.com/confused-Techie",target:"_blank",rel:"noopener noreferrer"},ne={href:"https://github.com/pulsar-edit/package-backend/pull/156",target:"_blank",rel:"noopener noreferrer"},se={href:"https://discordapp.com/channels/992103415163396136/1045131651748999239",target:"_blank",rel:"noopener noreferrer"},ae=e("h2",{id:"package-website-readme-links-overhaul",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#package-website-readme-links-overhaul","aria-hidden":"true"},"#"),t(" Package website readme links overhaul")],-1),oe=e("img",{src:f},null,-1),ie={href:"https://github.com/pulsar-edit/package-frontend/pull/96",target:"_blank",rel:"noopener noreferrer"},re={href:"https://github.com/confused-Techie",target:"_blank",rel:"noopener noreferrer"},le=e("code",null,"package.json",-1),ce={href:"https://docs.npmjs.com/cli/v9/configuring-npm/package-json#people-fields-author-contributors",target:"_blank",rel:"noopener noreferrer"},he=a(`
{
+  "author": "Barney Rubble <b@rubble.com> (http://barnyrubble.tumblr.com/)"
+}
+

Previously if this was not populated fully with all three options then the author simply wouldn't display. This fixes that problem by allowing the optional fields to be omitted (actually this is taken a step further in our implementation - even name can be optional)

`,2),pe={href:"https://docs.npmjs.com/cli/v9/configuring-npm/package-json#repository",target:"_blank",rel:"noopener noreferrer"},de=e("code",null,"repository",-1),ue=e("code",null,"package.json",-1),me=e("code",null,"authorName/repoName",-1),ge=e("code",null,"atom.io",-1),fe={href:"https://github.blog/2022-06-08-sunsetting-atom/",target:"_blank",rel:"noopener noreferrer"},ke=e("img",{src:k},null,-1),be=e("code",null,"https://atom.io/packages/...",-1),ve=e("code",null,"https://web.pulsar-edit.dev/packages/...",-1),_e={href:"https://github.com/pulsar-edit/package-frontend/issues/95",target:"_blank",rel:"noopener noreferrer"},we=e("ol",null,[e("li",null,'these links are all effectively "dead" so currently serve no useful purpose and'),e("li",null,[t("they are "),e("em",null,"already"),t(" being redirected from the original link.")])],-1),ye=e("p",null,"We feel that this is an entirely benign change even though it does redirect from the author's originally intended link to another.",-1),xe=e("code",null,"atom.io",-1),je={href:"https://web.archive.org/web/20221215003438/https://flight-manual.atom.io/",target:"_blank",rel:"noopener noreferrer"},Te=e("img",{src:b},null,-1),Ie=e("p",null,[t("Our last change is that relative links are now preserved against the repository of the package. For example "),e("code",null,"./LICENSE.md"),t(" => "),e("code",null,"https://github.com/pulsar-edit/package-frontend/LICENSE.md"),t(".")],-1),Ee=e("p",null,"These were some much needed changes which should hopefully improve the overall experience of using the website.",-1),Se=e("h2",{id:"decoupling-http-handling-for-the-package-backend",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#decoupling-http-handling-for-the-package-backend","aria-hidden":"true"},"#"),t(" Decoupling http handling for the package backend")],-1),Oe={href:"https://github.com/confused-Techie",target:"_blank",rel:"noopener noreferrer"},Ae={href:"https://github.com/pulsar-edit/package-backend/pull/171",target:"_blank",rel:"noopener noreferrer"},Ne=a(`

Currently the handling for HTTP and logic is all mixed together in the backend which means that nearly every function within the backend has a signature like:

async function getPackages(res, req) {...}
+

Where res is the response object as provided by ExpressJS and req is the request object provided by ExpressJS.

This also means when a function from the backend needs to return, it'll have a signature like:

// On Success
+res.status(200).json(data);
+logger.httpLog(req, res);
+
+// On Failure
+await common.handleError(req, res, err);
+return;
+

Which has a few downsides; when testing a function to the backend a fake Response and Request object must be made that also matches the ones provided by ExpressJS and those objects have to be monitored for the functions behavior, or otherwise the API has to be tested as just that, an API by calling the ExpressJS module itself then checking the api returns for behavior.

This also means that any new developers have to be familiar with ExpressJS to contribute and each function has to know the exact HTTP Status codes that have to be returned and in some circumstances concern themselves with header fields to set etc.

The planned changes are to allow each function to return just a serverStatusObject (which is used frequently within the backend) so we change that same signature from above to:

// On Success
+return {
+	ok: true,
+	content: data,
+};
+
+// On Failure
+return {
+	ok: false,
+	content: err,
+};
+

This has the upside of letting only the code that retrieves API requests deal with responding to them. The rest of the backend can work with just normal functions, without a care in the world about HTTP handling. This change lowers the amount of knowledge needed to contribute, so we hope that more people might be encouraged to get involved. As a bonus, it makes the code much easier to test.


`,11),Pe={href:"https://pulsar-edit.dev/community.html",target:"_blank",rel:"noopener noreferrer"};function qe(Le,Ce){const n=c("ExternalLinkIcon");return i(),r("div",null,[_,l(" more "),w,y,e("p",null,[t("Just want to first mention that we have a "),e("a",x,[t("new survey"),s(n)]),t(" up, we would like some feedback from our community on a few aspects of the project so if you haven't answered this already then please have a look, every response is useful to us and we greatly appreciated.")]),j,T,e("p",null,[t("So this is a change we "),e("a",I,[t("have in the works"),s(n)]),t(" thanks to "),e("a",E,[t("@confused-techie"),s(n)]),t(", to upgrade Pulsar's iconography by updating the version of "),e("a",S,[t("Octicons"),s(n)]),t(". The version being used currently in Pulsar is "),O,t(" old (v4.4.0 - from 2016!) and there have been many, many updates since then with a whole bunch of new icons and improvements (now on version v18.3.0).")]),A,N,P,q,L,e("p",null,[t("One of our others is a plan to hopefully modernise the UI, for example we have a bunch of new icons we can use for extending the set of file icons. You can see a couple of potential implementations in the below picture compared to the original. We actually have a survey up right now to gauge the community response to this so if you haven't already then please consider giving us "),e("a",C,[t("your opinion"),s(n)]),t(".")]),J,D,e("p",null,[t("If you want to read more details about what had to be done for the implementation then you can read the "),e("a",H,[t("document"),s(n)]),t(" that accompanies the PR that details what was done and how to maintain it in the future.")]),U,W,e("p",null,[t("Symbol-based navigation in Pulsar currently depends on "),e("a",F,[t("ctags"),s(n)]),t(". It's versatile and can be configured to support lots of different source code files, but it reads from disk and doesn't work well with files that have been modified a lot since the last save. It also doesn't work "),M,t(" on brand-new, unsaved files, or with languages that it hasn't been configured for.")]),e("p",null,[e("a",R,[t("@savetheclocktower"),s(n)]),t(" has been working on some improvements for this. For example, Tree-sitter parsers are quite good at this sort of code analysis, and can identify the important parts of a source code file with "),e("a",V,[t("a simple query file"),s(n)]),t(".")]),e("p",null,[e("a",B,[t("Language servers"),s(n)]),t(" are another potential source of symbol information. Some language servers are even capable of supplying project-wide symbol information; this would improve other "),G,t(" responsibilities, like the \u201CGo to Declaration\u201D\xA0command.")]),Y,Q,K,Z,e("p",null,[e("a",z,[t("@DeedeeG"),s(n)]),t(" has "),e("a",X,[t("made an update"),s(n)]),t(" to our continuous integration configuration by caching and restoring the dependencies which ends up saving us a huge amount of time, possibly over an hour each run! (Pulsar has to build and test an awful lot of packages on each run so it is a long process). This should hopefully make life just that bit easier on the development side of things.")]),$,ee,e("p",null,[e("a",te,[t("@confused-techie"),s(n)]),t(" has been busy adding yet another "),e("a",ne,[t("new feature"),s(n)]),t(" to our package backend, this time it is a new webhook that allows us to publish messages and notifications when package authors publish and update their packages. You can check this out in action in our "),e("a",se,[t("backend-notifications"),s(n)]),t(" channel on Discord.")]),ae,oe,e("p",null,[t("Now some "),e("a",ie,[t("changes to our package website"),s(n)]),t(". There are a few separate, but related, changes by "),e("a",re,[t("@confused-techie"),s(n)]),t(" here that involve improving our package website to overhaul some of the issues we have with readme links.")]),e("p",null,[t("The first change adds shorthand string author assignment in the "),le,t(" file of a package. The "),e("a",ce,[t("npm package specification"),s(n)]),t(" allows the three author properties to be combined into a single string e.g.")]),he,e("p",null,[t("In a similar vein we also now support "),e("a",pe,[t("shortcut syntax"),s(n)]),t(" for the "),de,t(" field of the "),ue,t(". For example writing "),me,t(" wasn't working so we have provided support for this.")]),e("p",null,[t("We noticed that many package authors would link to other community packages in their readmes in order to show other packages that work with or complement their package. Unfortunately as these point to the old "),ge,t(" website it means that every single link simply redirects to the "),e("a",fe,[t("sunsetting Atom"),s(n)]),t(" page. This is really not helpful as it entirely breaks the embedded links, sure there are workaround, people could look at the link and work out what packages it links to but this is a hassle when all you want to do is click a link.")]),ke,e("p",null,[t("Therefore in order to solve this a feature has been added that takes these "),be,t(" links and transforms them to "),ve,t(" so that the author's original intent for package linking is not lost. We did have a "),e("a",_e,[t("conversation"),s(n)]),t(' as to whether it was "right" to make these changes but we reasoned that:')]),we,ye,e("p",null,[t("Our next change also involves dead Atom links, in this case links to the Atom Flight Manual which has been taken down along with the rest of the "),xe,t(" sites. Again we are performing a redirection here but this time to the archived version of the website found on "),e("a",je,[t("Internet Archive"),s(n)]),t(". This keeps the correct link handling and means that the documentation the package author was intending to link to is still available.")]),Te,Ie,Ee,Se,e("p",null,[t("Lastly we have yet another "),e("a",Oe,[t("@confused-techie"),s(n)]),t(" update in the works, this time "),e("a",Ae,[t("improvements to the package backend"),s(n)]),t(" to make developing the backend more accessible to new contributors as well making tasks such as testing easier.")]),Ne,e("p",null,[t("And that just about wraps things up for this month's updates. As ever if you want to get more involved the community then feel free to join in on our various "),e("a",Pe,[t("social channels"),s(n)]),t(". Hope to see you again this time next month!")])])}const De=o(v,[["render",qe],["__file","20230501-Daeraxa-MayUpdate.html.vue"]]);export{De as default}; diff --git a/assets/20230516-Daeraxa-v1.105.0.html.a8dc50d7.js b/assets/20230516-Daeraxa-v1.105.0.html.a8dc50d7.js new file mode 100644 index 0000000000..621772e011 --- /dev/null +++ b/assets/20230516-Daeraxa-v1.105.0.html.a8dc50d7.js @@ -0,0 +1 @@ +import{_ as n,o as a,c as l,a as e,b as t,d as r,e as s,f as i,r as u}from"./app.0e1565ce.js";const d={},c={href:"https://github.com/pulsar-edit/pulsar/releases/tag/v1.105.0",target:"_blank",rel:"noopener noreferrer"},p=i('

What is new in 1.105.0?

This newest release of Pulsar contains many changes, from new features, to security patches, to testing improvements, this release has it all, and we hope it shows!

A big milestone for this release, is since our initial fork from Atom, we have now created fully green (passing) tests! This hopefully means we can iterate new changes, both fixes and new features, faster than ever before!

Also on this release we ensured to put a focus on solving the issues reported by our wonderful community such as resolving an issue that would prevent installation of Pulsar on RPM systems, fixing localization issues in settings-view when browsing packages, or addressing memory leak issues within second-mate.

Even better this release also comes with some brand new features and improvements to existing ones! Such as the addition of new community package activation hooks, adding a new bookmarks service to the ServiceHub, and updating the internal typescript used to support advancements within that language for community packages to take advantage of.

But this summary can't possibly include everything, make sure to take a look through the changelog below to see everything this release has to offer!

As always, we want to say a huge thanks to everyone that contributes to Pulsar, from the much appreciated donations, the critical issue reports, or amazing pull requests we receive. We appreciate every single one of you, and couldn't do any of this without you!

Till next time, happy coding, and see you among the stars!


Pulsar

',12),h=e("code",null,"...:uri-opened",-1),f=e("code",null,"...:file-name-opened",-1),m={href:"https://github.com/pulsar-edit/pulsar/pull/518",target:"_blank",rel:"noopener noreferrer"},_=e("code",null,"settings-view",-1),g={href:"https://github.com/pulsar-edit/pulsar/pull/526",target:"_blank",rel:"noopener noreferrer"},b={href:"https://github.com/pulsar-edit/pulsar/pull/525",target:"_blank",rel:"noopener noreferrer"},k={href:"https://github.com/pulsar-edit/pulsar/pull/529",target:"_blank",rel:"noopener noreferrer"},w={href:"https://github.com/pulsar-edit/pulsar/pull/530",target:"_blank",rel:"noopener noreferrer"},v={href:"https://github.com/pulsar-edit/pulsar/pull/528",target:"_blank",rel:"noopener noreferrer"},y={href:"https://github.com/pulsar-edit/pulsar/pull/523",target:"_blank",rel:"noopener noreferrer"},x=e("code",null,"second-mate",-1),A={href:"https://github.com/pulsar-edit/pulsar/pull/524",target:"_blank",rel:"noopener noreferrer"},T={href:"https://github.com/pulsar-edit/pulsar/pull/493",target:"_blank",rel:"noopener noreferrer"},B={href:"https://github.com/pulsar-edit/pulsar/pull/515",target:"_blank",rel:"noopener noreferrer"},P={href:"https://github.com/pulsar-edit/pulsar/pull/517",target:"_blank",rel:"noopener noreferrer"},F={href:"https://github.com/pulsar-edit/pulsar/pull/513",target:"_blank",rel:"noopener noreferrer"},R=e("code",null,"autocomplete-atom-api",-1),z={href:"https://github.com/pulsar-edit/pulsar/pull/476",target:"_blank",rel:"noopener noreferrer"},C={href:"https://github.com/pulsar-edit/pulsar/pull/505",target:"_blank",rel:"noopener noreferrer"},D={href:"https://github.com/pulsar-edit/pulsar/pull/502",target:"_blank",rel:"noopener noreferrer"},S={href:"https://github.com/pulsar-edit/pulsar/pull/477",target:"_blank",rel:"noopener noreferrer"},I={href:"https://github.com/pulsar-edit/pulsar/pull/501",target:"_blank",rel:"noopener noreferrer"},V={href:"https://github.com/pulsar-edit/pulsar/pull/495",target:"_blank",rel:"noopener noreferrer"},q={href:"https://github.com/pulsar-edit/pulsar/pull/456",target:"_blank",rel:"noopener noreferrer"},E={href:"https://github.com/pulsar-edit/pulsar/pull/496",target:"_blank",rel:"noopener noreferrer"},M=e("code",null,"nslog",-1),N={href:"https://github.com/pulsar-edit/pulsar/pull/494",target:"_blank",rel:"noopener noreferrer"},H={href:"https://github.com/pulsar-edit/pulsar/pull/469",target:"_blank",rel:"noopener noreferrer"},W={href:"https://github.com/pulsar-edit/pulsar/pull/481",target:"_blank",rel:"noopener noreferrer"},j={href:"https://github.com/pulsar-edit/pulsar/pull/480",target:"_blank",rel:"noopener noreferrer"},G=e("code",null,"typscript-simple",-1),L=e("code",null,"typescript",-1),U={href:"https://github.com/pulsar-edit/pulsar/pull/458",target:"_blank",rel:"noopener noreferrer"},J={href:"https://github.com/pulsar-edit/pulsar/pull/492",target:"_blank",rel:"noopener noreferrer"},K=e("h3",{id:"notifications",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#notifications","aria-hidden":"true"},"#"),t(" notifications")],-1),O={href:"https://github.com/pulsar-edit/notifications/pull/1",target:"_blank",rel:"noopener noreferrer"},Q={href:"https://github.com/pulsar-edit/notifications/pull/2",target:"_blank",rel:"noopener noreferrer"},X={href:"https://github.com/pulsar-edit/notifications/pull/3",target:"_blank",rel:"noopener noreferrer"},Y={href:"https://github.com/pulsar-edit/notifications/pull/4",target:"_blank",rel:"noopener noreferrer"},Z={href:"https://github.com/pulsar-edit/notifications/pull/5",target:"_blank",rel:"noopener noreferrer"},$={href:"https://github.com/pulsar-edit/notifications/pull/6",target:"_blank",rel:"noopener noreferrer"};function ee(te,oe){const o=u("ExternalLinkIcon");return a(),l("div",null,[e("p",null,[t("Welcome to a rad new release, Pulsar 1.105.0 is "),e("a",c,[t("available now!"),r(o)])]),s(" more "),p,e("ul",null,[e("li",null,[t("Added: Add new "),h,t(" && "),f,t(" Package Activation Hook "),e("a",m,[t("@confused-Techie"),r(o)])]),e("li",null,[t("Fixed: Properly localize Download/Stargazer Counts within "),_,t(),e("a",g,[t("@confused-Techie"),r(o)])]),e("li",null,[t("Added: Add bookmarks service for consumption by other packages "),e("a",b,[t("@savetheclocktower"),r(o)])]),e("li",null,[t("Added: Bundle notifications "),e("a",k,[t("@mauricioszabo"),r(o)])]),e("li",null,[t("Fixed: Fix Ripgrep download issues in CirrusCI "),e("a",w,[t("@confused-Techie"),r(o)])]),e("li",null,[t("Removed: Revert Incorrect Commit "),e("a",v,[t("@confused-Techie"),r(o)])]),e("li",null,[t("Fixed: Making CI green, hopefully "),e("a",y,[t("@mauricioszabo"),r(o)])]),e("li",null,[t("Bumped: Bump "),x,t(" to 96866771 "),e("a",A,[t("@savetheclocktower"),r(o)])]),e("li",null,[t("Removed: Remove cache of incompatible native packages "),e("a",T,[t("@mauricioszabo"),r(o)])]),e("li",null,[t("Added: Simplify and bundle fuzzy-finder "),e("a",B,[t("@mauricioszabo"),r(o)])]),e("li",null,[t("Added: Bundle find and replace "),e("a",P,[t("@mauricioszabo"),r(o)])]),e("li",null,[t("Added: Bundle tree view "),e("a",F,[t("@confused-Techie"),r(o)])]),e("li",null,[t("Added: Bundle "),R,t(),e("a",z,[t("@confused-Techie"),r(o)])]),e("li",null,[t("Added: Add FPM option to stop rpm buildid clash "),e("a",C,[t("@Daeraxa"),r(o)])]),e("li",null,[t("Bumped: chore(deps): update dependency minimist [security] "),e("a",D,[t("@renovate"),r(o)])]),e("li",null,[t("Fixed: Disable Failing Tests "),e("a",S,[t("@confused-Techie"),r(o)])]),e("li",null,[t("Bumped: chore(deps): update dependency ajv to 6.12.3 [security] "),e("a",I,[t("@renovate"),r(o)])]),e("li",null,[t("Bumped: chore(deps): update dependency async to 3.2.2 [security] "),e("a",V,[t("@renovate"),r(o)])]),e("li",null,[t('Added: Add "icon only" class to settings view icon '),e("a",q,[t("@Daeraxa"),r(o)])]),e("li",null,[t("Bumped: chore(deps): update dependency minimatch [security] "),e("a",E,[t("@renovate"),r(o)])]),e("li",null,[t("Removed: Remove "),M,t(" dependency "),e("a",N,[t("@mauricioszabo"),r(o)])]),e("li",null,[t("Added: Setup Renovate "),e("a",H,[t("@confused-Techie"),r(o)])]),e("li",null,[t("Fixed: Don't mark diff ranges on a destroyed buffer "),e("a",W,[t("@savetheclocktower"),r(o)])]),e("li",null,[t("Added: First Architectural Design Records "),e("a",j,[t("@mauricioszabo"),r(o)])]),e("li",null,[t("Bumped: use pular's "),G,t(" fork, which bumps "),L,t(" to 5.0.3 "),e("a",U,[t("@Meadowsys"),r(o)])]),e("li",null,[t("Added: CI: cache and restore dependencies, plus skip rebuilding all over the place (saves a lot of time) "),e("a",J,[t("@DeeDeeG"),r(o)])])]),K,e("ul",null,[e("li",null,[t("Fixed: Cleanup and rename "),e("a",O,[t("@Sertonix"),r(o)])]),e("li",null,[t("Added: reject promise with Error instance "),e("a",Q,[t("@Sertonix"),r(o)])]),e("li",null,[t("Added: Add our Testing Action "),e("a",X,[t("@confused-Techie"),r(o)])]),e("li",null,[t("Fixed: Change atom strings to pulsar "),e("a",Y,[t("@mdibella-dev"),r(o)])]),e("li",null,[t("Bumped: Bump to v3.2 of action-pulsar-dependency "),e("a",Z,[t("@confused-Techie"),r(o)])]),e("li",null,[t("Fixed: Fix all Tests "),e("a",$,[t("@confused-Techie"),r(o)])])])])}const ne=n(d,[["render",ee],["__file","20230516-Daeraxa-v1.105.0.html.vue"]]);export{ne as default}; diff --git a/assets/20230516-Daeraxa-v1.105.0.html.bf43148e.js b/assets/20230516-Daeraxa-v1.105.0.html.bf43148e.js new file mode 100644 index 0000000000..0d6d834c40 --- /dev/null +++ b/assets/20230516-Daeraxa-v1.105.0.html.bf43148e.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-dbbf0e92","path":"/blog/20230516-Daeraxa-v1.105.0.html","title":"Welcome to a rad new release, Pulsar 1.105.0 is available now!","lang":"en-US","frontmatter":{"title":"Welcome to a rad new release, Pulsar 1.105.0 is available now!","author":"Daeraxa","date":"2023-05-15T00:00:00.000Z","category":["dev"],"tag":["release"]},"excerpt":"

Welcome to a rad new release, Pulsar 1.105.0 is available now!

\\n","headers":[{"level":3,"title":"Pulsar","slug":"pulsar","link":"#pulsar","children":[]},{"level":3,"title":"notifications","slug":"notifications","link":"#notifications","children":[]}],"git":{"updatedTime":1684353742000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":2},{"name":"DeeDeeG","email":"DeeDeeG@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":3.13,"words":939},"filePathRelative":"blog/20230516-Daeraxa-v1.105.0.md","localizedDate":"May 15, 2023"}');export{e as data}; diff --git a/assets/20230525-Daeraxa-Survey2-Results.html.628207e7.js b/assets/20230525-Daeraxa-Survey2-Results.html.628207e7.js new file mode 100644 index 0000000000..84fd83f041 --- /dev/null +++ b/assets/20230525-Daeraxa-Survey2-Results.html.628207e7.js @@ -0,0 +1 @@ +import{_ as s,o as i,c as r,e as h,a as t,b as e,d as a,f as n,r as l}from"./app.0e1565ce.js";const c="/assets/survey2-icons.3bc9eaf1.png",u="/assets/survey2-q1.5103b594.png",d="/assets/survey2-q2.fbf62014.png",p="/assets/survey2-q3.2fdd208f.png",m={},g=t("p",null,"Here we have the results and comments of our second community survey!",-1),f=t("p",null,"Thanks to everyone who took part in this survey, we got some great responses, again showing how effective this style of community engagement can be.",-1),b=t("p",null,"The reason for this survey was to gauge some early feedback on a bunch of changes we are looking at making in various areas of the project. We really need to make sure that it is worth making these changes, the last thing we want is to make changes that nobody actually wants that end up making things worse.",-1),y={href:"https://pulsar-edit.dev/blog/20230326-Daeraxa-Survey1-Results.html",target:"_blank",rel:"noopener noreferrer"},w=t("h2",{id:"iconography",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#iconography","aria-hidden":"true"},"#"),e(" Iconography")],-1),k={href:"https://primer.style/design/foundations/icons/",target:"_blank",rel:"noopener noreferrer"},v=n('

survey2-icons

We asked which one was preferred out of these three mock-ups and for any additional comments people would like to make on it.

survey2-q1

The outcome of this is fairly clear in that there is a clear preference for the filled icons. This is something we still need to work on (the pictures were only mockups) so it probably won't be in the next release but this gives a us a good indication of the direction we should go with this.

Comments

There are a few comments here I've picked out to make comment on:

Wish the icons would be like VSCode icons extension, which react to something if you name a file a certain word, like (images, root, etc) or language images like put the language images in the file, idk how to explain

As long as is isn't the outline one I'll be happy. Side note, it would be cool if the file icons showed the kind of file.

Wondering why executable files (shell scripts) look the same as plain text files

',9),_={href:"https://github.com/file-icons",target:"_blank",rel:"noopener noreferrer"},q={href:"https://web.pulsar-edit.dev/packages/search?q=file+icons",target:"_blank",rel:"noopener noreferrer"},I=t("blockquote",null,[t("p",null,"Both new versions are fine, so if possible in the future, make a setting where you can choose?")],-1),x={href:"https://pulsar-edit.dev/docs/launch-manual/sections/core-hacking/#creating-a-theme",target:"_blank",rel:"noopener noreferrer"},T=t("code",null,"Styleguide: Show",-1),A=n('

Documentation Site Upgrade

We have plans to overhaul our documentation (both the site/framework and the structure of the documentation). For this we were just after some general feedback on what people like or dislike about the current docs and how it compares to other project documentation.

Whilst I can't address every comment individually, I've picked out a few to address as they are either a common question or a great point that I think needs to be addressed:

API Docs

api chapter needed

Whenever I've tried to use the documentation it seems to be mostly TODO's. So, I'd say that getting the content back to where it was under Atom is more important than fine tuning layout and structure (especially the API documentation).

The API, that's the most important part of the Atom project, is not accessible. I would love to have an accessible version soon, and one that is fully searchable, possibly by showing the classes and methods as more relevant and a "full text search" also present

So the API stuff has been a little more challenging in terms of the documentation migration from the flight manual. It wasn't actually part of the flight manual but was generated from the Atom team's own API tooling which output in HTML. There were so many discussions about potential API changes and migrating to a more used/supported API docs generation tool (JSDocs) that this got left behind in fear of spending a lot of time getting it ready only to have no good way to automatically update it.
The good news is that the JSDoc work is well on its way so we should be able to get this added before too long.

Layout

Having everything as one huge page is a bit much. Maybe make the subchapters individual pages?

I think everything in Launch Manual should be moved one level up

As far as I can see this Launch Manual navigation level is not needed and would clean things up and make it more obvious where things are located by removing this level. At this time I'm not certain what else to provide feedback on.

We agree with these points and this is one of the major areas that is spurring on this rework. We don't want to do anything drastic but we do plan to make navigation a little better and less "monolithic".

Videos

Video Tutorials

',15),S={href:"https://www.youtube.com/@htmltim/videos",target:"_blank",rel:"noopener noreferrer"},W=n('

We also had a bunch of comments saying they liked the current docs so we also need to take that into account rather than just going crazy and overhauling everything.

Community package publishing

Currently, in order to publish a package to the Pulsar package registry it is a requirement to make your package available in a GitHub repository. We were after info to know if this is something that is stopping any of our community from publishing packages.

survey2-q2

An interesting almost 50:50 split here, it makes us happy to see so many people are interested in creating packages so we will definitely take that into consideration with our documentation and processes in order to make the publishing and maintenance aspect as good as possible. I can see us asking more questions on the package development experience in the future!

survey2-q3

So this was quite enlightening. It is good to know that most people aren't being put off by the current requirement but it is also great to find out that our plans to support other forges or services are in demand. This was very much a question designed to find out if we were a) stopping people creating packages and b) making good use of our resources by looking to expand to other platforms.

Summary

So thank you again to everyone who took part in this survey, it is fantastic to get so much community engagement on the project. Not only does it help us make informed decisions but getting comments from people clearly passionate and interested in the project is fantastic. It can be easy to get caught in a echo chamber so it is really important to get input from people who may not be as present on our various social platforms but still want to have their voice heard.

',9),P={href:"https://github.com/pulsar-edit/.github/tree/main/surveys/20230524-ProjectFeedback",target:"_blank",rel:"noopener noreferrer"},j=t("p",null,"I don't know when we will have a new survey or what the topic will be but you can be fairly sure there will be one!",-1);function D(V,C){const o=l("ExternalLinkIcon");return i(),r("div",null,[g,h(" more "),f,b,t("p",null,[e("Honestly there isn't much more to say as there wasn't really an overarching theme for this one like there was with our "),t("a",y,[e("first survey"),a(o)]),e(" so let's just get on with it shall we?")]),w,t("p",null,[e("Our first question was asking about upgrading our icons in Pulsar. We are updating our "),t("a",k,[e("Octicons"),a(o)]),e(" version bundled with Pulsar to take advantage of changes made between v4.4.0 from 2016 to the current v18.3.0. As part of this we want to explore actually using these new icons in our default UI theme. The questions below are voting on preference between two styles of the new icons against the current theme:")]),v,t("p",null,[e("These all seem to be on the concept of something like "),t("a",_,[e("file-icons"),a(o)]),e(". The Octicons font simply doesn't have support for every file icon (although it does add some extra ones we would like to include such as icons to indicate binary files or specific types of config file) so we won't be able to add this functionality in this update. The good news is that file icons can be added via a number of "),t("a",q,[e("community packages"),a(o)]),e(".")]),I,t("p",null,[e("We probably won't ship a theme for both but the outline icons will be included in the font so it would simply be a case of "),t("a",x,[e("creating a theme"),a(o)]),e(" to use the outlined ones instead of the default. All the icons will be shown in the inbuilt style guide "),T,e(' so you can easily pick and choose what ones you want. We may however look at creating a default "legacy" theme that uses the original Octicon icons as they are in Pulsar right now.')]),A,t("p",null,[e("I won't rule anything out but definitely check out "),t("a",S,[e("HTML Tim's YouTube Channel"),a(o)]),e(". He has a ton of fantastic videos on Pulsar itself, how to get the best out of it for different types of languages and info on community packages.")]),W,t("p",null,[e("As always you can find all the raw data (all checked for any personal or identifiable data) from the survey on our "),t("a",P,[e("organization repo"),a(o)]),e(".")]),j])}const L=s(m,[["render",D],["__file","20230525-Daeraxa-Survey2-Results.html.vue"]]);export{L as default}; diff --git a/assets/20230525-Daeraxa-Survey2-Results.html.af46184a.js b/assets/20230525-Daeraxa-Survey2-Results.html.af46184a.js new file mode 100644 index 0000000000..2e6528d9a1 --- /dev/null +++ b/assets/20230525-Daeraxa-Survey2-Results.html.af46184a.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-aebfc812","path":"/blog/20230525-Daeraxa-Survey2-Results.html","title":"Survey Results: Project feedback","lang":"en-US","frontmatter":{"title":"Survey Results: Project feedback","author":"Daeraxa","date":"2023-05-25T00:00:00.000Z","category":["survey"],"tag":["community","feedback"]},"excerpt":"

Here we have the results and comments of our second community survey!

\\n","headers":[{"level":2,"title":"Iconography","slug":"iconography","link":"#iconography","children":[{"level":3,"title":"Comments","slug":"comments","link":"#comments","children":[]}]},{"level":2,"title":"Documentation Site Upgrade","slug":"documentation-site-upgrade","link":"#documentation-site-upgrade","children":[{"level":3,"title":"API Docs","slug":"api-docs","link":"#api-docs","children":[]},{"level":3,"title":"Layout","slug":"layout","link":"#layout","children":[]},{"level":3,"title":"Videos","slug":"videos","link":"#videos","children":[]}]},{"level":2,"title":"Community package publishing","slug":"community-package-publishing","link":"#community-package-publishing","children":[]},{"level":2,"title":"Summary","slug":"summary","link":"#summary","children":[]}],"git":{"updatedTime":1684974761000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":2}]},"readingTime":{"minutes":4.9,"words":1469},"filePathRelative":"blog/20230525-Daeraxa-Survey2-Results.md","localizedDate":"May 25, 2023"}');export{e as data}; diff --git a/assets/20230601-Daeraxa-JuneUpdate.html.5770013f.js b/assets/20230601-Daeraxa-JuneUpdate.html.5770013f.js new file mode 100644 index 0000000000..91eda23c35 --- /dev/null +++ b/assets/20230601-Daeraxa-JuneUpdate.html.5770013f.js @@ -0,0 +1 @@ +const e=JSON.parse(`{"key":"v-54f2f18e","path":"/blog/20230601-Daeraxa-JuneUpdate.html","title":"Community Update","lang":"en-US","frontmatter":{"title":"Community Update","author":"Daeraxa","date":"2023-06-01T00:00:00.000Z","category":["news","log"],"tag":["update"]},"excerpt":"

What's this? No... it can't be. But it is! It's the newest instalment of the Pulsar Community Update!

\\n","headers":[{"level":2,"title":"Community package feature detection","slug":"community-package-feature-detection","link":"#community-package-feature-detection","children":[]},{"level":2,"title":"Tree-sitter updates are live!","slug":"tree-sitter-updates-are-live","link":"#tree-sitter-updates-are-live","children":[]},{"level":2,"title":"GitHub Discussions reorganisation","slug":"github-discussions-reorganisation","link":"#github-discussions-reorganisation","children":[]},{"level":2,"title":"Community spotlight","slug":"community-spotlight","link":"#community-spotlight","children":[]}],"git":{"updatedTime":1685607621000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":4}]},"readingTime":{"minutes":7.95,"words":2385},"filePathRelative":"blog/20230601-Daeraxa-JuneUpdate.md","localizedDate":"June 1, 2023"}`);export{e as data}; diff --git a/assets/20230601-Daeraxa-JuneUpdate.html.e4da287c.js b/assets/20230601-Daeraxa-JuneUpdate.html.e4da287c.js new file mode 100644 index 0000000000..e637fbadd7 --- /dev/null +++ b/assets/20230601-Daeraxa-JuneUpdate.html.e4da287c.js @@ -0,0 +1 @@ +import{_ as s}from"./detective.e7e89ed0.js";import{_ as i}from"./package.4e7449ae.js";import{_ as r}from"./tree-sitter.6a39e323.js";import{_ as l,o as h,c as u,e as d,a as e,b as t,d as a,f as n,r as c}from"./app.0e1565ce.js";const p="/assets/gh-discussions.62e6b3a4.png",g="/assets/bacadra-project-files.ca067687.png",f="/assets/bacadra-navigation-panel.e144ab63.png",m="/assets/bacadra-pdf-viewer.9f160fe1.png",b={},_=e("p",null,"What's this? No... it can't be. But it is! It's the newest instalment of the Pulsar Community Update!",-1),w=e("h1",{id:"welcome-to-the-june-community-update",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#welcome-to-the-june-community-update","aria-hidden":"true"},"#"),t(" Welcome to the June Community Update")],-1),y={href:"https://pulsar-edit.dev/blog/20230525-Daeraxa-Survey2-Results.html",target:"_blank",rel:"noopener noreferrer"},k={href:"https://github.com/orgs/pulsar-edit/discussions",target:"_blank",rel:"noopener noreferrer"},v=e("p",null,"Enough waffling, lets get on with it!",-1),x=e("h2",{id:"community-package-feature-detection",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#community-package-feature-detection","aria-hidden":"true"},"#"),t(" Community package feature detection")],-1),T=e("p",null,[e("img",{src:s,height:"200"}),e("img",{src:i,height:"200"})],-1),j={href:"https://github.com/confused-Techie",target:"_blank",rel:"noopener noreferrer"},P={href:"https://pulsar-edit.dev/docs/launch-manual/sections/using-pulsar/#snippets",target:"_blank",rel:"noopener noreferrer"},I=e("p",null,"With this we are able to store and use information not explicitly written into the package metadata but actually in the source code of the package itself. This opens a load of possibilities in both Pulsar itself and the package website. For example you could have a filter that explicitly searches only for packages able to provide snippets or all packages that are able to provide a grammar for a particular language to the editor without just returning packages that contain the string you are looking for without the features you want.",-1),S=e("br",null,null,-1),N={href:"https://pulsar-edit.dev/blog/20230401-confused-Techie-PON.html",target:"_blank",rel:"noopener noreferrer"},A=e("code",null,".pon",-1),W=e("li",null,[t("Open my "),e("code",null,"config.pon"),t(" file for editing "),e("ul",null,[e("li",null,[t("In this case Pulsar doesn't have a grammar for PON and "),e("code",null,".pon"),t(" files so shows the language as the default "),e("code",null,"Plain Text"),t(" language")])])],-1),D=e("li",null,'Open the package install pane and search for "PON"',-1),O={href:"https://web.pulsar-edit.dev/packages/search?q=pon",target:"_blank",rel:"noopener noreferrer"},C=e("code",null,"language-pon",-1),H=e("code",null,"Install",-1),q=e("p",null,'This looks like a simple process but just look at how many results came back from searching for "PON". This shows the downside of just looking for packages via simple search terms, you have to trust that:',-1),B=e("ol",null,[e("li",null,"A language package actually exists for your language")],-1),E=e("p",null,"and",-1),U={start:"2"},F={href:"https://web.pulsar-edit.dev/packages/search?q=dockerfile",target:"_blank",rel:"noopener noreferrer"},G=e("code",null,"language-docker",-1),J=n('

This new feature would allow us to avoid such a situation altogether. As we are now able to determine what packages can support a given language or file extension we can instead use a much more helpful process:

This approach takes the guesswork out of it completely and will only show you packages that state they have support for that filetype.

Remember this is only one example, we can extend this same behaviour to snippets and potentially other features in the future which we hope will greatly reduce the amount of guesswork and manual searching that has to be performed.

Our biggest challenge here is that we have thousands of existing packages that all need to have this analysis run on them so this won't necessarily be a quick process as we battle against GitHub's API limits but we want to make sure we have all this data ready before we go live with this feature. For new packages this action will be performed at time of publishing/update so this is a one time only thing.

We feel this is a really exciting addition so stay tuned for more info as an when we start implementing this in various places.

Tree-sitter updates are live!

[1]

',8),M={href:"https://tree-sitter.github.io/tree-sitter/",target:"_blank",rel:"noopener noreferrer"},R={href:"https://github.com/savetheclocktower",target:"_blank",rel:"noopener noreferrer"},V={href:"https://github.com/mauricioszabo",target:"_blank",rel:"noopener noreferrer"},L={href:"https://github.com/pulsar-edit/pulsar/pull/472",target:"_blank",rel:"noopener noreferrer"},z={href:"https://tree-sitter.github.io/tree-sitter/",target:"_blank",rel:"noopener noreferrer"},K={href:"https://tree-sitter.github.io/tree-sitter/#parsers",target:"_blank",rel:"noopener noreferrer"},Q={href:"https://tree-sitter.github.io/tree-sitter/syntax-highlighting#queries",target:"_blank",rel:"noopener noreferrer"},X={href:"https://github.com/tree-sitter/tree-sitter/tree/master/lib/binding_web",target:"_blank",rel:"noopener noreferrer"},Y={href:"https://github.com/tree-sitter/node-tree-sitter",target:"_blank",rel:"noopener noreferrer"},Z={href:"https://pulsar-edit.dev/download.html#rolling-release",target:"_blank",rel:"noopener noreferrer"},$=e("code",null,"Use Modern Tree-Sitter Implementation",-1),ee=n("

With the new system enabled, here are a few things you might notice:

And here are some other things you wouldn\u2019t notice just as a user, but would enjoy the benefits of if you contributed to the Pulsar package ecosystem:

",3),te=e("li",null,"The system for mapping Tree-sitter semantics to syntax highlighting is much more robust, which means that grammar authors now have a number of extra tools at their disposal.",-1),oe={href:"https://tree-sitter.github.io/tree-sitter/syntax-highlighting#queries",target:"_blank",rel:"noopener noreferrer"},ae=e("li",null,[t("The results of a Tree-sitter parse are useful beyond the built-in editor duties of highlighting, indenting, and folding. For instance, a Pulsar package could use Tree-sitter to find logical sections of a document, or to associate an identifier with the line where it was defined (in fact, we\u2019re planning to use some of this information to improve the "),e("code",null,"symbols-view"),t(" package and make it more flexible).")],-1),ne={href:"https://discord.gg/7aEbB9dGRT",target:"_blank",rel:"noopener noreferrer"},se={href:"https://github.com/pulsar-edit/pulsar/issues",target:"_blank",rel:"noopener noreferrer"},ie=e("h2",{id:"github-discussions-reorganisation",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#github-discussions-reorganisation","aria-hidden":"true"},"#"),t(" GitHub Discussions reorganisation")],-1),re=e("img",{src:p,height:"300"},null,-1),le={href:"https://github.com/orgs/pulsar-edit/discussions/172",target:"_blank",rel:"noopener noreferrer"},he=e("p",null,"So feel free to comment on this organisation work (positive or negative - we won't be offended) and let us know if there is anything else we could be doing to improve it.",-1),ue=e("h2",{id:"community-spotlight",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#community-spotlight","aria-hidden":"true"},"#"),t(" Community spotlight")],-1),de=e("strong",null,"@asiloisad",-1),ce={href:"https://github.com/bacadra",target:"_blank",rel:"noopener noreferrer"},pe=e("p",null,[t("To give you an idea of the scale we pulled a few stats from the backend and found that we are looking at 273 updates over 15 packages (bear in mind that this is "),e("em",null,"only"),t(" packages published or updated on the Pulsar Package Repository and doesn't include those that haven't been updated since migration from Atom).")],-1),ge=e("p",null,"Some of the packages I find particularly interesting and useful are:",-1),fe={href:"https://web.pulsar-edit.dev/packages/project-files",target:"_blank",rel:"noopener noreferrer"},me=e("img",{src:g,height:"300"},null,-1),be={href:"https://web.pulsar-edit.dev/packages/navigation-panel",target:"_blank",rel:"noopener noreferrer"},_e=e("img",{src:f,height:"300"},null,-1),we={href:"https://web.pulsar-edit.dev/packages/pdf-viewer",target:"_blank",rel:"noopener noreferrer"},ye=e("img",{src:m,height:"300"},null,-1),ke={href:"https://github.com/orgs/bacadra/repositories?q=&type=all&language=&sort=",target:"_blank",rel:"noopener noreferrer"},ve=e("p",null,[t("This is exactly the kind of thing we were hoping to achieve by continuing this project. We know the atom shutdown did a lot of damage to people's confidence in the editor and many dismissed it entirely, archived all their packages and moved on when they saw the sunsetting notice. I really hope that we get more people who are just as enthusiastic about publishing packages and just generally about getting involved in the project. So thank you again "),e("strong",null,"@asiloisad"),t(" for all the work you have been putting into this and for putting your faith into this project.")],-1),xe=e("hr",null,null,-1),Te={href:"https://pulsar-edit.dev/community.html",target:"_blank",rel:"noopener noreferrer"},je=e("hr",{class:"footnotes-sep"},null,-1),Pe={class:"footnotes"},Ie={class:"footnotes-list"},Se={id:"footnote1",class:"footnote-item"},Ne={href:"https://tree-sitter.github.io/tree-sitter/",target:"_blank",rel:"noopener noreferrer"},Ae=e("a",{href:"#footnote-ref1",class:"footnote-backref"},"\u21A9\uFE0E",-1);function We(De,Oe){const o=c("ExternalLinkIcon");return h(),u("div",null,[_,d(" more "),w,e("p",null,[t("Before we start I just want to I just want to say another big thank you for everyone who took the time to provide feedback in our recent survey for which you can find the results of in a recent "),e("a",y,[t("blog post"),a(o)]),t(". We got a lot of useful information again so if you haven't already then you can see our analysis of the results and answers to some of the questions in the previous link.")]),e("p",null,[t("We have some really exciting news in this month's edition, some automatic detective work on our backend to make life easier for everyone, updates to our "),e("a",k,[t("GitHub Discussions"),a(o)]),t(" a community spotlight on one of our most prolific package maintainers and the long await and much hyped arrival of the modernised Tree-sitter implementation!")]),v,x,T,e("p",null,[e("a",j,[t("@confused-techie"),a(o)]),t(' has been busy developing a new feature to our Pulsar Package Repository backend to add "Feature Detection". What does this mean? Well in this case feature detection refers to the analysis of community packages as and when they are published to the repository in order to collect information on what features the package can provide. For example it can determine if the package is providing '),e("a",P,[t("snippets"),a(o)]),t(" or a language grammar and, most excitingly, what languages and file extensions that grammar is able to support.")]),I,e("p",null,[t("However one of the biggest benefits is being able to use this information in Pulsar. A fairly common task when one first installs Pulsar (or any text editor) is getting it set up for the languages you use. Now, whilst Pulsar does support a large number of common grammars out of the box it can't support everything."),S,t(" So in the below example I open up a "),e("a",N,[t("PON"),a(o)]),t(" ("),A,t(") file and detail the steps needed to get it to display properly.")]),e("ul",null,[W,D,e("li",null,[t("Search through the "),e("a",O,[t("list of returned results"),a(o)]),t(" until I find "),C,t(" which looks like it might do what I want it to do and click "),H,t(".")])]),q,B,E,e("ol",U,[e("li",null,[t('The package is sensibly named and actually has your search keywords in it (for example searching for "Dockerfile" '),e("a",F,[t("returns this"),a(o)]),t(" but the package we might actually want to add dockerfile support is named "),G,t(" which is noticably absent).")])]),J,e("p",null,[t("If you have been following these monthly updates then you know that "),e("a",M,[t("tree-sitter"),a(o)]),t(" has been a common topic and one of our biggest challenges in our goals to bring Pulsar up to date.")]),e("p",null,[t("So thanks to an awful lot of work by "),e("a",R,[t("@savetheclocktower"),a(o)]),t(" and "),e("a",V,[t("@maur\xEDcio szabo"),a(o)]),t(" this project that we have been working on for months has finally hit the rolling releases in an experimental form: "),e("a",L,[t("a modernization of the Tree-sitter grammars"),a(o)]),t(".")]),e("p",null,[t("Atom was the first editor to integrate with "),e("a",z,[t("Tree-sitter"),a(o)]),t(" \u2014 a system designed to deliver fast and accurate parsing of "),e("a",K,[t("a number of programming languages"),a(o)]),t(" for better syntax highlighting than the traditional TextMate-style grammars.")]),e("p",null,[t("But being the first editor to support something new is a double-edged sword; in this case it revealed gaps in Tree-sitter functionality that were "),e("a",Q,[t("addressed in later versions"),a(o)]),t(". However, Atom\u2019s Tree-sitter support never evolved to match, and that\u2019s the situation that Pulsar inherited.")]),e("p",null,[t("Now our hand has been forced: security changes in Electron mean that the existing system needs to be converted from the "),e("a",X,[t("Node bindings"),a(o)]),t(" to the "),e("a",Y,[t("web-tree-sitter bindings"),a(o)]),t(". We have therefore treated this as an opportunity to overhaul the entire Tree-sitter system and bring it to feature parity with other modern editors that are using Tree-sitter.")]),e("p",null,[t("To start using the modern Tree-sitter implementation, first download a "),e("a",Z,[t("rolling release"),a(o)]),t(". Then find the "),$,t(" option in the Core settings pane and enable it. Any languages with built-in Tree-sitter grammars will now use the modern system.")]),ee,e("ul",null,[te,e("li",null,[t("Likewise, the systems for describing code folding and indentation will be much more user-friendly than the corresponding systems for TextMate-style grammars. Instead of having to write long, confusing regular expressions, grammar authors will often just be able to describe these behaviours in just a few lines of "),e("a",oe,[t("query statements"),a(o)]),t(".")]),ae]),e("p",null,[t("With such a large overhaul, there are bound to be bugs. If you\u2019re using the new system and things look or feel worse in any way, I can almost guarantee that it\u2019s not intentional, so please "),e("a",ne,[t("tell us about it on Discord"),a(o)]),t(" or "),e("a",se,[t("file an issue"),a(o)]),t(". If you\u2019re unsure whether it\u2019s the fault of the new Tree-sitter system, try comparing the behaviour with and without the \u201CUse Modern Tree-Sitter Implementation\u201D option enabled.")]),ie,re,e("p",null,[t("We just had a "),e("a",le,[t("revamp of our GitHub Discussions"),a(o)]),t(` which follows a feature that GitHub finally added to allow organisation via sections. This means that we have been able to better separate the various categories into more thematic groups. Hopefully this shows that we do care about and maintain the Discussions area just as much as our other social channels, we don't want people to avoid this area just because it isn't currently as active (chicken or the egg comes to mind here - it won't become more active if people don't use it.). So please, if you want to have a more "long form" discussion or you want a little more of an "asynchronous" type of engagement then Discussions is perfect for that.`)]),he,ue,e("p",null,[t("And finally we want to give a huge shout out this month to "),de,t(" (aka "),e("a",ce,[t("@bacadra"),a(o)]),t(") who has been creating and maintaining a ton of packages for Pulsar with constant updates \u2014 making them by far our most prolific package author and maintainer.")]),pe,ge,e("ul",null,[e("li",null,[e("a",fe,[t("project-files"),a(o)]),t(" - a package for providing better management and navigation of projects within Pulsar including navigation of the project list, recent projects and even adds autocompletion of path names.")])]),me,e("ul",null,[e("li",null,[e("a",be,[t("navigation-panel"),a(o)]),t(" - this provides an awful lot of stuff that is quite difficult to explain in only a few words but essentially it allows you to add custom symbols to your files")])]),_e,e("ul",null,[e("li",null,[e("a",we,[t("pdf-viewer"),a(o)]),t(" - kind of self explanatory but allows you to open up PDFs directly in Pulsar.")])]),ye,e("p",null,[t("There are tons more packages, have a look at their "),e("a",ke,[t("GitHub repos"),a(o)]),t(" for more.")]),ve,xe,e("p",null,[t("And that just about wraps things up for this month's updates. As ever if you want to get more involved the community then feel free to join in on our various "),e("a",Te,[t("social channels"),a(o)]),t(". Hope to see you again this time next month!")]),je,e("section",Pe,[e("ol",Ie,[e("li",Se,[e("p",null,[t("Image from "),e("a",Ne,[t("https://tree-sitter.github.io/tree-sitter/"),a(o)]),t(" - Copyright (c) 2018-2021 Max Brunsfeld "),Ae])])])])])}const Ee=l(b,[["render",We],["__file","20230601-Daeraxa-JuneUpdate.html.vue"]]);export{Ee as default}; diff --git a/assets/20230610-Daeraxa-2kStars.html.b0855c71.js b/assets/20230610-Daeraxa-2kStars.html.b0855c71.js new file mode 100644 index 0000000000..eff70d7cf2 --- /dev/null +++ b/assets/20230610-Daeraxa-2kStars.html.b0855c71.js @@ -0,0 +1 @@ +import{_ as s,o as a,c as n,e as i,a as e,b as o,d as r,r as l}from"./app.0e1565ce.js";const c="/assets/star-history-2023610.47fda7b2.png",u={},h=e("p",null,"We just hit 2000 stars on GitHub!",-1),d=e("p",null,'This is just a quick blog post to say thank you to everyone in our wonderful community who have allowed us to get this far. I know it is "just" a number but 2000 stars shows us just how many people believe in our project.',-1),p={href:"https://github.com/pulsar-edit/pulsar",target:"_blank",rel:"noopener noreferrer"},_={href:"https://star-history.com/",target:"_blank",rel:"noopener noreferrer"},m=e("p",null,[e("img",{src:c,alt:"star-chart"})],-1),f=e("p",null,"So again, a huge thank you to everyone, particularly our wonderful community and our generous donors who make this project possible.",-1);function y(g,k){const t=l("ExternalLinkIcon");return a(),n("div",null,[h,i(" more "),d,e("p",null,[o("It is hard to believe this project is almost nearing a year old. The "),e("a",p,[o("pulsar-edit/pulsar"),r(t)]),o(" repository was created on the 12th of July 2022 and in that time we have already done so much and we look forward to doing so much more.")]),e("p",null,[o("Here is a chart generated on "),e("a",_,[o("star-history.com"),r(t)]),o(" showing our stars over time.")]),m,f])}const b=s(u,[["render",y],["__file","20230610-Daeraxa-2kStars.html.vue"]]);export{b as default}; diff --git a/assets/20230610-Daeraxa-2kStars.html.f63a03a7.js b/assets/20230610-Daeraxa-2kStars.html.f63a03a7.js new file mode 100644 index 0000000000..94f4188f92 --- /dev/null +++ b/assets/20230610-Daeraxa-2kStars.html.f63a03a7.js @@ -0,0 +1 @@ +const a=JSON.parse('{"key":"v-25d26d73","path":"/blog/20230610-Daeraxa-2kStars.html","title":"2k Stars!","lang":"en-US","frontmatter":{"title":"2k Stars!","author":"Daeraxa","date":"2023-06-10T00:00:00.000Z","category":["news"],"tag":["github","stars"]},"excerpt":"

We just hit 2000 stars on GitHub!

\\n","headers":[],"git":{"updatedTime":1686362721000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":0.54,"words":161},"filePathRelative":"blog/20230610-Daeraxa-2kStars.md","localizedDate":"June 10, 2023"}');export{a as data}; diff --git a/assets/20230616-Daeraxa-v1.106.0.html.22d3e3b7.js b/assets/20230616-Daeraxa-v1.106.0.html.22d3e3b7.js new file mode 100644 index 0000000000..85d950cf75 --- /dev/null +++ b/assets/20230616-Daeraxa-v1.106.0.html.22d3e3b7.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-7e31f297","path":"/blog/20230616-Daeraxa-v1.106.0.html","title":"Pulsar v1.106.0: A Focus on Grammars","lang":"en-US","frontmatter":{"title":"Pulsar v1.106.0: A Focus on Grammars","author":"Daeraxa","date":"2023-06-15T00:00:00.000Z","category":["dev"],"tag":["release"]},"excerpt":"

Welcome to our latest release, Pulsar 1.106.0 is available now!

\\n","headers":[{"level":2,"title":"What is new in 1.106.0?","slug":"what-is-new-in-1-106-0","link":"#what-is-new-in-1-106-0","children":[{"level":3,"title":"Changelog","slug":"changelog","link":"#changelog","children":[]},{"level":3,"title":"Pulsar","slug":"pulsar","link":"#pulsar","children":[]},{"level":3,"title":"github","slug":"github","link":"#github","children":[]}]}],"git":{"updatedTime":1686937601000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":3.65,"words":1096},"filePathRelative":"blog/20230616-Daeraxa-v1.106.0.md","localizedDate":"June 15, 2023"}');export{e as data}; diff --git a/assets/20230616-Daeraxa-v1.106.0.html.98178db0.js b/assets/20230616-Daeraxa-v1.106.0.html.98178db0.js new file mode 100644 index 0000000000..de5d85c7bb --- /dev/null +++ b/assets/20230616-Daeraxa-v1.106.0.html.98178db0.js @@ -0,0 +1 @@ +import{_ as a,o as n,c as i,a as e,b as t,d as r,e as s,f as l,r as u}from"./app.0e1565ce.js";const d={},h={href:"https://github.com/pulsar-edit/pulsar/releases/tag/v1.106.0",target:"_blank",rel:"noopener noreferrer"},p=e("h2",{id:"what-is-new-in-1-106-0",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#what-is-new-in-1-106-0","aria-hidden":"true"},"#"),t(" What is new in 1.106.0?")],-1),c=e("p",null,"We have a particularly exciting release for you because this is our first regular release that adds a new feature that we have been hard at work on but more on that later. Of course we still have our usual mix of updates and upgrades such as a whole host of improvements to our Clojure language support and a number of annoying bugs that have been firmly splatted.",-1),m={href:"https://tree-sitter.github.io/tree-sitter/",target:"_blank",rel:"noopener noreferrer"},g={href:"https://pulsar-edit.dev/blog/20230601-Daeraxa-JuneUpdate.html#tree-sitter-updates-are-live",target:"_blank",rel:"noopener noreferrer"},f=e("em",null,"lot",-1),b=e("code",null,"Use Modern Tree-Sitter Implementation",-1),_=e("code",null,"Core",-1),y={href:"https://discord.gg/7aEbB9dGRT",target:"_blank",rel:"noopener noreferrer"},w={href:"https://github.com/pulsar-edit/pulsar/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc",target:"_blank",rel:"noopener noreferrer"},k=l('

Onto our Clojure language updates, lots of this actually ties directly into the new Tree-sitter implementation as Clojure is now supported as a Tree-sitter grammar which means a whole host of new features have been added that are specific to this new implementation. We now properly support block comments, quotes and a bunch of other advanced features. Basically there has never been a better time to pick up learning Clojure in Pulsar!

Don't let these big updates make you assume we aren't also thinking about some smaller scale things, we have a tiny quality of life update to our GitHub package which adds a Log Out option to the package options (Packages > GitHub > Log Out) (previously only available in a somewhat obscure command - github:logout) - just beware that this will remove your token from your system entirely so you will either need to re-enter it or create a new one to log back in.

And of course to finish up we have some annoying bugs which we have now been squashed, for example an issue that prevented images opening correctly (an issue we apparently inherited from Atom) and, whilst not a bug, a fix to solve a less than ideal situation with our new CSS autocomplete implementation to sort the suggestions in a more expected fashion. We have to thank our community for these as these last couple of items as they were brought to our attention (and in one case fixed) by some of our community members.

So that just about wraps it up for another release. As ever a huge thank you to our wonderful community and donors who make this entire project possible.

Till next time, happy coding, and see you among the stars!

A quick note about our missing ARM Linux Binaries Sorry, there are no ARM Linux binaries at time of initial release, due to what we suspect is an issue at our CI provider. Hopefully this will be resolved soon and we can upload some ARM Linux binaries for this release! Thanks for your patience.

Changelog

Pulsar

',11),v={href:"https://github.com/pulsar-edit/pulsar/pull/592",target:"_blank",rel:"noopener noreferrer"},x=e("code",null,"request",-1),A={href:"https://github.com/pulsar-edit/pulsar/pull/474",target:"_blank",rel:"noopener noreferrer"},C={href:"https://github.com/pulsar-edit/pulsar/pull/579",target:"_blank",rel:"noopener noreferrer"},T={href:"https://github.com/pulsar-edit/pulsar/pull/563",target:"_blank",rel:"noopener noreferrer"},j={href:"https://github.com/pulsar-edit/pulsar/pull/584",target:"_blank",rel:"noopener noreferrer"},S={href:"https://github.com/pulsar-edit/pulsar/pull/570",target:"_blank",rel:"noopener noreferrer"},q=e("code",null,"completions.json",-1),F={href:"https://github.com/pulsar-edit/pulsar/pull/552",target:"_blank",rel:"noopener noreferrer"},I={href:"https://github.com/pulsar-edit/pulsar/pull/573",target:"_blank",rel:"noopener noreferrer"},B={href:"https://github.com/pulsar-edit/pulsar/pull/566",target:"_blank",rel:"noopener noreferrer"},R={href:"https://github.com/pulsar-edit/pulsar/pull/567",target:"_blank",rel:"noopener noreferrer"},U={href:"https://github.com/pulsar-edit/pulsar/pull/553",target:"_blank",rel:"noopener noreferrer"},D={href:"https://github.com/pulsar-edit/pulsar/pull/561",target:"_blank",rel:"noopener noreferrer"},E={href:"https://github.com/pulsar-edit/pulsar/pull/504",target:"_blank",rel:"noopener noreferrer"},L={href:"https://github.com/pulsar-edit/pulsar/pull/472",target:"_blank",rel:"noopener noreferrer"},M={href:"https://github.com/pulsar-edit/pulsar/pull/537",target:"_blank",rel:"noopener noreferrer"},z={href:"https://github.com/pulsar-edit/pulsar/pull/79",target:"_blank",rel:"noopener noreferrer"},G={href:"https://github.com/pulsar-edit/pulsar/pull/514",target:"_blank",rel:"noopener noreferrer"},N=e("h3",{id:"github",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#github","aria-hidden":"true"},"#"),t(" github")],-1),H={href:"https://github.com/pulsar-edit/github/pull/27",target:"_blank",rel:"noopener noreferrer"},O={href:"https://github.com/pulsar-edit/github/pull/19",target:"_blank",rel:"noopener noreferrer"};function P(V,W){const o=u("ExternalLinkIcon");return n(),i("div",null,[e("p",null,[t("Welcome to our latest release, Pulsar 1.106.0 is "),e("a",h,[t("available now!"),r(o)])]),s(" more "),p,c,e("p",null,[t("As alluded to in the title our biggest update we have in store is our experimental modern Tree-sitter implementation. This is a really important feature for us as it allows us to move to a modern and actively developed implementation of "),e("a",m,[t("Tree-sitter"),r(o)]),t(" as well as allowing us to remove one of our obstacles in our quest to get onto modern versions of Electron. To be honest this is a huge topic in its own right, so if you want to read more about it then you can have a look at our "),e("a",g,[t("previous blog post"),r(o)]),t(" which goes into a "),f,t(' more detail about this change. For now we have this under an experimental "opt-in" setting so to enable it you will need to go into your Settings and look for '),b,t(" in your "),_,t(' settings in order to enable it. In short, if you enable it then you should see more accurate and consistent syntax highlighting, improved automatic indentation and better code folding. As ever we are keen for feedback on this feature so, once enabled, if you notice anything "off" or have any other comments or feedback then please let us know on '),e("a",y,[t("Discord"),r(o)]),t(" or "),e("a",w,[t("file an issue"),r(o)]),t(".")]),k,e("ul",null,[e("li",null,[t("Updated: deps: Bump github to v0.36.16-pretranspiled "),e("a",v,[t("@DeeDeeG"),r(o)])]),e("li",null,[t("Removed: Mostly remove "),x,t(),e("a",A,[t("@confused-Techie"),r(o)])]),e("li",null,[t("Fixed: Fix: Image doesn't appear at first open "),e("a",C,[t("@asiloisad"),r(o)])]),e("li",null,[t("Removed: Remove specific cinnamon condition "),e("a",T,[t("@mauricioszabo"),r(o)])]),e("li",null,[t("Fixed: Fix of Clojure's indentation rules by removing query file "),e("a",j,[t("@mauricioszabo"),r(o)])]),e("li",null,[t("Fixed: Update links in settings page "),e("a",S,[t("@Daeraxa"),r(o)])]),e("li",null,[t("Added: [autocomplete-css] Sort "),q,t(),e("a",F,[t("@confused-Techie"),r(o)])]),e("li",null,[t('Fixed: Fixes on "comment block" for Clojure grammar '),e("a",I,[t("@mauricioszabo"),r(o)])]),e("li",null,[t("Added: Hardcode NSIS GUID "),e("a",B,[t("@confused-Techie"),r(o)])]),e("li",null,[t("Fixed: Make yarn sane "),e("a",R,[t("@mauricioszabo"),r(o)])]),e("li",null,[t("Fixed: Huge improvement on Clojure highlighting "),e("a",U,[t("@mauricioszabo"),r(o)])]),e("li",null,[t("Removed: Removed unused_require method "),e("a",D,[t("@mauricioszabo"),r(o)])]),e("li",null,[t("Bumped: Update dependency underscore to 1.12.1 [SECURITY] "),e("a",E,[t("@renovate"),r(o)])]),e("li",null,[t("Added: Add modern tree-sitter support behind an experimental flag "),e("a",L,[t("@savetheclocktower"),r(o)])]),e("li",null,[t("Added: Make CHANGELOG easier to merge and update dompurify "),e("a",M,[t("@mauricioszabo"),r(o)])]),e("li",null,[t("Added: js operators "),e("a",z,[t("@icecream17"),r(o)])]),e("li",null,[t("Bumped: Update dependency postcss to v8.2.13 [SECURITY] "),e("a",G,[t("@renovate"),r(o)])])]),N,e("ul",null,[e("li",null,[t("Added: Add logout menu option "),e("a",H,[t("@Daeraxa"),r(o)])]),e("li",null,[t("Updated: ci: Bump action dependencies "),e("a",O,[t("@Spiker985"),r(o)])])])])}const J=a(d,[["render",P],["__file","20230616-Daeraxa-v1.106.0.html.vue"]]);export{J as default}; diff --git a/assets/20230701-Daeraxa-JulyUpdate.html.1c15beb2.js b/assets/20230701-Daeraxa-JulyUpdate.html.1c15beb2.js new file mode 100644 index 0000000000..857b1dd6a2 --- /dev/null +++ b/assets/20230701-Daeraxa-JulyUpdate.html.1c15beb2.js @@ -0,0 +1 @@ +import{_ as n,o as r,c as i,e as l,a as t,b as e,d as s,f as a,r as h}from"./app.0e1565ce.js";const d="/assets/Linux-ARM.9adb19d5.jpg",c="/assets/reddit-private.034146a6.png",u="/assets/lemmy-icon.093c6158.png",p="/assets/malware.adaba8cd.png",m="/assets/deb-get-logo.d7385828.png",g="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAIAAAAiOjnJAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAlVJREFUeNrs2jFuglAAgOHasNsb4A24AXRovIXEg1jTNB6k8RSVONQblBvghhtudaKkaztoFSP6fWHk6fO9PzwHenVd38Gp3VsChIWwEBYIC2EhLBAWwkJYICyEhbBAWFy0YP9b3+bzoijOObmX6dQOdXQjPLFwFCIshAXCQlgIC4SFsBAWCAthISwQFsJCWCAshMWtCc7zNY9J0lyW+yTGaXrokI/Vqrk8sXAUgrAQFsICYSEshAXCQlgIC4SFsBAWCItWBV2ZaLXd5nl+a9sTRdFDvy+sFjVVPQ2HtxbWMsuSOHYUgrAQFsICYSEshAXCQlgIC2GBsBAWwgJhISyEBcLi8nXm1eRBGE4nk5Y+/HU2O2Z4Oho102vpVwurXWEYPl9wWB19M91RiLBAWAgLYSEsS4CwEBbCAmEhLIQFwkJYCAuEhbAQFggLYSEsEBbCQlggLISFsEBYCAthgbAQFsICYSEshAXCQlgIC4SFsBAWCAthISwQFsJCWCAshIWwQFgIC2GBsBAWwgJhISyEBcJCWAgLhIWwEBYIC2EhLBAWwkJYICyEhbBAWAgLYYGwEBbCAmEhLIQFwkJYCAuEhbDolMASNJZZdszwKIqsobD+kMSxRXAUIiwcha36zPNivT501DhN7dBv74tFudkcNKSqqusMq/qhiZNoqiqKwlGI/1ggLISFsEBYCAthgbAQFsICYSEshAXCQlgIC/6rV9f1nreWZfm1251zcoMwtEMd3YgDwgJHIcJCWCAshIWwQFgIC2GBsBAWwgJhISyuxbcAAwAe91yam6HiRgAAAABJRU5ErkJggg==",f="/assets/less.7be58a37.png",b={},_=t("p",null,"What has it got in its pocketses? It's the July community update!",-1),w=a('

Welcome to the July Community Update

This month's update is a little different in tone to some of the others. Recently, we\u2019ve had to deal with some problems outside of our control which is why our first few items are a little more "serious" and a bit less optimistic than our usual updates. But don\u2019t despair \u2014 all the good news comes in the second half!

The "bad news" section covers our CI not building certain binaries, uncertainty about our subreddit's future, and some false antivirus positives on a Pulsar dependency, but the "good news" section cleanses the palate with a new method of downloading Pulsar as well as a bunch of improvements to the application!

So with all that covered let's get on with it!

ARM builds problem

Some of you may have noticed that we had a bit of a problem recently in producing binaries for ARM on Linux which, for the most part shouldn't have affected anyone except ARM Linux users, had a knock-on effect with some of the community-maintained packages. Some of these packages (and associated package managers) may have seen a delay receiving an update to 1.106.0.

This wasn't anything to do with our code or builds but was instead a failure of the CI machine used to produce those builds failing to properly connect to various services.

',8),y={href:"https://pulsar-edit.dev/download.html",target:"_blank",rel:"noopener noreferrer"},k=a('

Subreddit closure/reopening and possible Lemmy community

[1]

',2),v={href:"https://www.reddit.com/r/pulsaredit/comments/146ja5a/rpulsaredit_is_taking_part_in_the_reddit_blackout/",target:"_blank",rel:"noopener noreferrer"},A={href:"https://www.reddit.com/r/pulsaredit/comments/14ktr3q/rpulsaredit_is_reopening_read_on_for_our/",target:"_blank",rel:"noopener noreferrer"},C=a('

The reason I bring this up here is because it is already possible that some people who were using Reddit for Pulsar news have now left it and won't have seen the above post. This post also contains a poll to get feedback from the affected community to see what our next steps would be. Should we close the subreddit and abandon it? Should we open a Lemmy community instead? Should we do both?

So if you are interested in using either Reddit or Lemmy, please have a look at the above post and vote in the poll.

Antivirus es5-ext issue

Some users have been reporting that various antivirus applications and virus scanning tools have been identifying Pulsar as a virus due to the es5-ext package.

This is is nothing to worry about \u2014 Pulsar is not a virus, nor do we use dependencies that are.

After some investigation, we discovered that somewhere in our dependency tree (i.e., a package dependency of a package we use) exists this es5-ext package. Early last year, this package was updated to display a "message of peace" to Russians (in reference to the ongoing conflict). This message displays to anyone within a Russian time zone, and, whilst the "payload" of a simple message is harmless, the act of targeting based on time zone has caused this package to be flagged by various scanners. Unfortunately the package author does not seem to want to remove it.

',7),I={href:"https://github.com/confused-Techie",target:"_blank",rel:"noopener noreferrer"},S={href:"https://github.com/savetheclocktower",target:"_blank",rel:"noopener noreferrer"},x={href:"https://github.com/pulsar-edit/pulsar/pull/608",target:"_blank",rel:"noopener noreferrer"},B=a('

In our experiments, binaries built in this manner are no longer being flagged by any notable antivirus program. Hopefully this gives our users peace of mind.

Pulsar available on deb-get

[2]

We want to put Pulsar on all major package managers, but it can be trickier than it looks. Various package managers have special requirements and restrictions, so it behoves us to move slowly and make sure we understand those constraints.

',4),W={href:"https://github.com/wimpysworld/deb-get",target:"_blank",rel:"noopener noreferrer"},E=t("code",null,"deb-get",-1),T=t("code",null,".deb",-1),L=t("code",null,".deb",-1),F=t("em",null,"how",-1),P={href:"https://github.com/wimpysworld/deb-get/blob/main/01-main/packages/pulsar",target:"_blank",rel:"noopener noreferrer"},R=a('

It's simple from the user's perspective, too. Once you have installed deb-get, all you have to do to install Pulsar is:

To update Pulsar (and other deb-get packages, all you need to do is:

So if you are on a Debian based distro and want a quick and easy way to install and update Pulsar, give it a try!

Tree-sitter migration of TOML grammar

[3]

',7),Y={href:"https://pulsar-edit.dev/blog/20230616-Daeraxa-v1.106.0.html",target:"_blank",rel:"noopener noreferrer"},q={href:"https://github.com/pulsar-edit/pulsar/pull/617",target:"_blank",rel:"noopener noreferrer"},J={href:"https://github.com/savetheclocktower",target:"_blank",rel:"noopener noreferrer"},Q={href:"https://pulsar-edit.dev/download.html#rolling-release",target:"_blank",rel:"noopener noreferrer"},M=t("code",null,"Use Modern Tree-Sitter Implementation",-1),N=t("code",null,"Core",-1),V={href:"https://discord.gg/7aEbB9dGRT",target:"_blank",rel:"noopener noreferrer"},j={href:"https://github.com/pulsar-edit/pulsar/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc",target:"_blank",rel:"noopener noreferrer"},O=a('

less-cache package update

[4]

',2),X={href:"https://github.com/confused-Techie",target:"_blank",rel:"noopener noreferrer"},U=t("code",null,"less-cache",-1),D=t("code",null,"less-cache",-1),G={href:"https://lesscss.org/",target:"_blank",rel:"noopener noreferrer"},H=t("code",null,"less-cache",-1),Z=t("code",null,"less@3.12.2",-1),z={href:"https://github.com/pulsar-edit/pulsar/pull/611",target:"_blank",rel:"noopener noreferrer"},K=t("code",null,"less-cache@2.0.0",-1),$=t("code",null,"less",-1),ee=t("code",null,"less",-1),te=t("ul",null,[t("li",null,"Parens-division now required around math expressions that use division")],-1),oe={href:"https://github.com/less/less.js/releases/tag/v4.0.0",target:"_blank",rel:"noopener noreferrer"},se=t("p",null,[e("The latter item has more impact: because "),t("code",null,"/"),e(" is used in newer CSS features like grids, parentheses are needed when doing division to remove ambiguity about the function of "),t("code",null,"/"),e(" on a particular line. Lots of usages in Pulsar core needed to be updated as a result of this, so it's quite possible that third-party packages are affected as well.")],-1),ae=t("p",null,[e("We're brainstorming ways that we can detect these usages and minimise the impact on these packages, but if you've got a community package that you're maintaining that uses "),t("code",null,"less"),e(" stylesheets, please take a moment to see if it's affected. At times, an error may be shown informing the user that the stylesheet failed to be compiled; in other cases, it won't be reported and instead can result in a broken UI.")],-1),ne=t("p",null,[e("The upside of this upgrade is that we can take advantage of new "),t("code",null,"less"),e(" features and keep our implementation up to date.")],-1),re=t("hr",null,null,-1),ie={href:"https://pulsar-edit.dev/community.html",target:"_blank",rel:"noopener noreferrer"},le=t("hr",{class:"footnotes-sep"},null,-1),he={class:"footnotes"},de={class:"footnotes-list"},ce={id:"footnote1",class:"footnote-item"},ue={href:"https://github.com/LemmyNet/lemmy-ui",target:"_blank",rel:"noopener noreferrer"},pe=t("a",{href:"#footnote-ref1",class:"footnote-backref"},"\u21A9\uFE0E",-1),me={id:"footnote2",class:"footnote-item"},ge={href:"https://github.com/wimpysworld/deb-get",target:"_blank",rel:"noopener noreferrer"},fe=t("a",{href:"#footnote-ref2",class:"footnote-backref"},"\u21A9\uFE0E",-1),be={id:"footnote3",class:"footnote-item"},_e={href:"https://github.com/toml-lang/toml",target:"_blank",rel:"noopener noreferrer"},we=t("a",{href:"#footnote-ref3",class:"footnote-backref"},"\u21A9\uFE0E",-1),ye={id:"footnote4",class:"footnote-item"},ke={href:"https://github.com/less/less.github.io",target:"_blank",rel:"noopener noreferrer"},ve=t("a",{href:"#footnote-ref4",class:"footnote-backref"},"\u21A9\uFE0E",-1);function Ae(Ce,Ie){const o=h("ExternalLinkIcon");return r(),i("div",null,[_,l(" more "),w,t("p",null,[e("This has now been resolved, so if you were waiting for an ARM binary you can now download it on our "),t("a",y,[e("downloads"),s(o)]),e(" page as normal. Community maintained packages should now all be up-to-date as well.")]),k,t("p",null,[e("You may or may not be aware that we decided to "),t("a",v,[e("close our subreddit"),s(o)]),e(" as part of the protest against the Reddit API changes. The subreddit remained closed after the original 48 hours for various reasons.")]),t("p",null,[e("We recently decided to "),t("a",A,[e("re-open the subreddit"),s(o)]),e(" for a number of reasons, some because we don't want to abandon a portion of our community entirely and partly because of threats from Reddit themselves.")]),C,t("p",null,[e("So we have been working on a way to get around this. "),t("a",I,[e("@confused-techie"),s(o)]),e(", with "),t("a",S,[e("@savetheclocktower"),s(o)]),e("'s help, has managed to "),t("a",x,[e("pin this to an earlier version"),s(o)]),e(" using our own fork of this package.")]),B,t("p",null,[e("That said: Pulsar is now officially supported on "),t("a",W,[e("deb-get"),s(o)]),e(", a package manager for Debian (and Debian based distributions such as Ubuntu). Amongst package managers, "),E,e(" is straightforward for us to support; all it does is grab a pre-built "),T,e(" binary from our GitHub releases and install it as any other "),L,e(". You can see exactly "),F,e(" straightforward by looking at "),t("a",P,[e("the script that grabs the latest version of Pulsar"),s(o)]),e(".")]),R,t("p",null,[e("In our "),t("a",Y,[e("last regular release"),s(o)]),e(", we made a big deal about our modern Tree-sitter migration and the new grammars that come with it.")]),t("p",null,[e("This work is still very much ongoing and we have new grammars being migrated all the time. Our latest is the "),t("a",q,[e("TOML Tree-sitter grammar"),s(o)]),e(" that "),t("a",J,[e("@savetheclocktower"),s(o)]),e(" has added. It's got all the benefits of a Tree-sitter grammar: more consistent highlighting, better performance, and support for features that can't be delivered with a TextMate-style grammar.")]),t("p",null,[e("You can try this out in our "),t("a",Q,[e("rolling release"),s(o)]),e(" or wait until the next regular one. Either way you will need to tick the "),M,e(" option in your "),N,e(" settings in order to enable it.")]),t("p",null,[e("As ever we are after feedback, so if you use this new TOML Tree-sitter grammar (or any other for that matter) and have any issues or see any inconsistencies compared to the existing grammar then please let us know on "),t("a",V,[e("Discord"),s(o)]),e(" or "),t("a",j,[e("file an issue"),s(o)]),e(".")]),O,t("p",null,[e("This is still in the works but "),t("a",X,[e("@confused-techie"),s(o)]),e(" has been making some updates to be added in the near future that bumps the version of "),U,e(" that we are using.")]),t("p",null,[D,e(" is a module that handles turning all Pulsar's (and our community's) packages written in "),t("a",G,[e("less"),s(o)]),e(" into valid CSS, as well as ensuring each one is able to import the values from Pulsar that it cares about. For the past three years "),H,e(" has been using "),Z,e(" which, while fine, has been missing out on some of the big new changes in less after version 4 was released. So "),t("a",z,[e("this PR"),s(o)]),e(" bumps it to "),K,e(" in order to bump the version of "),$,e(" to 4.1.3. This change gives us some exciting new features, but it also introduces a breaking change to existing "),ee,e(" stylesheets:")]),te,t("p",null,[e("See "),t("a",oe,[e("less.js v4.0.0 -> v4.1.3 change logs"),s(o)]),e(" for more info.")]),se,ae,ne,re,t("p",null,[e("And just like that, we are done with yet another month's updates. As ever, if you want to get more involved in the community, please join in on our various "),t("a",ie,[e("social channels"),s(o)]),e(". Hope to see you again this time next month!")]),le,t("section",he,[t("ol",de,[t("li",ce,[t("p",null,[e("Image from "),t("a",ue,[e("https://github.com/LemmyNet/lemmy-ui"),s(o)]),e(" - Copyright LemmyNet "),pe])]),t("li",me,[t("p",null,[e("Image from "),t("a",ge,[e("https://github.com/wimpysworld/deb-get"),s(o)]),e(" - Copyright (c) 2022 Wimpy's World "),fe])]),t("li",be,[t("p",null,[e("Image from "),t("a",_e,[e("https://github.com/toml-lang/toml"),s(o)]),e(" - Copyright (c) Tom Preston-Werner "),we])]),t("li",ye,[t("p",null,[e("Image from "),t("a",ke,[e("https://github.com/less/less.github.io"),s(o)]),e(" - Copyright (c) 2013 Alexis Sellier, Less.js, contributors. "),ve])])])])])}const xe=n(b,[["render",Ae],["__file","20230701-Daeraxa-JulyUpdate.html.vue"]]);export{xe as default}; diff --git a/assets/20230701-Daeraxa-JulyUpdate.html.acbe26e1.js b/assets/20230701-Daeraxa-JulyUpdate.html.acbe26e1.js new file mode 100644 index 0000000000..38f56631ae --- /dev/null +++ b/assets/20230701-Daeraxa-JulyUpdate.html.acbe26e1.js @@ -0,0 +1 @@ +const e=JSON.parse(`{"key":"v-4b1ee0b9","path":"/blog/20230701-Daeraxa-JulyUpdate.html","title":"Community Update","lang":"en-US","frontmatter":{"title":"Community Update","author":"Daeraxa","date":"2023-07-01T00:00:00.000Z","category":["news","log"],"tag":["update"]},"excerpt":"

What has it got in its pocketses? It's the July community update!

\\n","headers":[{"level":2,"title":"ARM builds problem","slug":"arm-builds-problem","link":"#arm-builds-problem","children":[]},{"level":2,"title":"Subreddit closure/reopening and possible Lemmy community","slug":"subreddit-closure-reopening-and-possible-lemmy-community","link":"#subreddit-closure-reopening-and-possible-lemmy-community","children":[]},{"level":2,"title":"Antivirus es5-ext issue","slug":"antivirus-es5-ext-issue","link":"#antivirus-es5-ext-issue","children":[]},{"level":2,"title":"Pulsar available on deb-get","slug":"pulsar-available-on-deb-get","link":"#pulsar-available-on-deb-get","children":[]},{"level":2,"title":"Tree-sitter migration of TOML grammar","slug":"tree-sitter-migration-of-toml-grammar","link":"#tree-sitter-migration-of-toml-grammar","children":[]},{"level":2,"title":"less-cache package update","slug":"less-cache-package-update","link":"#less-cache-package-update","children":[]}],"git":{"updatedTime":1689466216000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":18}]},"readingTime":{"minutes":5.62,"words":1687},"filePathRelative":"blog/20230701-Daeraxa-JulyUpdate.md","localizedDate":"July 1, 2023"}`);export{e as data}; diff --git a/assets/20230715-DeeDeeG-v1.107.0.html.38608ae1.js b/assets/20230715-DeeDeeG-v1.107.0.html.38608ae1.js new file mode 100644 index 0000000000..0559d33ae3 --- /dev/null +++ b/assets/20230715-DeeDeeG-v1.107.0.html.38608ae1.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-5e247e06","path":"/blog/20230715-DeeDeeG-v1.107.0.html","title":"Fresh off the CI press: Pulsar 1.107.0 is available now!","lang":"en-US","frontmatter":{"title":"Fresh off the CI press: Pulsar 1.107.0 is available now!","author":"Daeraxa","date":"2023-07-15T00:00:00.000Z","category":["dev"],"tag":["release"]},"excerpt":"

Fresh off the CI press: Pulsar 1.107.0 is available now!

\\n","headers":[{"level":2,"title":"What is new in 1.107.0?","slug":"what-is-new-in-1-107-0","link":"#what-is-new-in-1-107-0","children":[{"level":3,"title":"Changelog","slug":"changelog","link":"#changelog","children":[]}]}],"git":{"updatedTime":1689469688000,"contributors":[{"name":"DeeDeeG","email":"DeeDeeG@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":3.97,"words":1190},"filePathRelative":"blog/20230715-DeeDeeG-v1.107.0.md","localizedDate":"July 15, 2023"}');export{e as data}; diff --git a/assets/20230715-DeeDeeG-v1.107.0.html.9b90e470.js b/assets/20230715-DeeDeeG-v1.107.0.html.9b90e470.js new file mode 100644 index 0000000000..10242a64d5 --- /dev/null +++ b/assets/20230715-DeeDeeG-v1.107.0.html.9b90e470.js @@ -0,0 +1 @@ +import{_ as n,o as l,c as r,a as e,b as t,d as s,e as a,r as i}from"./app.0e1565ce.js";const d={},h={href:"https://github.com/pulsar-edit/pulsar/releases/tag/v1.107.0",target:"_blank",rel:"noopener noreferrer"},u=e("h2",{id:"what-is-new-in-1-107-0",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#what-is-new-in-1-107-0","aria-hidden":"true"},"#"),t(" What is new in 1.107.0?")],-1),c=e("p",null,"Another month and another Pulsar release! This month features a bunch of those all important quality-of-life updates. We have a whole host of bugfixes and upgrades for you along with a sprinkling of new features.",-1),p=e("code",null,"pulsar",-1),_=e("code",null,"ppm",-1),m=e("code",null,"pulsar",-1),f={href:"https://pulsar-edit.dev/docs/launch-manual/sections/getting-started/#opening-modifying-and-saving-files",target:"_blank",rel:"noopener noreferrer"},g=e("code",null,"pulsar --help",-1),b=e("p",null,[t("Please note that whilst we have this working now for the most part, the "),e("code",null,"-p"),t("/"),e("code",null,"--package"),t(" option as well as "),e("code",null,"ppm"),t(" still needs some more development work. However until this has been completed you can still access Pulsar package management from the command line on Windows by simply using "),e("code",null,"apm"),t(" (what "),e("code",null,"ppm"),t(" is still named underneath) in place of those commands.")],-1),w=e("code",null,"less-cache",-1),k={href:"https://lesscss.org/",target:"_blank",rel:"noopener noreferrer"},v=e("code",null,"3.12.2",-1),y=e("code",null,"4.1.3",-1),T={href:"https://pulsar-edit.dev/blog/20230701-Daeraxa-JulyUpdate.html#less-cache-package-update",target:"_blank",rel:"noopener noreferrer"},x={href:"https://pulsar-edit.dev/blog/20230601-Daeraxa-JuneUpdate.html#tree-sitter-updates-are-live",target:"_blank",rel:"noopener noreferrer"},A=e("code",null,"TOML",-1),P=e("code",null,"atom.versionSatisfies()",-1),B={href:"https://github.com/pulsar-edit/pulsar/pull/588",target:"_blank",rel:"noopener noreferrer"},S={href:"https://pulsar-edit.dev/blog/20230701-Daeraxa-JulyUpdate.html#antivirus-es5-ext-issue",target:"_blank",rel:"noopener noreferrer"},D=e("p",null,[t("And that brings us to a close for the "),e("code",null,"1.107.0"),t(" release notes. As ever a huge thank you to our wonderful donors and community members who not only make this project possible but worthwhile.")],-1),I=e("p",null,"Until next time, happy coding and see you amongst the stars!",-1),C=e("ul",null,[e("li",null,"The Pulsar Team")],-1),F=e("hr",null,null,-1),M=e("h3",{id:"changelog",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#changelog","aria-hidden":"true"},"#"),t(" Changelog")],-1),R=e("li",null,"Fixed a number of issues with the experimental modern Tree-sitter grammar mode",-1),U=e("li",null,'Pulsar can now be added to the PATH on Windows, via the "System" pane within Settings View.',-1),L=e("code",null,"less-cache",-1),E=e("code",null,"v2.0.0",-1),O=e("code",null,"less@4.1.3",-1),V={href:"https://github.com/pulsar-edit/less-cache/releases/tag/v2.0.0",target:"_blank",rel:"noopener noreferrer"},W=e("li",null,"Fixed a bug that would render files unable to be clicked with sticky headers enabled on One-Dark and One-Light themes.",-1),N=e("li",null,"Added a Modern Tree-Sitter TOML Grammar.",-1),Y=e("li",null,[t("Added a new API endpoint within Pulsar of "),e("code",null,"atom.versionSatisifes()"),t(" to allow packages to safely check the version of Pulsar, instead of having to do so themselves.")],-1),j=e("li",null,"An issue in a downstream dependency has been resolved that improperly flagged Pulsar as malicious.",-1),H=e("h4",{id:"pulsar",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#pulsar","aria-hidden":"true"},"#"),t(" Pulsar")],-1),G=e("code",null,"PATH",-1),J=e("code",null,"ATOM_HOME",-1),q=e("code",null,"InstallLocation",-1),z={href:"https://github.com/pulsar-edit/pulsar/pull/604",target:"_blank",rel:"noopener noreferrer"},K={href:"https://github.com/pulsar-edit/pulsar/pull/555",target:"_blank",rel:"noopener noreferrer"},Q={href:"https://github.com/pulsar-edit/pulsar/pull/631",target:"_blank",rel:"noopener noreferrer"},X={href:"https://github.com/pulsar-edit/pulsar/pull/607",target:"_blank",rel:"noopener noreferrer"},Z={href:"https://github.com/pulsar-edit/pulsar/pull/630",target:"_blank",rel:"noopener noreferrer"},$={href:"https://github.com/pulsar-edit/pulsar/pull/599",target:"_blank",rel:"noopener noreferrer"},ee={href:"https://github.com/pulsar-edit/pulsar/pull/620",target:"_blank",rel:"noopener noreferrer"},te=e("code",null,"atom.",-1),oe={href:"https://github.com/pulsar-edit/pulsar/pull/588",target:"_blank",rel:"noopener noreferrer"},se={href:"https://github.com/pulsar-edit/pulsar/pull/558",target:"_blank",rel:"noopener noreferrer"},ne=e("code",null,"less-cache",-1),le=e("code",null,"v2.0.0",-1),re=e("code",null,"less",-1),ae=e("code",null,"4.1.3",-1),ie={href:"https://github.com/pulsar-edit/pulsar/pull/611",target:"_blank",rel:"noopener noreferrer"},de=e("code",null,"spell-check",-1),he={href:"https://github.com/pulsar-edit/pulsar/pull/614",target:"_blank",rel:"noopener noreferrer"},ue={href:"https://github.com/pulsar-edit/pulsar/pull/609",target:"_blank",rel:"noopener noreferrer"},ce={href:"https://github.com/pulsar-edit/pulsar/pull/617",target:"_blank",rel:"noopener noreferrer"},pe={href:"https://github.com/pulsar-edit/pulsar/pull/610",target:"_blank",rel:"noopener noreferrer"},_e=e("code",null,"es5-ext",-1),me=e("code",null,"pulsar-edit/es5-ext",-1),fe={href:"https://github.com/pulsar-edit/pulsar/pull/608",target:"_blank",rel:"noopener noreferrer"},ge={href:"https://github.com/pulsar-edit/pulsar/pull/603",target:"_blank",rel:"noopener noreferrer"},be={href:"https://github.com/pulsar-edit/pulsar/pull/605",target:"_blank",rel:"noopener noreferrer"},we={href:"https://github.com/pulsar-edit/pulsar/pull/601",target:"_blank",rel:"noopener noreferrer"},ke={href:"https://github.com/pulsar-edit/pulsar/pull/602",target:"_blank",rel:"noopener noreferrer"},ve={href:"https://github.com/pulsar-edit/pulsar/pull/568",target:"_blank",rel:"noopener noreferrer"},ye=e("h4",{id:"less-cache",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#less-cache","aria-hidden":"true"},"#"),t(" less-cache")],-1),Te=e("code",null,"1.1.1",-1),xe=e("code",null,"2.0.0",-1),Ae={href:"https://github.com/pulsar-edit/less-cache/pull/5",target:"_blank",rel:"noopener noreferrer"},Pe=e("code",null,"less",-1),Be=e("code",null,"3.12.2",-1),Se=e("code",null,"4.1.3",-1),De={href:"https://github.com/pulsar-edit/less-cache/pull/4",target:"_blank",rel:"noopener noreferrer"},Ie={href:"https://github.com/pulsar-edit/less-cache/pull/3",target:"_blank",rel:"noopener noreferrer"},Ce={href:"https://github.com/pulsar-edit/less-cache/pull/2",target:"_blank",rel:"noopener noreferrer"},Fe={href:"https://github.com/pulsar-edit/less-cache/pull/1",target:"_blank",rel:"noopener noreferrer"};function Me(Re,Ue){const o=i("ExternalLinkIcon");return l(),r("div",null,[e("p",null,[t("Fresh off the CI press: Pulsar 1.107.0 is "),e("a",h,[t("available now!"),s(o)])]),a(" more "),u,c,e("p",null,[t("To kick things off we have now resolved an issue which has been hanging around since we began this project. As we no longer had the same method of installing the software as Atom, we created an issue for Windows users where the "),p,t(" and "),_,t(" were not being added to PATH. The upshot of this was some rather important Pulsar features were no longer working by default on Windows - namely the ability to launch pulsar by just running "),m,t(" in run or cmd/powershell (you can find more in our "),e("a",f,[t("launch manual"),s(o)]),t(" on how you can use this to open Pulsar directly to a particular file, line and even column). You can also run "),g,t(" to display all the available options.")]),b,e("p",null,[t("Next we have an update to our "),w,t(" package which bumps the version of "),e("a",k,[t("less"),s(o)]),t(" used by Pulsar from "),v,t(" to "),y,t(" which adds some new functionality but also introduces a breaking change regarding using division in math expressions. The good news is that Pulsar will automatically attempt to fix those issues during load. You can read more about this in our recent "),e("a",T,[t("community update blog post"),s(o)]),t(" on this subject.")]),e("p",null,[t("To continue the trend of new developments with the modern "),e("a",x,[t("Tree-sitter implementation"),s(o)]),t(", we now have a new grammar for "),A,t(" files as well as a couple of fixes to some issues that have been discovered with the new implementation.")]),e("p",null,[t("Another nice feature recently added is a new API endpoint - "),P,t(`. No longer do package maintainers (or any other Pulsar hacker) need to write their own way of checking for Pulsar versions, we now have an inbuilt way of doing it which will hopefully alleviate some of the problems we have seen with some packages using (let's just say) "less-than-ideal" methods of version checking. You can read more about it on the `),e("a",B,[t("PR"),s(o)]),t(".")]),e("p",null,[t("And lastly, in case you missed it from the community update, we have resolved an issue due to a downstream dependency that was causing Pulsar to get falsely flagged by virus checkers. You can read more about this from the "),e("a",S,[t("update post"),s(o)]),t(".")]),D,I,C,F,M,e("ul",null,[R,U,e("li",null,[t("Bumped "),L,t(" to "),E,t(" which uses "),O,t(". This adds many new features of Less, while causing breaking changes to existing Less StyleSheets. Read more about these changes "),e("a",V,[t("here"),s(o)]),t(". Pulsar will attempt to automatically repair any breaking changes in any package style sheets, while emitting deprecations.")]),W,N,Y,j]),H,e("ul",null,[e("li",null,[t("Added: Improved Windows Install ("),G,t(", "),J,t(", "),q,t(") "),e("a",z,[t("@confused-Techie"),s(o)])]),e("li",null,[t("Fixed: Running PR for Tree-Sitter fixes "),e("a",K,[t("@savetheclocktower"),s(o)])]),e("li",null,[t("Added: [autocomplete-css]: Manual Decaf of Source "),e("a",Q,[t("@confused-Techie"),s(o)])]),e("li",null,[t("Fixed: [welcome]: Ensure Changelog Always Shows if enabled, and version hasn't been dismissed "),e("a",X,[t("@confused-Techie"),s(o)])]),e("li",null,[t("Bumped: [autocomplete-plus] Maintenance - Deps bumps, remove CoffeeScript files "),e("a",Z,[t("@confused-Techie"),s(o)])]),e("li",null,[t("Fixed: Fix tree-view sticky headers of one-dark & one-light themes "),e("a",$,[t("@asiloisad"),s(o)])]),e("li",null,[t("Fixed: [spell-check]: Remove usage of reserved word "),e("a",ee,[t("@confused-Techie"),s(o)])]),e("li",null,[t("Added: [core]: Implement API on "),te,t(" to compare Pulsar Versions "),e("a",oe,[t("@confused-Techie"),s(o)])]),e("li",null,[t("Added: [settings-view]: Manual Decaf (source) "),e("a",se,[t("@confused-Techie"),s(o)])]),e("li",null,[t("Bumped: [core]: Bump "),ne,t(" to "),le,t(" Upgrades "),re,t(" to "),ae,t(),e("a",ie,[t("@confused-Techie"),s(o)])]),e("li",null,[t("Added: [core]: Bundle "),de,t(),e("a",he,[t("@confused-Techie"),s(o)])]),e("li",null,[t("Bumped: Update dependency semver to v7.5.2 [SECURITY] "),e("a",ue,[t("@renovate"),s(o)])]),e("li",null,[t("Added: [modern-tree-sitter] Add TOML tree-sitter grammar "),e("a",ce,[t("@savetheclocktower"),s(o)])]),e("li",null,[t("Fixed: [language-toml]: Allow spaces within Array "),e("a",pe,[t("@confused-Techie"),s(o)])]),e("li",null,[t("Fixed: Pin "),_e,t(" to "),me,t(" removing code flagged as malicious "),e("a",fe,[t("@confused-Techie"),s(o)])]),e("li",null,[t("Bumped: [git-diff] Bump all Deps "),e("a",ge,[t("@confused-Techie"),s(o)])]),e("li",null,[t("Bumped: Update dependency semver [SECURITY] "),e("a",be,[t("@renovate"),s(o)])]),e("li",null,[t("Fixed: [autocomplete-css] Get tests passing for new CSS tree-sitter grammar "),e("a",we,[t("@savetheclocktower"),s(o)])]),e("li",null,[t("Bumped: [dalek] Bump dependencies to latest, fix links "),e("a",ke,[t("@confused-Techie"),s(o)])]),e("li",null,[t("Bumped: Update dependency marked to v5.0.3 "),e("a",ve,[t("@mauricioszabo"),s(o)])])]),ye,e("ul",null,[e("li",null,[t("Bumped: Bump "),Te,t(" => "),xe,t(),e("a",Ae,[t("@confused-Techie"),s(o)])]),e("li",null,[t("Bumped: Bump "),Pe,t(),Be,t(" => "),Se,t(),e("a",De,[t("@confused-Techie"),s(o)])]),e("li",null,[t("Added: Repository Cleanup + CoffeeScript tool Removal (Depends on #2) "),e("a",Ie,[t("@confused-Techie"),s(o)])]),e("li",null,[t("Added: Manual decaf of source files "),e("a",Ce,[t("@confused-Techie"),s(o)])]),e("li",null,[t("Added: Implement Repo Tests "),e("a",Fe,[t("@confused-Techie"),s(o)])])])])}const Ee=n(d,[["render",Me],["__file","20230715-DeeDeeG-v1.107.0.html.vue"]]);export{Ee as default}; diff --git a/assets/20230716-Daeraxa-v1.107.1.html.3624d889.js b/assets/20230716-Daeraxa-v1.107.1.html.3624d889.js new file mode 100644 index 0000000000..f8a085abda --- /dev/null +++ b/assets/20230716-Daeraxa-v1.107.1.html.3624d889.js @@ -0,0 +1 @@ +import{_ as s,o as n,c as o,a as t,b as e,d as r,e as i,r as l}from"./app.0e1565ce.js";const h={},c={href:"https://github.com/pulsar-edit/pulsar/releases/tag/v1.107.1",target:"_blank",rel:"noopener noreferrer"},d=t("h2",{id:"what-is-new-in-1-107-1",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#what-is-new-in-1-107-1","aria-hidden":"true"},"#"),e(" What is new in 1.107.1?")],-1),p=t("p",null,[e("Hotfix for important cosmetic issues in the bundled "),t("code",null,"github"),e(" package. This package missed updates to be compatible with the breaking changes in Less Style Sheet syntax, and needed to be updated to preserve the intended styling of the GitHub and Git panes.")],-1),u={href:"https://github.com/pulsar-edit/pulsar/pull/639",target:"_blank",rel:"noopener noreferrer"},_={href:"https://github.com/pulsar-edit/pulsar/issues/638",target:"_blank",rel:"noopener noreferrer"},f={href:"https://github.com/pulsar-edit/pulsar/releases/tag/v1.107.0",target:"_blank",rel:"noopener noreferrer"};function m(b,g){const a=l("ExternalLinkIcon");return n(),o("div",null,[t("p",null,[e("Hotfix: Pulsar 1.107.1 is "),t("a",c,[e("available now!"),r(a)])]),i(" more "),d,p,t("p",null,[e("Includes this PR: "),t("a",u,[e("#639"),r(a)]),e(" to fix this issue: "),t("a",_,[e("#638"),r(a)])]),t("p",null,[e("See the "),t("a",f,[e("v1.107.0"),r(a)]),e(" release for all the other changes since v1.106.0.")])])}const k=s(h,[["render",m],["__file","20230716-Daeraxa-v1.107.1.html.vue"]]);export{k as default}; diff --git a/assets/20230716-Daeraxa-v1.107.1.html.d06cfc56.js b/assets/20230716-Daeraxa-v1.107.1.html.d06cfc56.js new file mode 100644 index 0000000000..4f1388ed30 --- /dev/null +++ b/assets/20230716-Daeraxa-v1.107.1.html.d06cfc56.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-6bf84516","path":"/blog/20230716-Daeraxa-v1.107.1.html","title":"Hotfix: Pulsar v1.107.1","lang":"en-US","frontmatter":{"title":"Hotfix: Pulsar v1.107.1","author":"Daeraxa","date":"2023-07-16T00:00:00.000Z","category":["dev"],"tag":["release"]},"excerpt":"

Hotfix: Pulsar 1.107.1 is available now!

\\n","headers":[{"level":2,"title":"What is new in 1.107.1?","slug":"what-is-new-in-1-107-1","link":"#what-is-new-in-1-107-1","children":[]}],"git":{"updatedTime":1689556751000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":0.39,"words":118},"filePathRelative":"blog/20230716-Daeraxa-v1.107.1.md","localizedDate":"July 16, 2023"}');export{e as data}; diff --git a/assets/20230801-Daeraxa-AugustUpdate.html.8f13ad09.js b/assets/20230801-Daeraxa-AugustUpdate.html.8f13ad09.js new file mode 100644 index 0000000000..533ce85fa0 --- /dev/null +++ b/assets/20230801-Daeraxa-AugustUpdate.html.8f13ad09.js @@ -0,0 +1 @@ +import{_ as r}from"./detective.e7e89ed0.js";import{_ as i}from"./package.4e7449ae.js";import{_ as s}from"./spotlight.aba19e76.js";import{_ as l,o as h,c as d,e as u,a as t,b as e,d as a,f as n,r as c}from"./app.0e1565ce.js";const p="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAANAAAACACAYAAAB6KueDAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAp3SURBVHhe7Z1BSBRfHMefu2AIkREsFBseJOgkCAVRCJonIegQhNcgCIK6dBLs8D9GEBR2KeoQdBAEwQgSAkkUoygMOgYdYiPBi1IIirL//U3PWGd+b2d2f29m3sx8P/Bl3afzfu+1823nfWd2tqveQEVgcXFRzc/Pq4WFBbW3t6e+fPniPQKQdcrlshocHPQeR0dH1djYmBoeHta/DYEMZGJra6v+4MGDeqVSIZNBUGFE+zzt++SBVhgNNDc3V+/r62M7h6CiiDxAXjDBGmhiYoLtDIKKKvIER8BA4+PjbAcQVHSRN/wcMNDk5CS7IQRBf0UeaeafgWZnZ9kNIAg6KPLKPl6MvbOzo/r7+9XPnz8bvwcAtKJararv37+r7u5uVaKGR48ewTwARIS8Qp4hunZ3d+snTpxQ6+vrXgMAIJxKpaJqtZoq0dUF7ZiHztDOzc15GzeO/iAo86J9mfbpyFcfNCDPvH37VpWPHTv238ePH3VzayYmJtTLly/V6dOn1ZEjR3QrANmG9mXap69du6a2t7fV8vKy/k1rGt6hy3/K/5EDwyB3vnjxQpVK3rIJgFxy8eJF73rPHz9+6BYzdO1cF13zE+UQbmlpSQ0NDelnAOQXunB6ZGREPzND66Cu7u5uL8YOY2NjQ/X29upnAOSXzc1NdfToUf3MDMXYXY1HOjkUCi22ACgKXV1kjXCwoAFAAAwEgAAYCAABMBAAAmAgAATAQAAIgIEAEAADASAABgJAAAwEgAAYCAABMBAAAmAgAATAQAAISM1AdLm4TT1//lz37B40Nm7MErkCNzaJskZqnwey/Y9FH2569+6dOn/+vG5xg/fv33ufbozyocV2cOXzWbZfx6zNKzcGIuiGd58+fVLHjx/XLemytramzp49G8s992CgeIk6r1ytgWhHvXLlivX/7TuBxnD16lXcsDLn5C5EoEOm27dv62fpQWOIenskkG3oPTNUtuFq2NSzZ890peSh2tyYbMoVuLFJ5Arc2Djlag3UTFqhQlyhgR/br0enFH0NlFsDEUmHCnGGBn5goHiJOq9cn0hNMlRAaFBMcn8lQlKhAkKD4kLvmaGyDVcjTsUZKiQRGvjlCtzYJHIFbmyccr0GaiauUCGp0MCP7dejU4q+BiqMgQjboUKSoYEfGCheos4r92ugZmyGCtQH9YXQoNgUykCErVCB+qC+AKD3zFDZhquRpCShQhqhgV+uwI1NIlfgxsapUGugZjoNFdIKDfzYfj06pehroMIaiGg3VEgzNPADA8VL1HkVbg3UTDuhAkIDwFFoAxFRQwWEBsAEvWeGyjZcjTTVKlRwITTwyxW4sUnkCtzYOBV6DdSMKVRwJTTwY/v16JSir4FgoCYoTFhdXf0XKrgUGviBgeIl6rwKvwZqhgyzHyogNABRwDsQw40bN7zHp0+feo8ugnegeIk6Lxgoo8BA8RJ1XjiEA0AADASAABgIAAG5MdD169f1T+7h8tiAjNyECNvb294JT9cut6ETs3SC9tChQ7rFDggR4iXqvHJjIBofnbOhE590PscF6IQsXe1NV30XfUeLStbmlas1EO2os7Oz3mU5aUNjmJmZ8cYE8kvuQgQ6ZJqamtLP0oPGMDQ0pJ+BPEPvmaGyDVdDIj+NhTv7d0mIavvh/k6iTrl58ybbXx5Fc+0Urj+D2MaAbMPVkMjP9vZ2vfFuxP5tnKKaVNsP97cSdcru7m798uXLbJ95Es2R5topXJ8GsY0B2YarIRFHrVarNxby7N/HIapFNTm4v5dIwu/fv+sDAwNsv3kQzW1jY0PPtjO4fg1iGwOyDVdDIhMrKyv1xoKe3camqMbS0pKuGoTbRiIpZPRqtcr2nWXRnEz/ibUD17dBbGNAtuFqSNSKJ0+esNvYFNVoBbeNRDZYXV2t9/T0sP1nUTQXmpMNuP4NYhsDsg1XQ6Iw4gwVuNDAD7edRLaYm5url8tltkaWRHOgudiCq2EQ2xiQbbgaEoURV6hgCg38cNtKZJOpqSm2RpZEc7AJV8MgtjEg23A1JIqC7VChVWjgh9teIttkOd6WxNUmuDoGsY0B2YarIVFUbIUK1Af1FRWuD4lsk9V4WxpXm+BqGcQ2BmQbroZE7WAjVAgLDfxwfUgUB1mLt23E1Sa4egaxjQHZhqshUbtIQoUooYEfrh+J4iIr8batuNoEV9MgtjEg23A1JGqXTkOFqKGBH64vieLE9XjbZlxtgqtrENsYkG24GhJ1QruhQjuhgR+uP4nixtV423ZcbYKrbRDbGJBtuBoSdUrUUKHd0MAP16dESeBivG07rjbB1TaIbQzINlwNiSRECRXaDQ38cH1KlBQuxdtxxNUmuPoGsY0B2YarIZGUVqFCJ6GBH65fiZLClXg7rrjaBDcGg9jGgGzD1ZBIiilU6DQ08OPvV6okSTvejjOuNsGNwyC2MSDbcDUkssGvX78OhAqS0MBP81htKGnSirfjjqtNcGMxiG0MyDZcDYlssR8qSEMDP9yYJUqDpOPtJOJqE9x4OOXqrjy22L+p/P5N5m2Ql7vXvHr1yvvWir29Pd0SD+Vy2btBTGPto1uSpZ3Xy3NSmGzD1ZDIdbgxS5QmScTbScXVJrgxGcQ2BmQbroZErsONWaK0iTPeTjKuNsGNixMO4RIib/OlQzg6lKNDOpvQIRsdutEhXJpEfb1goITI43z//PmjLly4oL5+/apbZAwMDKilpSXV29urW9IDBnKMvM6Xbqd87tw58VdhVqtV9eHDB2fu5Br19cLXmwARtMO/efNG9fT06Jb2oW1fv36dydsgw0BADB16TU9Pd7RuoW1o28HBQd2SLWAgYAVa/D98+FA/iw5tk9a5HlvQwXSobMPVkMh1uDFL5CrtxNsuxNUmuPFyQoiQEEWZb9R425W42kTU1wsGSogizTcs3qY108rKijp8+LBucY+orxfWQMA6ZAxK5rhUbT+1c9k87QADgVjg4u0sx9UmYCAQG83xdtbjahOprYFAcXj8+LH3eOvWLe8xCzgfIgDgMggRAEgAGAgAATAQAAJgIAAEwEAACICBABAAAwEgAAYCQAAMBIAAGAgAATAQAAJgIAAEwEAACCh1d3frH1uzubmpfwIg39BH0qNA3ilFvY2qrdu3AuA6nz9/1j+1hrxTOnXqlH7amrt378b+nTAApA3t47SvR4G8U4r6EdvFxcXIHQOQVWgfX15e1s9aQ94pXbp0ST8N5969e2pkZMS759fa2ppuBSDb0L5M+zTt27SPR4W800XfQH3y5Em1vr6umwEAYVQqFVWr1f6mcHfu3NHNAIAokGfIO131Bjs7O6q/v1/8HS8AFAG6r923b9+8+9x5J1LJSVNTU94vAQCtIa/8u2EkvQPtMzk5SfeugiDIoImJCe2WvxwwEDE+Ps5uCEFFF3nDT8BABLmM6wCCiir/O88+rIGImZmZel9fH9sZBBVF1Wq1Pj09rV0RxGggYmtrq37//v16pVJhO4egvIr2edr3yQOt8GLsxgah0KU88/PzamFhwbteKOoFdwBkgTNnznjfIDE6OqrGxsbU8PCw/k0rlPofGV48eEJHPcUAAAAASUVORK5CYII=",g="/assets/fork.425ac14f.png",m="/assets/info.7c7632fc.png",f={},w=t("p",null,"How many roads must a man walk down? Not a clue, but what I do know is that this is the one and only Pulsar Community Update!",-1),A=n('

Welcome to the August Community Update

As always, a big, warm welcome to this month's update! This time around we have some interesting new features, some of which have been in the works for a while, so it is great to be able to report on their state as they will soon be making their way to a Pulsar near you! These updates include news on a new "pulsar-updater" package designed to help you keep up to date with our latest releases, a big new feature for one of our core packages for working with Markdown documents and bringing some of our dependencies up to date so you no longer have to keep around old toolchains.

So lets get on with it!

Update to markdown-preview language identifiers

',5),b={href:"https://github.com/confused-Techie",target:"_blank",rel:"noopener noreferrer"},y=t("code",null,"markdown-preview",-1),v=t("code",null,"markdown-preview",-1),_=t("code",null,"markdown-preview",-1),k=n('

This is done by providing a "language identifier" to the codeblock in order to signal to Pulsar what language you want it to highlight. For example, if you wanted to embed some javascript in a document, you would need to start the code fence like this:

```javascript\n

or

```js\n

The issue was that this list of identifiers had not been updated for a long time, and people used to other tools and applications with this same functionality (for example, within GitHub and various static site generators) were finding that identifiers they commonly used were not being correctly identified.

',5),x=t("em",null,"only",-1),P={href:"https://github.com/github-linguist/linguist/blob/master/lib/linguist/languages.yml",target:"_blank",rel:"noopener noreferrer"},D=t("code",null,"markdown-preview",-1),N=t("code",null,"Custom Syntax Highlighting Language Identifiers",-1),I={href:"https://web.pulsar-edit.dev/packages/language-pon",target:"_blank",rel:"noopener noreferrer"},B=t("code",null,"j",-1),G=t("code",null,"pon: source.pon, j: source.js",-1),Q={href:"https://pulsar-edit.dev/download.html#rolling-release",target:"_blank",rel:"noopener noreferrer"},E={href:"https://pulsar-edit.dev/download.html#regular-releases",target:"_blank",rel:"noopener noreferrer"},V=t("h2",{id:"moving-ppm-to-our-own-npm-fork",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#moving-ppm-to-our-own-npm-fork","aria-hidden":"true"},"#"),e(" Moving PPM to our own NPM fork")],-1),j=t("img",{src:g,height:"200"},null,-1),T={href:"https://github.com/nodejs/node-gyp/tree/main",target:"_blank",rel:"noopener noreferrer"},S=t("code",null,"node-gyp",-1),U={href:"https://github.com/pulsar-edit/ppm",target:"_blank",rel:"noopener noreferrer"},C={href:"https://github.com/DeeDeeG",target:"_blank",rel:"noopener noreferrer"},M={href:"https://github.com/pulsar-edit/ppm/pull/79",target:"_blank",rel:"noopener noreferrer"},F=t("code",null,"node-gyp",-1),H=t("h2",{id:"pulsar-updater-package-in-the-works",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#pulsar-updater-package-in-the-works","aria-hidden":"true"},"#"),e(" pulsar-updater package in the works")],-1),O=t("img",{src:m,height:"200"},null,-1),R=t("p",null,'A comment that we see come up somewhat regularly from both our team and community members alike is how Pulsar should deal with updates. Atom has an auto-update module that (at least on Windows and macOS) would allow you to update Atom from within the application. Unfortunately, these existing auto-update methods are either incompatible or unfeasible to implement within Pulsar (for example, the requirement that we would need to pay large amounts of money to sign our binaries). We also support (and plan to support) a much larger set of distribution methods than Atom - various Linux, Windows and macOS package managers as well as our "standard" binary releases, which means accounting for all of these different methods.',-1),X={href:"https://github.com/confused-Techie",target:"_blank",rel:"noopener noreferrer"},q={href:"https://github.com/pulsar-edit/pulsar/pull/656",target:"_blank",rel:"noopener noreferrer"},Y=t("p",null,"The notifications generated by this package can be dismissed entirely or until the next launch in order to be as minimally intrusive as possible.",-1),J=t("p",null,"This feature is still something in the works so isn't available just yet, but you can be sure we will provide more details and an announcement once it is out in the wild.",-1),K=t("h2",{id:"pulsar-package-repository-feature-detection-is-now-live",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#pulsar-package-repository-feature-detection-is-now-live","aria-hidden":"true"},"#"),e(" Pulsar Package Repository feature detection is now live!")],-1),z=t("p",null,[t("img",{src:r,height:"200"}),t("img",{src:i,height:"200"})],-1),Z={href:"https://pulsar-edit.dev/blog/20230601-Daeraxa-JuneUpdate.html#community-package-feature-detection",target:"_blank",rel:"noopener noreferrer"},L=t("p",null,"We don't currently have any implementations of this feature in Pulsar itself just yet, but we do have plans for it. For example, we could provide automatic detection of a community grammar for a language not installed on the system. This could easily be extended to all kinds of other features provided by community packages.",-1),W=t("h2",{id:"community-spotlight",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#community-spotlight","aria-hidden":"true"},"#"),e(" Community spotlight")],-1),$=t("img",{src:s,height:"200"},null,-1),ee={href:"https://github.com/arite",target:"_blank",rel:"noopener noreferrer"},te={href:"https://github.com/cat-master21",target:"_blank",rel:"noopener noreferrer"},oe={href:"https://github.com/2colours",target:"_blank",rel:"noopener noreferrer"},ae={href:"https://github.com/GuilleW",target:"_blank",rel:"noopener noreferrer"},ne=t("hr",null,null,-1),re={href:"https://pulsar-edit.dev/community.html",target:"_blank",rel:"noopener noreferrer"};function ie(se,le){const o=c("ExternalLinkIcon");return h(),d("div",null,[w,u(" more "),A,t("p",null,[t("a",b,[e("@confused-techie"),a(o)]),e(" has been working on a significant update to the way the "),y,e(" package handles the highlighting of code inside code fences. The "),v,e(' package is a package included with Pulsar that allows you to render Markdown documents directly within Pulsar without the need for any external tools. One of its features is the fact that it is able to perform syntax highlighting of code within "code fences" or "code blocks". That is, if you include a small snippet of javascript or shell script into your document, '),_,e(" can provide the same level of syntax highlighting to your snippet in the preview pane as in the main editor.")]),k,t("p",null,[e("This change adds some significant improvements to this system, not content with "),x,e(" updating the existing list of languages to match GitHub's "),t("a",P,[e("Linguist"),a(o)]),e(' library but massively extending the system to allow users to tailor the behaviour to suit their own needs. In addition to the "Linguist" (GitHub) mode, it adds in lists for other tools such as "Chroma" (Codeberg, Gitea, Hugo), "Rouge" (GitLab, Jekyll) and "HighlightJS" (Markdown-IT). In many cases, these will be identical and users can leave it on the existing default mode (Linguist). However, for those who rely on Pulsar and the '),D,e(" package to correctly display code as it will be used on the platform of their choice, this allows them to not have to modify the identifier between their editor and their production files.")]),t("p",null,[e("As if that wasn't enough, it also features a new feature that allows you to freely add and customise your own options in order to include non-default languages and to override your own options by way of a new setting within the package: \xA0"),N,e(". For example, if I wanted to have "),t("a",I,[e("PON"),a(o)]),e(" support or wanted to add "),B,e(' as an identifier for "javascript", I could add the following to that setting: '),G,e(".")]),t("p",null,[e("You can try out this feature right now in our "),t("a",Q,[e("rolling release"),a(o)]),e(" or you can wait until our next "),t("a",E,[e("regular release"),a(o)]),e(".")]),V,j,t("p",null,[e("One issue we have had crop up again and again that has frustrated contributors and users alike is that Pulsar was using some older versions of some tools, particularly "),t("a",T,[e("node-gyp"),a(o)]),e(" (a tool for compiling native modules for Node.js), which had some very particular requirements for development tooling on the machine. For example, it was very particular about which version of the Visual Studio tools would work and which versions of Python could be used (i.e. not the latest releases).")]),t("p",null,[e("This is a problem for Pulsar because "),S,e(" is used by NPM which is used as part of our "),t("a",U,[e("PPM"),a(o)]),e(" (Pulsar Package Manager) tool for installing Pulsar packages. Pulsar does not use a system NPM installation but instead has its own version bundled with the application.")]),t("p",null,[e("So "),t("a",C,[e("@DeeDeeG"),a(o)]),e(" has done some work to "),t("a",M,[e("migrate to our own fork of NPM"),a(o)]),e(" which changes our "),F,e(" dependency from the old 5.11 to the much newer 9.4.0 which should mean that Pulsar will soon properly support modern compiler toolchains and drop support for old, unsupported ones.")]),H,O,R,t("p",null,[e("So a first step towards a solution here is a new core package developed by "),t("a",X,[e("@confused-techie"),a(o)]),e(" called "),t("a",q,[e("pulsar-updater"),a(o)]),e(" which is designed to inform the user not only when a new version has been released but also how to install it based on what install method is detected on the system. For example, it might prompt you to visit the Pulsar website to download a new binary, or it can provide a command for your terminal to run.")]),Y,J,K,z,t("p",null,[e("A couple of months ago we announced that a "),t("a",Z,[e("new feature was coming to our package backend"),a(o)]),e('. The linked post goes into far more detail, but in a nutshell this is a feature that adds "feature detection" to our backend, allowing users and developers to have a much more cohesive experience when trying to install or develop new Pulsar packages that rely on features from other community packages.')]),L,W,$,t("p",null,[e("As we always say (because it is absolutely true), this project would be nothing if it weren't for our wonderful community members, and we want to make sure we provide proper attribution when people directly contribute to Pulsar, not only through our release notes but on our social channels as well. So in light of that, a big thank you to "),t("a",ee,[e("@arite"),a(o)]),e(", "),t("a",te,[e("@cat-master21"),a(o)]),e(", "),t("a",oe,[e("@2colors"),a(o)]),e(" and "),t("a",ae,[e("@GuilleW"),a(o)]),e(" who have made contributions to the project which we have recently included.")]),ne,t("p",null,[e("And just like that, we are done with yet another month's updates. As ever, if you want to get more involved in the community, please join in on our various "),t("a",re,[e("social channels"),a(o)]),e(". Hope to see you again this time next month!")])])}const pe=l(f,[["render",ie],["__file","20230801-Daeraxa-AugustUpdate.html.vue"]]);export{pe as default}; diff --git a/assets/20230801-Daeraxa-AugustUpdate.html.a30019be.js b/assets/20230801-Daeraxa-AugustUpdate.html.a30019be.js new file mode 100644 index 0000000000..32886257db --- /dev/null +++ b/assets/20230801-Daeraxa-AugustUpdate.html.a30019be.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-3cb6722f","path":"/blog/20230801-Daeraxa-AugustUpdate.html","title":"Community Update","lang":"en-US","frontmatter":{"title":"Community Update","author":"Daeraxa","date":"2023-08-01T00:00:00.000Z","category":["news","log"],"tag":["update"]},"excerpt":"

How many roads must a man walk down? Not a clue, but what I do know is that this is the one and only Pulsar Community Update!

\\n","headers":[{"level":2,"title":"Update to markdown-preview language identifiers","slug":"update-to-markdown-preview-language-identifiers","link":"#update-to-markdown-preview-language-identifiers","children":[]},{"level":2,"title":"Moving PPM to our own NPM fork","slug":"moving-ppm-to-our-own-npm-fork","link":"#moving-ppm-to-our-own-npm-fork","children":[]},{"level":2,"title":"pulsar-updater package in the works","slug":"pulsar-updater-package-in-the-works","link":"#pulsar-updater-package-in-the-works","children":[]},{"level":2,"title":"Pulsar Package Repository feature detection is now live!","slug":"pulsar-package-repository-feature-detection-is-now-live","link":"#pulsar-package-repository-feature-detection-is-now-live","children":[]},{"level":2,"title":"Community spotlight","slug":"community-spotlight","link":"#community-spotlight","children":[]}],"git":{"updatedTime":1691032635000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":3}]},"readingTime":{"minutes":4.95,"words":1486},"filePathRelative":"blog/20230801-Daeraxa-AugustUpdate.md","localizedDate":"August 1, 2023"}');export{e as data}; diff --git a/assets/20230816-DeeDeeG-v1.108.0.html.11a20d1e.js b/assets/20230816-DeeDeeG-v1.108.0.html.11a20d1e.js new file mode 100644 index 0000000000..cfd1bfd1f1 --- /dev/null +++ b/assets/20230816-DeeDeeG-v1.108.0.html.11a20d1e.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-0730d41e","path":"/blog/20230816-DeeDeeG-v1.108.0.html","title":"Great Scott! A new Pulsar release. Pulsar 1.108.0 is available now!","lang":"en-US","frontmatter":{"title":"Great Scott! A new Pulsar release. Pulsar 1.108.0 is available now!","author":"Daeraxa","date":"2023-08-16T00:00:00.000Z","category":["dev"],"tag":["release"]},"excerpt":"

Great Scott! A new Pulsar release. Pulsar 1.108.0 is available now!

\\n","headers":[{"level":2,"title":"What is new in 1.108.0?","slug":"what-is-new-in-1-108-0","link":"#what-is-new-in-1-108-0","children":[{"level":3,"title":"Changelog","slug":"changelog","link":"#changelog","children":[]}]}],"git":{"updatedTime":1692232131000,"contributors":[{"name":"DeeDeeG","email":"DeeDeeG@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":3.8,"words":1140},"filePathRelative":"blog/20230816-DeeDeeG-v1.108.0.md","localizedDate":"August 16, 2023"}');export{e as data}; diff --git a/assets/20230816-DeeDeeG-v1.108.0.html.49ef70c8.js b/assets/20230816-DeeDeeG-v1.108.0.html.49ef70c8.js new file mode 100644 index 0000000000..e48c2d75ac --- /dev/null +++ b/assets/20230816-DeeDeeG-v1.108.0.html.49ef70c8.js @@ -0,0 +1 @@ +import{_ as o,o as n,c as i,a as e,b as t,d as a,e as l,f as s,r as d}from"./app.0e1565ce.js";const u={},h={href:"https://github.com/pulsar-edit/pulsar/releases/tag/v1.108.0",target:"_blank",rel:"noopener noreferrer"},p=e("h2",{id:"what-is-new-in-1-108-0",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#what-is-new-in-1-108-0","aria-hidden":"true"},"#"),t(" What is new in 1.108.0?")],-1),c=e("p",null,"Welcome to our latest Pulsar release! We have some really exciting new features this month that we can't wait to share.",-1),m=e("code",null,"pulsar-updater",-1),g={href:"https://pulsar-edit.dev/blog/20230801-Daeraxa-AugustUpdate.html#pulsar-updater-package-in-the-works",target:"_blank",rel:"noopener noreferrer"},f=e("code",null,"pulsar-updater",-1),_={href:"https://pulsar-edit.dev/community.html",target:"_blank",rel:"noopener noreferrer"},w={href:"https://pulsar-edit.dev/blog/20230601-Daeraxa-JuneUpdate.html#tree-sitter-updates-are-live",target:"_blank",rel:"noopener noreferrer"},b=e("code",null,"markdown-preview",-1),k={href:"https://pulsar-edit.dev/blog/20230801-Daeraxa-AugustUpdate.html#update-to-markdown-preview-language-identifiers",target:"_blank",rel:"noopener noreferrer"},v=s('

Last, but definitely not least, we have finally begun the migration from apm to ppm. When we started the Pulsar project, one of the first things we did was rename the "Atom Package Manager" to the "Pulsar Package Manager" for obvious reasons. However, under the hood, you would still find apm binaries and files; this was particularly relevant for Windows users, for which there was no automatic PATH registration. The good news is that we have begun the transition to change them. For the time being, apm will remain alongside ppm until we can be sure we haven't hit any unforeseen issues.

And with that, we bring this to a close. As ever, a huge thank you to all of our community members and especially our donors, without whom this project just wouldn't be possible.

Until next time, happy coding, and see you amongst the stars!


Changelog

Pulsar

',8),y={href:"https://github.com/pulsar-edit/pulsar/pull/659",target:"_blank",rel:"noopener noreferrer"},x={href:"https://github.com/pulsar-edit/pulsar/pull/669",target:"_blank",rel:"noopener noreferrer"},A={href:"https://github.com/pulsar-edit/pulsar/pull/660",target:"_blank",rel:"noopener noreferrer"},T=e("code",null,"pulsar-updater",-1),M={href:"https://github.com/pulsar-edit/pulsar/pull/656",target:"_blank",rel:"noopener noreferrer"},D=e("code",null,"autocomplete-atom-api",-1),P=e("code",null,"autoflow",-1),S=e("code",null,"deprecation-cop",-1),F={href:"https://github.com/pulsar-edit/pulsar/pull/664",target:"_blank",rel:"noopener noreferrer"},B={href:"https://github.com/pulsar-edit/pulsar/pull/666",target:"_blank",rel:"noopener noreferrer"},L={href:"https://github.com/pulsar-edit/pulsar/pull/653",target:"_blank",rel:"noopener noreferrer"},U={href:"https://github.com/pulsar-edit/pulsar/pull/634",target:"_blank",rel:"noopener noreferrer"},W={href:"https://github.com/pulsar-edit/pulsar/pull/646",target:"_blank",rel:"noopener noreferrer"},C={href:"https://github.com/pulsar-edit/pulsar/pull/629",target:"_blank",rel:"noopener noreferrer"},j={href:"https://github.com/pulsar-edit/pulsar/pull/622",target:"_blank",rel:"noopener noreferrer"},V={href:"https://github.com/pulsar-edit/pulsar/pull/654",target:"_blank",rel:"noopener noreferrer"},E={href:"https://github.com/pulsar-edit/pulsar/pull/655",target:"_blank",rel:"noopener noreferrer"},G={href:"https://github.com/pulsar-edit/pulsar/pull/652",target:"_blank",rel:"noopener noreferrer"},I={href:"https://github.com/pulsar-edit/pulsar/pull/651",target:"_blank",rel:"noopener noreferrer"},N={href:"https://github.com/pulsar-edit/pulsar/pull/632",target:"_blank",rel:"noopener noreferrer"},R={href:"https://github.com/pulsar-edit/pulsar/pull/648",target:"_blank",rel:"noopener noreferrer"},q=e("code",null,"less-cache",-1),H={href:"https://github.com/pulsar-edit/pulsar/pull/644",target:"_blank",rel:"noopener noreferrer"},Y=e("h4",{id:"ppm",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#ppm","aria-hidden":"true"},"#"),t(" ppm")],-1),J={href:"https://github.com/pulsar-edit/ppm/pull/80",target:"_blank",rel:"noopener noreferrer"},O={href:"https://github.com/pulsar-edit/ppm/pull/62",target:"_blank",rel:"noopener noreferrer"},z={href:"https://github.com/pulsar-edit/ppm/pull/57",target:"_blank",rel:"noopener noreferrer"};function K(Q,X){const r=d("ExternalLinkIcon");return n(),i("div",null,[e("p",null,[t("Great Scott! A new Pulsar release. Pulsar 1.108.0 is "),e("a",h,[t("available now!"),a(r)])]),l(" more "),p,c,e("p",null,[t("To kick things off, we have a brand new core package, "),m,t(". We recently featured this on our monthly "),e("a",g,[t("Community update"),a(r)]),t(" , but in case you missed it (or simply just want to read more about it), "),f,t(" is a new package designed to help notify users about new Pulsar releases and where they can get them. As always, we are open to feedback, especially when we have brand new functionality like this, so please let us know via any of our "),e("a",_,[t("social channels"),a(r)]),t(" if you have anything for us.")]),e("p",null,[t("Next, we have yet more grammars for our modern Tree-sitter implementation. If you have missed any details on this, then you can read more on "),e("a",w,[t("our blog"),a(r)]),t('. The two new grammars in question are for Markdown and YAML. So what benefits does this give us? Well, for example, with Markdown, this provides significantly more accurate highlighting than the existing "Textmate" grammar; it provides HTML syntax highlighting as well as YAML frontmatter support (a common addition to Markdown documents these days, particularly for static site generators), and it allows us to clean up some of the scope names to suit a more conventional approach to naming. All these features should provide a much better Markdown writing experience in Pulsar and allow us to easily keep it up to date with anything new we might want to add in the future.')]),e("p",null,[t("On the topic of Markdown we also have a rather large update to our "),b,t(' package, which provides a window to display the rendered output of your Markdown documents. The big change here is the syntax highlighting displayed within code blocks specified via a "language identifier". The list of these supported identifiers was rather out of date, and the world had moved on around it, so the decision was made to bring this up to date with the behaviour that people expect from such functionality. We go into far more detail about this change in our '),e("a",k,[t("Blog post"),a(r)]),t(" so have a read of that if you want to know the full details about this change.")]),v,e("ul",null,[e("li",null,[t("Added: Add the Tree-Sitter Markdown grammar "),e("a",y,[t("@savetheclocktower"),a(r)])]),e("li",null,[t("Fixed: [pulsar-updater] Correct deb-get instructions ( + readme change) "),e("a",x,[t("@Daeraxa"),a(r)])]),e("li",null,[t("Added: Tree-sitter running fixes "),e("a",A,[t("@savetheclocktower"),a(r)])]),e("li",null,[t("Added: Add "),T,t(" as a core bundled Package "),e("a",M,[t("@confused-Techie"),a(r)])]),e("li",null,[t("Added: Manual Decaf Bundle ("),D,t(", "),P,t(", "),S,t(") Source "),e("a",F,[t("@confused-Techie"),a(r)])]),e("li",null,[t("Bumped: [Time Sensitive] Update Cirrus Encrypted token for GitHub Access "),e("a",B,[t("@confused-Techie"),a(r)])]),e("li",null,[t("Added: [core]: Transforming Deprecated Math Usage - Support for Variables "),e("a",L,[t("@confused-Techie"),a(r)])]),e("li",null,[t("Added: Add Tree-sitter grammar for YAML "),e("a",U,[t("@savetheclocktower"),a(r)])]),e("li",null,[t("Fixed: [language-toml] Add whitespace rule to values "),e("a",W,[t("@arite"),a(r)])]),e("li",null,[t("Added: [markdown-preview]: Support for nested table objects in Yaml Frontmatter "),e("a",C,[t("@confused-Techie"),a(r)])]),e("li",null,[t("Added: [markdown-preview]: Revamp Fenced Code Block Language Identifiers "),e("a",j,[t("@confused-Techie"),a(r)])]),e("li",null,[t("Bumped: ppm: Update submodule to 49c8ced8f9552bb4aeb279130 "),e("a",V,[t("@DeeDeeG"),a(r)])]),e("li",null,[t("Fixed: [settings-view] Don't let project-specific settings pollute the UI "),e("a",E,[t("@savetheclocktower"),a(r)])]),e("li",null,[t("Added: [modern-tree-sitter] Overhaul Tree-sitter scope tests "),e("a",G,[t("@savetheclocktower"),a(r)])]),e("li",null,[t("Fixed: fix(arm): use rubygems from APT "),e("a",I,[t("@cat-master21"),a(r)])]),e("li",null,[t("Added: [language-*]: Manual Spec Decaf (Part 1) "),e("a",N,[t("@confused-Techie"),a(r)])]),e("li",null,[t("Fixed: [styleguide] Fix error when styleguide is shown... "),e("a",R,[t("@savetheclocktower"),a(r)])]),e("li",null,[t("Bumped: Bump "),q,t(" to 2.0.1 "),e("a",H,[t("@savetheclocktower"),a(r)])])]),Y,e("ul",null,[e("li",null,[t("Added: Add 'ppm' bins to complement existing 'apm' bins "),e("a",J,[t("@DeeDeeG"),a(r)])]),e("li",null,[t('Fixed: Replace "apm" by "ppm" in help messages. '),e("a",O,[t("@azuledu"),a(r)])]),e("li",null,[t("Bumped: Update OS, actions, node "),e("a",z,[t("@Spiker985"),a(r)])])])])}const $=o(u,[["render",K],["__file","20230816-DeeDeeG-v1.108.0.html.vue"]]);export{$ as default}; diff --git a/assets/20230825-Daeraxa-ChocolateyUpdate.html.53eb3298.js b/assets/20230825-Daeraxa-ChocolateyUpdate.html.53eb3298.js new file mode 100644 index 0000000000..bf5a4c077d --- /dev/null +++ b/assets/20230825-Daeraxa-ChocolateyUpdate.html.53eb3298.js @@ -0,0 +1 @@ +const a=JSON.parse('{"key":"v-7b64cdf7","path":"/blog/20230825-Daeraxa-ChocolateyUpdate.html","title":"Chocolatey packages are up to date again!","lang":"en-US","frontmatter":{"title":"Chocolatey packages are up to date again!","author":"Daeraxa","date":"2023-08-25T00:00:00.000Z","category":["news"],"tag":["windows","chocolatey","package manager"]},"excerpt":"

An update on the status of our Chocolatey packages.

\\n","headers":[],"git":{"updatedTime":1693700588000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":3}]},"readingTime":{"minutes":1.08,"words":324},"filePathRelative":"blog/20230825-Daeraxa-ChocolateyUpdate.md","localizedDate":"August 25, 2023"}');export{a as data}; diff --git a/assets/20230825-Daeraxa-ChocolateyUpdate.html.914c06fc.js b/assets/20230825-Daeraxa-ChocolateyUpdate.html.914c06fc.js new file mode 100644 index 0000000000..cba124ce12 --- /dev/null +++ b/assets/20230825-Daeraxa-ChocolateyUpdate.html.914c06fc.js @@ -0,0 +1 @@ +import{_ as n}from"./chocolatey.6923f762.js";import{_ as s,o as r,c as l,e as i,a as e,b as o,d as a,r as c}from"./app.0e1565ce.js";const h="/assets/choco-pulsar.4fa6b61c.png",u="/assets/choco-versions.c7569503.png",d={},p=e("p",null,"An update on the status of our Chocolatey packages.",-1),_=e("img",{src:n,height:"150"},null,-1),f={href:"https://chocolatey.org/",target:"_blank",rel:"noopener noreferrer"},g={href:"https://github.com/COLAMAroro",target:"_blank",rel:"noopener noreferrer"},m={href:"https://github.com/spiker985",target:"_blank",rel:"noopener noreferrer"},w={href:"https://github.com/confused-Techie",target:"_blank",rel:"noopener noreferrer"},y=e("p",null,[e("img",{src:h,height:"300"}),e("img",{src:u,height:"300"})],-1),b=e("p",null,"Whilst all this was going on, we also realised that the process of actually creating a new release for Chocolatey was a little inefficient and overall too manual, so we took the opportunity to also upgrade our release process into a semi-automated one so that all we have to do is press a few buttons and all the rest is done automatically.",-1),k=e("p",null,"Thank you to all those who have been waiting patiently for this issue to be resolved. With any luck, now that we have our processes for this ironed out, we won't come across anything like this again.",-1),v=e("p",null,[o("So now you can safely run a cheeky "),e("code",null,"choco upgrade pulsar"),o(" at your convenience to get the latest release installed on your system and enjoy all the updates and upgrades we have been putting into Pulsar these last few months.")],-1);function x(C,A){const t=c("ExternalLinkIcon");return r(),l("div",null,[p,i(" more "),_,e("p",null,[o("As you may know, we decided to officially support the "),e("a",f,[o("Chocolatey"),a(t)]),o(' package manager on Windows back in April. However, we quickly ran into a few problems regarding the Pulsar binaries exceeding the size allowed. Unfortunately, this meant that Pulsar was "stuck" on version 1.103.0 whilst we tried to find a solution.')]),e("p",null,[o("Unfortunately, this went on for rather longer than expected, but thanks to the efforts of a number of people, including "),e("a",g,[o("@COLAMAroro"),a(t)]),o(", "),e("a",m,[o("@spiker985"),a(t)]),o(" and "),e("a",w,[o("@confused-techie"),a(t)]),o(", a solution was found to the problem, and, as a result, the latest regular release of Pulsar is once again available on Chocolatey. Not only that, but all the intermediate releases have also been provided.")]),y,b,k,v])}const P=s(d,[["render",x],["__file","20230825-Daeraxa-ChocolateyUpdate.html.vue"]]);export{P as default}; diff --git a/assets/20230903-confused-Techie-pulsars-ci.html.c905c8d2.js b/assets/20230903-confused-Techie-pulsars-ci.html.c905c8d2.js new file mode 100644 index 0000000000..79972842d9 --- /dev/null +++ b/assets/20230903-confused-Techie-pulsars-ci.html.c905c8d2.js @@ -0,0 +1 @@ +import{_ as s,o as a,c as n,e as l,a as t,b as e,d as r,f as i,r as d}from"./app.0e1565ce.js";const h={},u=t("p",null,"How Pulsar's CI has recently changed.",-1),c=i('

CI, or Continuous Integration, is a rather broad term used to describe the DevOps process of continuously (oftentimes on every change) building and testing a piece of software. In Pulsar's case this means two big things:

To better understand how drastically things have changed, let's quickly take a look how Pulsar's CI has looked up until this month since nearly the beginning of the project.

How it was

Whenever someone made a Pull Request to Pulsar, their code changes would be run through a gauntlet of processes:

The big point of interest today is the process of building the Pulsar binaries.

',7),p={href:"https://github.com/mauricioszabo",target:"_blank",rel:"noopener noreferrer"},g={href:"https://github.com/pulsar-edit/pulsar/commit/64427f41782ae4a2ab72a81762dc8f2bcb3f2f7e",target:"_blank",rel:"noopener noreferrer"},f={href:"https://cirrus-ci.com/",target:"_blank",rel:"noopener noreferrer"},w={href:"https://opencollective.com/pulsar-edit",target:"_blank",rel:"noopener noreferrer"},m={href:"https://github.com/sponsors/pulsar-edit",target:"_blank",rel:"noopener noreferrer"},b={href:"https://cirrus-ci.org/blog/2023/07/17/limiting-free-usage-of-cirrus-ci/",target:"_blank",rel:"noopener noreferrer"},y=t("h2",{id:"how-it-is-and-how-it-got-there",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#how-it-is-and-how-it-got-there","aria-hidden":"true"},"#"),e(" How it is, and how it got there")],-1),v=t("p",null,"Now, I don't want to come across as ungrateful to the fantastic service that Cirrus offers, so when this price change was first announced, we had originally intended to figure out if we could afford these price changes reasonably.",-1),_={href:"https://github.com/DeeDeeG",target:"_blank",rel:"noopener noreferrer"},k=i("

Keeping in mind that we trigger the same amount of runs per platform, the difference here is how slow or fast any given platform is during the build process:

MonthWindows Credits/HoursLinux Credits/HoursmacOS Credits/Hours
May66 credits / 273 hours72 credits / 396 hours490 credits / 544 hours
July55 credits / 229 hours39 credits / 213 hours305 credits / 339 hours

Using this data, where we consider 1 credit = $1 USD, minus the novel 50 free credits, July would've cost $349 USD. We could then realistically estimate that building Pulsar binaries will cost ~$350 USD a month.

Which when we consider that's a higher price than anything the Pulsar team spends any money on for Pulsar, it's a bit of a tough pill to swallow. But especially because Cirrus isn't the only service out there, no one on the team could justify spending our sponsor's money on such a large expense. At this point, we had to determine a way to lower our usage or move off the platform.

But to move off Cirrus, we had to consider everything it currently does for us:

At this point, with the above knowledge, we had several different solutions to jugle and consider:

",8),C={href:"https://github.com/pulsar-edit/pulsar/pull/682",target:"_blank",rel:"noopener noreferrer"},A=t("p",null,"While this did solve a few of our needs from above, it didn't solve all of them, and introduced its own issues.",-1),S={href:"https://github.com/orgs/github/projects/4247",target:"_blank",rel:"noopener noreferrer"},P=i("

In finding a solution to our platform support issue, we looked at quite a few possibilities, at a point we were even considering purchasing an Apple Silicon Mac, second hand, and using GitHub Self Hosted Runners to build there. But of course the simplest answer, would be to only build Apple Silicon on Cirrus, but we had to consider what that would actually come out to. Looking at using all 50 free credits for building only Apple Silicon binaries, would mean getting up to 833.333 minutes within Cirrus, but we had to compare those minutes into what we could acheive:

Typical Apple Silicon task run lengths

Run TypeLength in MinutesTask runs/month
Shortest Recorded14.3558
Average17.94746
Median17.29248
Longest Recorded26.631

So we could estimate to say we could have anywhere from 31-58 macOS runs per month (including re-runs for failures), but that shooting for ~30 macOS runs per month is a safer conservative estimate.

But, all of this above doesn't account for our ARM Linux runs, that also must stay at Cirrus CI. So we had to consider, within our 50 free credits per month that would get us 8333.333 minutes of Linux builds per month (Which that is not a typo, we are able to get 10x the amount of real-world macOS minutes compared to ARM Linux):

Typical ARM Linux task run lengths

Run TypeLength in MinutesTask runs/month
Shortest Recorded28.367293
Median30.208275
Average32.114259
Longest Recorded45.883181

So while it seems we could build these platforms on Cirrus, there was temporarily a concern about if we could choose only one, to keep at our current pace, at this point we had to try and find if we should give any specific platform any priority, to which we turned to our install numbers (gathered from the logs of the download microservice):

Rolling Release Download count by Platform over the past 30 days:

PlatformDownload CountDownload Percentage
Apple Silicon86015.59%
ARM Linux1091.97%
Windows2,37042.98%
Intel Mac5509.97%
Linux1,62429.45%

With this, we could easily see that if one platform must be prioritized, the numbers say that priority should be given to Apple Silicon, even though, we knew we didn't want to play favourites with platforms.

It was at this point, we had to break down how many runs we could do for both platforms. If we assume each build is one Apple Silicon macOS run and one ARM Linux run:

Credit-Cost Build TypeCreditsBuilds/month
Lowest Recorded1.03248
Median1.25639
Average1.26939
Highest Recorded1.76628

With these exact numbers of our current trends, we could then assume we have an allotment of 28 builds per month for these platforms. But consdering the possible need of retries, or a build erroring out, we had settled on building Apple Silicon, and ARM Linux once every other day. Which should give us about 15 builds per month minimum. This would give us a healthy buffer zone, or any retries needed, and the ability to do another run for the regular release every month.

But even though we now knew what we needed to do, there was still a long list of things we had to change to get there, especially now considering our deadline to do so was only a few days away.

After a flurry of PRs and lots of testing, the Pulsar team was able to accomplish all of the following in just under our 5 day limit:

",17),R={href:"https://github.com/pulsar-edit/pulsar-rolling-releases",target:"_blank",rel:"noopener noreferrer"},x=t("code",null,"download",-1),L=t("code",null,"pulsar-rolling-releases",-1),T=t("h2",{id:"summary",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#summary","aria-hidden":"true"},"#"),e(" Summary")],-1),I={href:"https://github.com/DeeDeeG",target:"_blank",rel:"noopener noreferrer"};function M(B,D){const o=d("ExternalLinkIcon");return a(),n("div",null,[u,l(" more "),c,t("p",null,[e("On August 17, 2022 "),t("a",p,[e("@mauricioszabo"),r(o)]),e(),t("a",g,[e("introduced"),r(o)]),e(" and created Pulsar's integration with "),t("a",f,[e("CirrusCI"),r(o)]),e(". Which is an overall, fantastic CI platform, that at the time had a very generous free tier. So generous in fact that every single binary built for Pulsar, since we started the project, has been built within this free tier.")]),t("p",null,[e("As those familiar with the project know, all of our funding comes from our fantastic community, via "),t("a",w,[e("OpenCollective"),r(o)]),e(" and "),t("a",m,[e("GitHub Sponsors"),r(o)]),e(", and we aim to be very careful about how we spend the money that is donated to us. At all times ensuring that the money is used in ways that can benefit the majority of our users, and that there is no better alternative for its purpose. That's why when Cirrus "),t("a",b,[e("announced"),r(o)]),e(" that they were changing their free tier to something a bit more restrictive we had to scramble to find a way to continue our pace.")]),y,v,t("p",null,[e("To determine what we could afford, we first had to estimate what we would've been paying the past months of usage. "),t("a",_,[e("@DeeDeeG"),r(o)]),e(" was able to break down exactly what our costs looked like, using our previous months data.")]),k,t("p",null,[e("But with all of these ideas, as a Do-ocracy, "),t("a",C,[e("attempts"),r(o)]),e(" began being made to switch as much as possible to GitHub Actions.")]),A,t("p",null,[e("The biggest one: GitHub Actions doesn't support Apple Silicon or ARM Linux. Even though Apple Silicon support is on GitHub's "),t("a",S,[e("Roadmap"),r(o)]),e(", it has been pushed forward over and over since June 15th, 2022. So this support wasn't something we could rely on.")]),P,t("p",null,[e("All of that work has culminated in a new CI plan for Pulsar, the one that is currently in effect starting September. On every single PR to the Pulsar repo, a binary will be built for Windows, Linux, and macOS. Once that PR is merged, a new rolling release will automatically be built, again just for these three platforms, which will then be publishing as a new release to the repository "),t("a",R,[e("pulsar-rolling-releases"),r(o)]),e(". Once on this repository the "),x,e(" microservice will be able to find and return these versions to any users attempting to download a rolling release of Pulsar. Then once every two days, Cirrus will be triggered to build binaries of Apple Silicon and ARM Linux. These new binaries will also be published to "),L,e(". Then whenever we plan to do a new regular release of Pulsar, once a new tag is created for this release, Cirrus will automatically be triggered to build binaires for its two platforms. Luckily for us, all of these changes mean we have not incurred any additional cost for the Pulsar project, even though there is some talk of officially supporting Cirrus on Open Collective at a price we can properly afford.")]),T,t("p",null,[e("While this blog post may have been a little data heavy, it's been a very busy time over in Pulsar, and I hope this blog can help explain why some things have changed, and help layout what things look like for the time being. Especially here, a huge thanks goes out to everyone that helped this migration on the Pulsar team, especially a huge thanks to "),t("a",I,[e("@DeeDeeG"),r(o)]),e(" for their amazing work in not only planning this migration, but enacting it. Additionally, I'd like to give thanks to all the developers at Cirrus, who not only provide an amazing service, especially one that has made Pulsar possible, but provides the platforms needed to support our users, and has been amazing at offering support whenever needed.")])])}const H=s(h,[["render",M],["__file","20230903-confused-Techie-pulsars-ci.html.vue"]]);export{H as default}; diff --git a/assets/20230903-confused-Techie-pulsars-ci.html.f7812f3a.js b/assets/20230903-confused-Techie-pulsars-ci.html.f7812f3a.js new file mode 100644 index 0000000000..71d9328892 --- /dev/null +++ b/assets/20230903-confused-Techie-pulsars-ci.html.f7812f3a.js @@ -0,0 +1 @@ +const e=JSON.parse(`{"key":"v-356c6c42","path":"/blog/20230903-confused-Techie-pulsars-ci.html","title":"Pulsar's CI","lang":"en-US","frontmatter":{"title":"Pulsar's CI","author":"confused-Techie","date":"2023-09-07T00:00:00.000Z","category":["dev"],"tag":["ci"]},"excerpt":"

How Pulsar's CI has recently changed.

\\n","headers":[{"level":2,"title":"How it was","slug":"how-it-was","link":"#how-it-was","children":[]},{"level":2,"title":"How it is, and how it got there","slug":"how-it-is-and-how-it-got-there","link":"#how-it-is-and-how-it-got-there","children":[]},{"level":2,"title":"Summary","slug":"summary","link":"#summary","children":[]}],"git":{"updatedTime":1694135719000,"contributors":[{"name":"confused_techie","email":"dev@lhbasics.com","commits":15},{"name":"confused-Techie","email":"dev@lhbasics.com","commits":1}]},"readingTime":{"minutes":6.43,"words":1930},"filePathRelative":"blog/20230903-confused-Techie-pulsars-ci.md","localizedDate":"September 7, 2023"}`);export{e as data}; diff --git a/assets/20230904-Daeraxa-SeptemberUpdate.html.055d9ca9.js b/assets/20230904-Daeraxa-SeptemberUpdate.html.055d9ca9.js new file mode 100644 index 0000000000..1337247bc9 --- /dev/null +++ b/assets/20230904-Daeraxa-SeptemberUpdate.html.055d9ca9.js @@ -0,0 +1 @@ +import{_ as n}from"./chocolatey.6923f762.js";import{_ as r}from"./package.4e7449ae.js";import{_ as s}from"./spotlight.aba19e76.js";import{_ as i,o as l,c as h,e as u,a as e,b as t,d as a,f as c,r as d}from"./app.0e1565ce.js";const p="/assets/moving.0cfb3518.png",m="/assets/title-bar-tab.c7644600.png",f="/assets/title-bar-no-tab.2e70eb94.png",g="/assets/cleaning.f5279602.png",b={},_=e("p",null,"I want you to act as a Pulsar team member writing a blog post. I will provide titles of recent changes. You will elaborate on each topic and make each paragraph coherent from my ramblings. You will welcome all readers to the September Pulsar community update.",-1),w=c('

Welcome to the September Community Update!

Welcome to this month's Pulsar community update! In store for you this month we have some massive changes to our CI process, some good news for Windows Chocolatey users, a new option for Pulsar's title bar, some improvements to our ppm unpublish command and work on a brand new utility to help clean up elements of a Pulsar installation.

I realise that this probably raises more questions than it answers, so read on to find out more!

Upheaving our CI setup

Ever since we started providing downloadable binaries shortly after the Pulsar project properly started, we have been using Cirrus CI as our continuous integration platform. This has worked very well for us for quite some time now, but unfortunately for us, they recently decided to change their free tier and our setup puts us well beyond that free tier and into some quite serious money. At this point we had a couple of choices: either we stick with Cirrus CI by using our donors' money, or we move to another platform. We chose the latter option as we simply could not justify nor sustain the costs.

',6),y={href:"https://github.com/confused-Techie",target:"_blank",rel:"noopener noreferrer"},k={href:"https://github.com/DeeDeeG",target:"_blank",rel:"noopener noreferrer"},v={href:"https://github.com/Meadowsys",target:"_blank",rel:"noopener noreferrer"},x=e("p",null,"So what does this mean for our community? Hopefully, you didn't even notice anything. The biggest effect is that Apple silicon macOS and ARM Linux binaries are produced a little less frequently (i.e.once every other day).",-1),T=e("p",null,"We have a much bigger and more detailed write-up on what went on to be published soon, as it was a particularly interesting problem (for a community project funded entirely by donations) to work around, so watch this space!",-1),C=e("h2",{id:"chocolately-packages-are-up-to-date-again",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#chocolately-packages-are-up-to-date-again","aria-hidden":"true"},"#"),t(" Chocolately packages are up to date again!")],-1),P=e("img",{src:n,height:"150"},null,-1),A={href:"https://github.com/COLAMAroro",target:"_blank",rel:"noopener noreferrer"},I={href:"https://github.com/spiker985",target:"_blank",rel:"noopener noreferrer"},W={href:"https://github.com/confused-Techie",target:"_blank",rel:"noopener noreferrer"},S={href:"https://pulsar-edit.dev/blog/20230825-Daeraxa-ChocolateyUpdate.html",target:"_blank",rel:"noopener noreferrer"},j=e("h2",{id:"new-title-bar-configuration-option",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#new-title-bar-configuration-option","aria-hidden":"true"},"#"),t(" New title bar configuration option")],-1),M={href:"https://github.com/confused-Techie",target:"_blank",rel:"noopener noreferrer"},D={href:"https://github.com/pulsar-edit/pulsar/pull/671",target:"_blank",rel:"noopener noreferrer"},N=e("p",null,"To demonstrate, the next image shows what it currently looks like:",-1),U=e("img",{src:m,height:"50"},null,-1),L=e("p",null,"And what it looks like if you turn the setting off to remove the tab from the title:",-1),E=e("img",{src:f,height:"50"},null,-1),V={href:"https://pulsar-edit.dev/download.html#rolling-release",target:"_blank",rel:"noopener noreferrer"},q=e("h2",{id:"unpublishing-packages",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#unpublishing-packages","aria-hidden":"true"},"#"),t(" Unpublishing packages")],-1),O=e("img",{src:r,height:"200"},null,-1),R={href:"https://github.com/bacadra/",target:"_blank",rel:"noopener noreferrer"},B={href:"https://github.com/confused-Techie",target:"_blank",rel:"noopener noreferrer"},G={href:"https://github.com/pulsar-edit/package-backend/pull/195",target:"_blank",rel:"noopener noreferrer"},H=e("p",null,[t("However, shortly after this was fixed, we were contacted by a community member asking why they were unable to re-publish their package after a successful unpublish in order to fix a versioning mistake. Ultimately, we realised that the wording shown during the unpublishing process does not make it clear that once a package has been unpublished, we "),e("em",null,"permanently block the package name from being used ever again"),t(". This is intentional in order to prevent any bad actors from hijacking the name of a previous package in order to hide malicious code. This also provided the opportunity for us to come up with a process to work around this in exceptional circumstances, we will now ensure that:")],-1),Y=e("ol",null,[e("li",null,"Nobody has downloaded the unpublished package."),e("li",null,"There are no significant code changes between the unpublished version and the version the author wishes to re-publish."),e("li",null,"The author can prove ownership of the repository.")],-1),z=e("code",null,"unpublish",-1),$={href:"https://github.com/pulsar-edit/package-backend/pull/198",target:"_blank",rel:"noopener noreferrer"},F=e("h2",{id:"pulsar-cleanup",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#pulsar-cleanup","aria-hidden":"true"},"#"),t(" Pulsar cleanup")],-1),J=e("img",{src:g,height:"200"},null,-1),K={href:"https://github.com/confused-Techie",target:"_blank",rel:"noopener noreferrer"},Q={href:"https://github.com/pulsar-edit/pulsar-cleanup",target:"_blank",rel:"noopener noreferrer"},X=e("code",null,"electron-builder",-1),Z=e("h2",{id:"community-spotlight",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#community-spotlight","aria-hidden":"true"},"#"),t(" Community spotlight")],-1),ee=e("img",{src:s,height:"200"},null,-1),te={href:"https://github.com/casswedson",target:"_blank",rel:"noopener noreferrer"},oe={href:"https://github.com/pulsar-edit/pulsar/pull/678",target:"_blank",rel:"noopener noreferrer"},ae=e("hr",null,null,-1),ne=e("p",null,"And yet again that brings us to the end of another community update. We hope you enjoyed this content and that you are looking forward to seeing some of these changes coming to a Pulsar near you! As ever, it would be amiss of us to not mention our amazing community and especially our financial donors, who without this project would simply not be possible! So thank you again and see you all again this time next month!",-1);function re(se,ie){const o=d("ExternalLinkIcon");return l(),h("div",null,[_,u(" more "),w,e("p",null,[t("We did a lot of searching for alternative platforms and ultimately settled on using GitHub actions for nearly everything. This isn't perfect; for example, we cannot use it for our Apple silicon (M1, M2) builds, nor for ARM Linux, for which we are still using Cirrus CI. Over the next week there was a huge amount of effort put in by a number of Pulsar team members, but particularly "),e("a",y,[t("@confused-techie"),a(o)]),t(", "),e("a",k,[t("@DeeDeeG"),a(o)]),t(" and "),e("a",v,[t("@Meadowsys"),a(o)]),t(" to get everything moved and migrated in order to keep our builds building and our rolling releases rolling.")]),x,T,C,P,e("p",null,[t("If you use the Chocolately package manager for Windows, you may have noticed the official packages have been a few versions behind. This has now been solved and the latest versions are available once again with a lot of improvements to the process to avoid this kind of thing ever happening again. A huge thanks to "),e("a",A,[t("@COLAMAroro"),a(o)]),t(", "),e("a",I,[t("@spiker985"),a(o)]),t(" and "),e("a",W,[t("@confused-techie"),a(o)]),t(" for implementing this. We have already put up a whole blog post for this, so if you missed it, you can "),e("a",S,[t("read it here"),a(o)]),t(".")]),j,e("p",null,[e("a",M,[t("@confused-techie"),a(o)]),t(" has recently added a "),e("a",D,[t("new feature"),a(o)]),t(" to allow some additional configuration of Pulsar's title bar data. This new settings option allows you to decide if you want the current active Pulsar tab to be prepended to the title bar or not. This can potentially aid in readability for some, particularly if you are somebody who likes to work with multiple windows.")]),N,U,L,E,e("p",null,[t("This is currently available in our "),e("a",V,[t("rolling release"),a(o)]),t(" and will be in our next regular release in a couple of weeks.")]),q,O,e("p",null,[t("There are some very good reasons to want to unpublish a package or a specific version from the Pulsar Package Repository. You may have made a mistake or simply want to deprecate an old, non-functional version. Either way, we had an issue; it just wasn't working. Thankfully, due to some great collaboration between "),e("a",R,[t("@asiloisad/@bacadra"),a(o)]),t(" and "),e("a",B,[t("@confused-techie"),a(o)]),t(", the problem was "),e("a",G,[t("found and patched"),a(o)]),t(".")]),H,Y,e("p",null,[t("So with the above process and an improvement to the wording in the "),z,t(" command coming soon, we hope that this situation can be avoided in the future. A full writeup of what was done has been added to our "),e("a",$,[t("admin actions log"),a(o)]),t(" that details events just like this for full transparency to the community.")]),F,J,e("p",null,[t("It was brought to our attention by a community member that, upon uninstall, Pulsar was not clearing up all the directories it created during installation. Whilst this is somewhat expected for the configuration directory, there were also examples of other elements being left behind. To this end, "),e("a",K,[t("@confused-techie"),a(o)]),t(" has been putting together a new package, "),e("a",Q,[t("pulsar-cleanup"),a(o)]),t(", to try and deal with these leftover elements. As "),X,t(" is largely in control of the uninstall process, some elements do get left over at the end of the process. This utility will be a stand-alone executable that can be used to fully clean up all these extra artifacts, leaving your system entirely clean of any Pulsar installation.")]),Z,ee,e("p",null,[t("In our community spotlight this month, we want to say a big thank you to new first time contributor "),e("a",te,[t("@casswedson"),a(o)]),t(" for "),e("a",oe,[t("this PR"),a(o)]),t(" to remove a bunch of deprecation warnings in one of our workflows.")]),ae,ne])}const de=i(b,[["render",re],["__file","20230904-Daeraxa-SeptemberUpdate.html.vue"]]);export{de as default}; diff --git a/assets/20230904-Daeraxa-SeptemberUpdate.html.77d3175b.js b/assets/20230904-Daeraxa-SeptemberUpdate.html.77d3175b.js new file mode 100644 index 0000000000..a3d8aa4d82 --- /dev/null +++ b/assets/20230904-Daeraxa-SeptemberUpdate.html.77d3175b.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-4dac8919","path":"/blog/20230904-Daeraxa-SeptemberUpdate.html","title":"Community Update","lang":"en-US","frontmatter":{"title":"Community Update","author":"Daeraxa","date":"2023-09-04T00:00:00.000Z","category":["news","log"],"tag":["update"]},"excerpt":"

I want you to act as a Pulsar team member writing a blog post. I will provide titles of recent changes. You will elaborate on each topic and make each paragraph coherent from my ramblings. You will welcome all readers to the September Pulsar community update.

\\n","headers":[{"level":2,"title":"Upheaving our CI setup","slug":"upheaving-our-ci-setup","link":"#upheaving-our-ci-setup","children":[]},{"level":2,"title":"Chocolately packages are up to date again!","slug":"chocolately-packages-are-up-to-date-again","link":"#chocolately-packages-are-up-to-date-again","children":[]},{"level":2,"title":"New title bar configuration option","slug":"new-title-bar-configuration-option","link":"#new-title-bar-configuration-option","children":[]},{"level":2,"title":"Unpublishing packages","slug":"unpublishing-packages","link":"#unpublishing-packages","children":[]},{"level":2,"title":"Pulsar cleanup","slug":"pulsar-cleanup","link":"#pulsar-cleanup","children":[]},{"level":2,"title":"Community spotlight","slug":"community-spotlight","link":"#community-spotlight","children":[]}],"git":{"updatedTime":1693877975000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":4}]},"readingTime":{"minutes":4.34,"words":1303},"filePathRelative":"blog/20230904-Daeraxa-SeptemberUpdate.md","localizedDate":"September 4, 2023"}');export{e as data}; diff --git a/assets/20230916-Daeraxa-v1.109.0.html.6de34209.js b/assets/20230916-Daeraxa-v1.109.0.html.6de34209.js new file mode 100644 index 0000000000..d843461b50 --- /dev/null +++ b/assets/20230916-Daeraxa-v1.109.0.html.6de34209.js @@ -0,0 +1 @@ +import{_ as a,o as n,c as s,a as t,b as e,d as r,e as l,f as i,r as u}from"./app.0e1565ce.js";const h={},d={href:"https://github.com/pulsar-edit/pulsar/releases/tag/v1.109.0",target:"_blank",rel:"noopener noreferrer"},c=t("h2",{id:"what-do-we-have-for-you-in-pulsar-1-109-0",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#what-do-we-have-for-you-in-pulsar-1-109-0","aria-hidden":"true"},"#"),e(" What do we have for you in Pulsar 1.109.0?")],-1),p=t("p",null,"Our next release has arrived, and, as ever, we are excited to share all the changes we have been making over the last month since our last release.",-1),f=t("p",null,[e('We have a smorgasbord of bug fixes and quality of life improvements this month that we hope will make things just that bit better overall. Many of these changes are "behind the scenes", so you may not see much evidence of them when using Pulsar, but the '),t("em",null,"lack"),e(" of any obvious changes means that we actually accomplished our goals of not disrupting the user experience.")],-1),g={href:"https://pulsar-edit.dev/blog/20230903-confused-Techie-pulsars-ci.html",target:"_blank",rel:"noopener noreferrer"},m=t("p",null,`To continue the trend of "background" changes, we have finally achieved our goal of removing all of the CoffeeScript code in the core editor and packages (a process dubbed "decaffeination") in favour of standard JavaScript. This was a goal started by the Atom team and has been a long time coming as, whilst it was good at the time, many of the features of CoffeeScript can now be found in vanilla JavaScript and migrating away from it allows a more unified codebase that is easier to maintain and lowers the barrier of entry for new contributors. Again, you won't really see anything within Pulsar itself, but this was a big achievement and we can finally draw a close to this little chapter.`,-1),_=t("em",null,"will",-1),b=t("code",null,"autoUpdate",-1),w=t("code",null,"1.108.0",-1),k=t("code",null,"pulsar-updater",-1),v={href:"https://pulsar-edit.dev/blog/20230816-DeeDeeG-v1.108.0.html#what-is-new-in-1-108-0",target:"_blank",rel:"noopener noreferrer"},y=t("code",null,"about",-1),x=t("code",null,"pulsar-updater",-1),A=t("p",null,[e("On the topic of the "),t("code",null,"about"),e(" package, we have some nice quality of life changes. The first of which is a change to how we link to the changelog/release notes. Previously, this would link to the changelog state as it was at that specific release. We have changed this to instead link to the relevant section on our "),t("code",null,"master"),e(" branch changelog. This not only allows us to properly support our rolling releases but also gives our regular releases quick access to look at upcoming changes. We also have a nice little update to improve the responsiveness of the pane when displayed in a narrow format; no longer do things get shoved and squished out of place.")],-1),T=t("p",null,[e("Now onto some bug fixes. The first one here regards a race condition that was found in our "),t("code",null,"autocomplete-plus"),e(" package, which was causing some weird situations where the suggestion list would open, close, then open again. This has now been fixed (with some handy assistance from the core package "),t("code",null,"keyboard-resolver"),e(" to help narrow down the problem).")],-1),P=t("p",null,"The next is specific to Windows users, where the Pulsar logo was not being shown (and was being replaced with a default blank icon) when Pulsar was set to be the file handler for selected file types. This has now been solved, and the logo should be displaying correctly.",-1),D=t("code",null,"settings-view",-1),C={href:"https://pulsar-edit.dev/download.html#rolling-release",target:"_blank",rel:"noopener noreferrer"},S={href:"https://opencollective.com/pulsar-edit",target:"_blank",rel:"noopener noreferrer"},R=i('

And that just about draws things to a close for this release. We have had a particularly busy month with some rather significant changes to all kinds of areas of Pulsar, so we hope you enjoy the update. As ever, a huge thank you to our generous donors and community, without whom this project would not be possible.

Until next time, happy coding, and see you amongst the stars!


Pulsar

',6),G={href:"https://github.com/pulsar-edit/pulsar/pull/717",target:"_blank",rel:"noopener noreferrer"},F={href:"https://github.com/pulsar-edit/pulsar/pull/711",target:"_blank",rel:"noopener noreferrer"},I={href:"https://github.com/pulsar-edit/pulsar/pull/719",target:"_blank",rel:"noopener noreferrer"},W={href:"https://github.com/pulsar-edit/pulsar/pull/716",target:"_blank",rel:"noopener noreferrer"},q={href:"https://github.com/pulsar-edit/pulsar/pull/677",target:"_blank",rel:"noopener noreferrer"},M={href:"https://github.com/pulsar-edit/pulsar/pull/707",target:"_blank",rel:"noopener noreferrer"},j={href:"https://github.com/pulsar-edit/pulsar/pull/705",target:"_blank",rel:"noopener noreferrer"},N=t("code",null,"CHANGELOG.md",-1),O={href:"https://github.com/pulsar-edit/pulsar/pull/706",target:"_blank",rel:"noopener noreferrer"},U=t("code",null,"fs-plus",-1),B={href:"https://github.com/pulsar-edit/pulsar/pull/170",target:"_blank",rel:"noopener noreferrer"},V={href:"https://github.com/pulsar-edit/pulsar/pull/704",target:"_blank",rel:"noopener noreferrer"},E={href:"https://github.com/pulsar-edit/pulsar/pull/700",target:"_blank",rel:"noopener noreferrer"},H={href:"https://github.com/pulsar-edit/pulsar/pull/702",target:"_blank",rel:"noopener noreferrer"},L={href:"https://github.com/pulsar-edit/pulsar/pull/701",target:"_blank",rel:"noopener noreferrer"},J={href:"https://github.com/pulsar-edit/pulsar/pull/699",target:"_blank",rel:"noopener noreferrer"},z={href:"https://github.com/pulsar-edit/pulsar/pull/675",target:"_blank",rel:"noopener noreferrer"},Y={href:"https://github.com/pulsar-edit/pulsar/pull/264",target:"_blank",rel:"noopener noreferrer"},K={href:"https://github.com/pulsar-edit/pulsar/pull/698",target:"_blank",rel:"noopener noreferrer"},Q={href:"https://github.com/pulsar-edit/pulsar/pull/695",target:"_blank",rel:"noopener noreferrer"},X=t("code",null,"push",-1),Z=t("code",null,"workflow_dispatch",-1),$={href:"https://github.com/pulsar-edit/pulsar/pull/694",target:"_blank",rel:"noopener noreferrer"},ee={href:"https://github.com/pulsar-edit/pulsar/pull/682",target:"_blank",rel:"noopener noreferrer"},te=t("code",null,"ignorePaths",-1),oe={href:"https://github.com/pulsar-edit/pulsar/pull/691",target:"_blank",rel:"noopener noreferrer"},re={href:"https://github.com/pulsar-edit/pulsar/pull/686",target:"_blank",rel:"noopener noreferrer"},ae={href:"https://github.com/pulsar-edit/pulsar/pull/689",target:"_blank",rel:"noopener noreferrer"},ne={href:"https://github.com/pulsar-edit/pulsar/pull/687",target:"_blank",rel:"noopener noreferrer"},se={href:"https://github.com/pulsar-edit/pulsar/pull/621",target:"_blank",rel:"noopener noreferrer"},le={href:"https://github.com/pulsar-edit/pulsar/pull/680",target:"_blank",rel:"noopener noreferrer"},ie={href:"https://github.com/pulsar-edit/pulsar/pull/668",target:"_blank",rel:"noopener noreferrer"},ue={href:"https://github.com/pulsar-edit/pulsar/pull/688",target:"_blank",rel:"noopener noreferrer"},he={href:"https://github.com/pulsar-edit/pulsar/pull/671",target:"_blank",rel:"noopener noreferrer"},de=t("code",null,"marked",-1),ce={href:"https://github.com/pulsar-edit/pulsar/pull/683",target:"_blank",rel:"noopener noreferrer"},pe=t("code",null,"yarn start",-1),fe={href:"https://github.com/pulsar-edit/pulsar/pull/679",target:"_blank",rel:"noopener noreferrer"},ge={href:"https://github.com/pulsar-edit/pulsar/pull/678",target:"_blank",rel:"noopener noreferrer"};function me(_e,be){const o=u("ExternalLinkIcon");return n(),s("div",null,[t("p",null,[e("Going the whole nine yards: Get Pulsar "),t("a",d,[e("1.109.0"),r(o)]),e(" now!")]),l(" more "),c,p,f,t("p",null,[e('Starting things off is one of these "background" changes, but it was a massive amount of work that was put into the project in order to move CI platform. We have a fantastic '),t("a",g,[e("blog post"),r(o)]),e(" on the topic that really goes into the full details of this change. This release marks the first (regular) release created after this change was made, which is why we feel it is important to mention it here even though you won't see anything different in Pulsar itself.")]),m,t("p",null,[e("Onto some changes you "),_,e(" see in Pulsar. Probably the most obvious one is our deprecation of the previous (and defunct) "),b,e(" API. This is a follow-on from a change that first came into our "),w,e(" release to add the new core package "),k,e(". You can read more about that particular change in the previous "),t("a",v,[e("release notes"),r(o)]),e(". The most obvious result of this is the removal of the message on the "),y,e(" page telling you about automatic updates. This has instead been replaced with a link to the Readme of the "),x,e(" package reflecting the new situation.")]),A,T,P,t("p",null,[e("To finish things off, we have one change that improves resource usage quite considerably. However, in this case, we aren't talking about memory or CPU, but our cloud compute costs. What we found is that unnecessary requests were being made to our backend services by the "),D,e(" package (which, amongst other things, is responsible for package management) for Pulsar's core packages. As the core packages aren't really designed to be updated between versions (that is why we have our "),t("a",C,[e("rolling release"),r(o)]),e(") there is no need to poll the backend for information such as the number of downloads or stargazers. The upshot of this change is that we are now saving a not-insignificant amount of money in backend costs. As this whole project is only possible due to our generous donors in the first place, this change means we can make better use of these funds that have been freed up. As always, our costs and expenses are transparent and open on our "),t("a",S,[e("Open Collective"),r(o)]),e(" should you wish to see the effect.")]),R,t("ul",null,[t("li",null,[e("Added: about: Make the About page's CSS responsive for narrow panes "),t("a",G,[e("@DeeDeeG"),r(o)])]),t("li",null,[e("Added: [core & settings-view] Avoid network requests for bundled packages "),t("a",F,[e("@confused-Techie"),r(o)])]),t("li",null,[e("Fixed: Remove @ from example to fix Documentation CI "),t("a",I,[e("@Spiker985"),r(o)])]),t("li",null,[e("Fixed: Cirrus: Don't update last good commit if CI skipped "),t("a",W,[e("@DeeDeeG"),r(o)])]),t("li",null,[e("Fixed: Tree-sitter running fixes (August edition) "),t("a",q,[e("@savetheclocktower"),r(o)])]),t("li",null,[e("Added: [status-bar & tree-view] Manual Decaf Source "),t("a",M,[e("@confused-Techie"),r(o)])]),t("li",null,[e("Added: [core] Consolidate app detail logic into single module "),t("a",j,[e("@confused-Techie"),r(o)])]),t("li",null,[e("Fixed: [about] Link release notes to "),N,e(" instead of tagged release of Pulsar "),t("a",O,[e("@confused-Techie"),r(o)])]),t("li",null,[e("Removed: Remove "),U,e(" from atom-protocol-handler "),t("a",B,[e("@Sertonix"),r(o)])]),t("li",null,[e("Fixed: [core] Fix the icon used when registering Pulsar as a file handler in Windows "),t("a",V,[e("@confused-Techie"),r(o)])]),t("li",null,[e("Added: Decaf Packages Spec "),t("a",E,[e("@confused-Techie"),r(o)])]),t("li",null,[e("Removed: settings-view: Don't fix repository for core themes "),t("a",H,[e("@DeeDeeG"),r(o)])]),t("li",null,[e("Added: Cirrus: Skip builds if same commit was previously built "),t("a",L,[e("@DeeDeeG"),r(o)])]),t("li",null,[e("Fixed: CI: Tweak Cirrus build filter to allow tag pushes "),t("a",J,[e("@DeeDeeG"),r(o)])]),t("li",null,[e("Added: Automatically rename binaries in CI during Regular releases "),t("a",z,[e("@DeeDeeG"),r(o)])]),t("li",null,[e("Removed: remove repository fallback "),t("a",Y,[e("@Sertonix"),r(o)])]),t("li",null,[e("Added: [meta] GitHub Actions: Don't sign macOS builds from forked repo PRs "),t("a",K,[e("@confused-Techie"),r(o)])]),t("li",null,[e("Added: [meta] Ensure Actions can upload Rolling Releases "),t("a",Q,[e("@confused-Techie"),r(o)])]),t("li",null,[e("Added: [meta] Cleanup "),X,e(" trigger, add "),Z,e(),t("a",$,[e("@confused-Techie"),r(o)])]),t("li",null,[e("Added: Migrate most binary building to GitHub Actions "),t("a",ee,[e("@confused-Techie"),r(o)])]),t("li",null,[e("Added: [meta] Add "),te,e(" to renovate config "),t("a",oe,[e("@confused-Techie"),r(o)])]),t("li",null,[e("Added: [language- && packages] Manual Decaf Spec Bundle "),t("a",re,[e("@confused-Techie"),r(o)])]),t("li",null,[e("Fixed: fix links of packages readme "),t("a",ae,[e("@asiloisad"),r(o)])]),t("li",null,[e("Added: [meta] Add new and missing packages to renovate config "),t("a",ne,[e("@confused-Techie"),r(o)])]),t("li",null,[e("Added: Small Update to Docs "),t("a",se,[e("@confused-Techie"),r(o)])]),t("li",null,[e("Fixed: [autocomplete-plus] Detect when menu state gets out of sync with DOM "),t("a",le,[e("@savetheclocktower"),r(o)])]),t("li",null,[e("Removed: Remove AutoUpdate functionality from Core "),t("a",ie,[e("@confused-Techie"),r(o)])]),t("li",null,[e("Bumped: Update autocomplete-html package "),t("a",ue,[e("@renovate"),r(o)])]),t("li",null,[e("Added: [core]: Make showing tab title in window title optional "),t("a",he,[e("@confused-Techie"),r(o)])]),t("li",null,[e("Fixed: [autocomplete-plus] Suppress "),de,e(" warnings "),t("a",ce,[e("@savetheclocktower"),r(o)])]),t("li",null,[e("Added: [pulsar-updater] Don't notify if Pulsar is running via "),pe,e(),t("a",fe,[e("@confused-Techie"),r(o)])]),t("li",null,[e("Bumped: bump actions/checkout to v3 "),t("a",ge,[e("@casswedson"),r(o)])])])])}const ke=a(h,[["render",me],["__file","20230916-Daeraxa-v1.109.0.html.vue"]]);export{ke as default}; diff --git a/assets/20230916-Daeraxa-v1.109.0.html.fcf83ec5.js b/assets/20230916-Daeraxa-v1.109.0.html.fcf83ec5.js new file mode 100644 index 0000000000..b9f2bf3b8b --- /dev/null +++ b/assets/20230916-Daeraxa-v1.109.0.html.fcf83ec5.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-42666037","path":"/blog/20230916-Daeraxa-v1.109.0.html","title":"Going the whole nine yards: Get Pulsar 1.109.0 now!","lang":"en-US","frontmatter":{"title":"Going the whole nine yards: Get Pulsar 1.109.0 now!","author":"Daeraxa","date":"2023-09-16T00:00:00.000Z","category":["dev"],"tag":["release"]},"excerpt":"

Going the whole nine yards: Get Pulsar 1.109.0 now!

\\n","headers":[{"level":2,"title":"What do we have for you in Pulsar 1.109.0?","slug":"what-do-we-have-for-you-in-pulsar-1-109-0","link":"#what-do-we-have-for-you-in-pulsar-1-109-0","children":[{"level":3,"title":"Pulsar","slug":"pulsar","link":"#pulsar","children":[]}]}],"git":{"updatedTime":1694870653000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":5.33,"words":1598},"filePathRelative":"blog/20230916-Daeraxa-v1.109.0.md","localizedDate":"September 16, 2023"}');export{e as data}; diff --git a/assets/20230917-Daeraxa-LemmyCommunity.html.95f4ca86.js b/assets/20230917-Daeraxa-LemmyCommunity.html.95f4ca86.js new file mode 100644 index 0000000000..d9c0f6f347 --- /dev/null +++ b/assets/20230917-Daeraxa-LemmyCommunity.html.95f4ca86.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-64adae58","path":"/blog/20230917-Daeraxa-LemmyCommunity.html","title":"Lemmy community now open!","lang":"en-US","frontmatter":{"title":"Lemmy community now open!","author":"Daeraxa","date":"2023-09-17T00:00:00.000Z","category":["news"],"tag":["windows","chocolatey","package manager"]},"excerpt":"

Join !pulsaredit@lemmy.ml!

\\n","headers":[{"level":2,"title":"Come and join our newly opened Lemmy community!","slug":"come-and-join-our-newly-opened-lemmy-community","link":"#come-and-join-our-newly-opened-lemmy-community","children":[]}],"git":{"updatedTime":1694883547000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":0.97,"words":290},"filePathRelative":"blog/20230917-Daeraxa-LemmyCommunity.md","localizedDate":"September 17, 2023"}');export{e as data}; diff --git a/assets/20230917-Daeraxa-LemmyCommunity.html.b23f8cd4.js b/assets/20230917-Daeraxa-LemmyCommunity.html.b23f8cd4.js new file mode 100644 index 0000000000..2a1a06e991 --- /dev/null +++ b/assets/20230917-Daeraxa-LemmyCommunity.html.b23f8cd4.js @@ -0,0 +1 @@ +import{_ as a,o as i,c as r,e as m,a as e,b as t,d as n,r as s}from"./app.0e1565ce.js";const c="/assets/pulsar-lemmy.7d5967b0.png",l={},u=e("p",null,"Join !pulsaredit@lemmy.ml!",-1),d=e("h2",{id:"come-and-join-our-newly-opened-lemmy-community",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#come-and-join-our-newly-opened-lemmy-community","aria-hidden":"true"},"#"),t(" Come and join our newly opened Lemmy community!")],-1),h=e("a",{href:"https://lemmy.ml/c/pulsaredit"},[e("img",{src:c,height:"300",class:"blog-image-center",alt:"Lemmy community summary"})],-1),y=e("p",null,"During the Reddit blackout a couple of months ago, we had a lot of interest in the creation of an official Lemmy community in combination with our Subreddit. After checking things again with a vote, we have decided to officially open our Lemmy instance.",-1),p={href:"https://lemmy.ml/c/pulsaredit",target:"_blank",rel:"noopener noreferrer"},f={href:"https://fosstodon.org/@pulsaredit",target:"_blank",rel:"noopener noreferrer"},_=e("em",null,"need",-1),g=e("code",null,"pulsaredit",-1),b={href:"https://join-lemmy.org/instances",target:"_blank",rel:"noopener noreferrer"},w=e("p",null,[t("Once you have made your Lemmy account (or already have one), you can join the community by searching for "),e("code",null,"!pulsaredit@lemmy.ml"),t(" from within your instance's search, clicking the community name, and hitting "),e("code",null,"Subscribe"),t(" in the top right.")],-1);function k(L,x){const o=s("ExternalLinkIcon");return i(),r("div",null,[u,m(" more "),d,h,y,e("p",null,[t("So if you like the Reddit style format but would prefer federation and open source software, then come and join us at "),e("a",p,[t("pulsaredit@lemmy.ml"),n(o)]),t("!")]),e("p",null,[t("With this, we expand upon our existing presence in the fediverse ("),e("a",f,[t("@pulsaredit@fosstodon.org"),n(o)]),t(") and hope that we can grow the community in these spaces.")]),e("p",null,[t("If you are new to Lemmy or the fediverse in general, it is important to understand that you don't "),_,t(" to join the instance that the community is hosted on. In the case of Lemmy, our instance is on https://lemmy.ml/, but you can join pretty much any community you like and still fully interact with the "),g,t(" community - this is what federation is all about. If you don't have a Lemmy account, then you can go to "),e("a",b,[t("https://join-lemmy.org/"),n(o)]),t(" to help you find a community that caters to your interests.")]),w])}const j=a(l,[["render",k],["__file","20230917-Daeraxa-LemmyCommunity.html.vue"]]);export{j as default}; diff --git a/assets/20230925-savetheclocktower-modern-tree-sitter-part-1.html.3d8130c9.js b/assets/20230925-savetheclocktower-modern-tree-sitter-part-1.html.3d8130c9.js new file mode 100644 index 0000000000..cd906467f0 --- /dev/null +++ b/assets/20230925-savetheclocktower-modern-tree-sitter-part-1.html.3d8130c9.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-25bd77f6","path":"/blog/20230925-savetheclocktower-modern-tree-sitter-part-1.html","title":"Modern Tree-sitter, part 1: the new old feature","lang":"en-US","frontmatter":{"title":"Modern Tree-sitter, part 1: the new old feature","author":"savetheclocktower","date":"2023-09-25T00:00:00.000Z","category":["dev"],"tag":["modernization","tree-sitter"]},"excerpt":"

The last few releases of Pulsar have been bragging about a feature that arguably isn\u2019t even new: our experimental \u201Cmodern\u201D Tree-sitter implementation. You might\u2019ve read that phrase a few times now without fully understanding what it means, and an explanation is long overdue.

\\n","headers":[{"level":2,"title":"What is Tree-sitter?","slug":"what-is-tree-sitter","link":"#what-is-tree-sitter","children":[]},{"level":2,"title":"What is the new Tree-sitter integration replacing?","slug":"what-is-the-new-tree-sitter-integration-replacing","link":"#what-is-the-new-tree-sitter-integration-replacing","children":[]},{"level":2,"title":"If Tree-sitter is already in Pulsar, why write a new implementation?","slug":"if-tree-sitter-is-already-in-pulsar-why-write-a-new-implementation","link":"#if-tree-sitter-is-already-in-pulsar-why-write-a-new-implementation","children":[]},{"level":2,"title":"Why is Tree-sitter better in general?","slug":"why-is-tree-sitter-better-in-general","link":"#why-is-tree-sitter-better-in-general","children":[]},{"level":2,"title":"Why is this new implementation better than the old one?","slug":"why-is-this-new-implementation-better-than-the-old-one","link":"#why-is-this-new-implementation-better-than-the-old-one","children":[]},{"level":2,"title":"I disabled Tree-sitter grammars at some point, and I don\u2019t feel like I\u2019ve missed anything. Why should I turn them back on?","slug":"i-disabled-tree-sitter-grammars-at-some-point-and-i-don-t-feel-like-i-ve-missed-anything-why-should-i-turn-them-back-on","link":"#i-disabled-tree-sitter-grammars-at-some-point-and-i-don-t-feel-like-i-ve-missed-anything-why-should-i-turn-them-back-on","children":[]},{"level":2,"title":"Can I use this new implementation now?","slug":"can-i-use-this-new-implementation-now","link":"#can-i-use-this-new-implementation-now","children":[]},{"level":2,"title":"Which Tree-sitter grammars come with Pulsar?","slug":"which-tree-sitter-grammars-come-with-pulsar","link":"#which-tree-sitter-grammars-come-with-pulsar","children":[]},{"level":2,"title":"The old grammar highlighted my code in a way that I liked. Now things are colored differently and it\u2019s driving me nuts. Should I turn off Tree-sitter?","slug":"the-old-grammar-highlighted-my-code-in-a-way-that-i-liked-now-things-are-colored-differently-and-it-s-driving-me-nuts-should-i-turn-off-tree-sitter","link":"#the-old-grammar-highlighted-my-code-in-a-way-that-i-liked-now-things-are-colored-differently-and-it-s-driving-me-nuts-should-i-turn-off-tree-sitter","children":[]},{"level":2,"title":"Why should I write a Tree-sitter grammar for Pulsar?","slug":"why-should-i-write-a-tree-sitter-grammar-for-pulsar","link":"#why-should-i-write-a-tree-sitter-grammar-for-pulsar","children":[]},{"level":2,"title":"Why is this interesting enough to write about?","slug":"why-is-this-interesting-enough-to-write-about","link":"#why-is-this-interesting-enough-to-write-about","children":[]}],"git":{"updatedTime":1695678390000,"contributors":[{"name":"Andrew Dupont","email":"github@andrewdupont.net","commits":1}]},"readingTime":{"minutes":8.62,"words":2585},"filePathRelative":"blog/20230925-savetheclocktower-modern-tree-sitter-part-1.md","localizedDate":"September 25, 2023"}');export{e as data}; diff --git a/assets/20230925-savetheclocktower-modern-tree-sitter-part-1.html.a328c2a8.js b/assets/20230925-savetheclocktower-modern-tree-sitter-part-1.html.a328c2a8.js new file mode 100644 index 0000000000..b7f47790f4 --- /dev/null +++ b/assets/20230925-savetheclocktower-modern-tree-sitter-part-1.html.a328c2a8.js @@ -0,0 +1 @@ +import{_ as n,o,c as s,e as l,a as e,b as t,d as a,f as i,r as h}from"./app.0e1565ce.js";const d={},u=e("p",null,[t("The last few releases of Pulsar have been bragging about a feature that arguably isn\u2019t even new: our "),e("strong",null,"experimental \u201Cmodern\u201D Tree-sitter implementation"),t(". You might\u2019ve read that phrase a few times now without fully understanding what it means, and an explanation is long overdue.")],-1),c=e("p",null,"This is the first of a series of articles about Pulsar\u2019s ongoing project to migrate its Tree-sitter implementation to a more modern version \u2014\xA0the culmination of hundreds of hours of development work that started back in February of this year. It first shipped in Pulsar version 1.106 back in June as an opt-in feature, and is being improved on an ongoing basis with each new monthly release.",-1),m=e("p",null,"This is a big feature, perhaps the biggest since Pulsar was forked from Atom \u2014 and yet it\u2019s a feature that, if we\u2019ve done our jobs right, won\u2019t even seem like much of a change to most users. Before we dive into the deep end, I\u2019ll try to explain why this is a topic worthy of multiple blog posts.",-1),g=e("h2",{id:"what-is-tree-sitter",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#what-is-tree-sitter","aria-hidden":"true"},"#"),t(" What is Tree-sitter?")],-1),p={href:"https://tree-sitter.github.io/tree-sitter/",target:"_blank",rel:"noopener noreferrer"},f={href:"https://github.com/maxbrunsfeld",target:"_blank",rel:"noopener noreferrer"},y={href:"https://zed.dev/",target:"_blank",rel:"noopener noreferrer"},w=i('

It\u2019s a code parsing system that represents your code as a tree of nodes. It\u2019s very fast on first parse \u2014 and even faster at re-parsing code after you\u2019ve made changes, because it can reuse the output from the last parse and reprocess only the parts that have changed.

You can use its output to underpin lots of features that you\u2019d need in a code editor:

A Tree-sitter parser is designed to parse code quickly, but not necessarily with 100% accuracy; the goal is to be accurate enough for the purposes listed above.

What is the new Tree-sitter integration replacing?

The new Tree-sitter integration \u2014 which I\u2019ll be calling modern Tree-sitter throughout this series \u2014 won\u2019t replace anything except for the previous Tree-sitter integration, which I\u2019ll be calling legacy Tree-sitter.

Once we decide modern Tree-sitter is stable, we\u2019ll drop support for legacy Tree-sitter so that Pulsar can update to a newer version of Electron.

',7),b={href:"https://macromates.com/manual/en/language_grammars",target:"_blank",rel:"noopener noreferrer"},_=e("strong",null,"TextMate grammars",-1),T={href:"https://macromates.com/",target:"_blank",rel:"noopener noreferrer"},v={href:"https://code.visualstudio.com/",target:"_blank",rel:"noopener noreferrer"},k={href:"https://www.sublimetext.com/",target:"_blank",rel:"noopener noreferrer"},x=e("h2",{id:"if-tree-sitter-is-already-in-pulsar-why-write-a-new-implementation",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#if-tree-sitter-is-already-in-pulsar-why-write-a-new-implementation","aria-hidden":"true"},"#"),t(" If Tree-sitter is already in Pulsar, why write a new implementation?")],-1),I=e("p",null,"Good question! Atom was, after all, the first code editor to ship with support for Tree-sitter. It was introduced in late 2017, and was made the preferred system for syntax highlighting starting with Atom 1.32 nearly a year later.",-1),M=e("p",null,"There are two major reasons why the legacy implementation needs to be replaced:",-1),P=e("p",null,[e("strong",null,"Tree-sitter now has powerful features that the legacy implementation doesn\u2019t leverage."),t(" As is often the case, being the first to implement it meant that Atom found all of Tree-sitter\u2019s early pain points. It was a stated goal to use TextMate-style scope names in the new Tree-sitter grammars \u2014\xA0so as to make migration easier \u2014 but Atom had to invent its own system for mapping Tree-sitter output to scope names, and that system didn\u2019t have the flexibility it needed to match TextMate grammars\u2019 syntax highlighting in all cases. This revealed a need for a more robust system of describing tree nodes, and for highlighting ranges that didn\u2019t correspond to the exact ranges of tree nodes.")],-1),S={href:"https://tree-sitter.github.io/tree-sitter/using-parsers#pattern-matching-with-queries",target:"_blank",rel:"noopener noreferrer"},A=e("p",null,"That\u2019s a task worth doing, but it will change how Tree-sitter grammars are written, so there\u2019s no way to avoid the fact that backward compatibility will be broken. But this is a perfect time to make the leap, because\u2026",-1),j=e("strong",null,[t("We need to switch to the "),e("code",null,"web-tree-sitter"),t(" bindings.")],-1),B={href:"https://nodejs.github.io/node-addon-examples/special-topics/context-awareness/",target:"_blank",rel:"noopener noreferrer"},C=e("code",null,"node-tree-sitter",-1),W=e("code",null,"node-tree-sitter",-1),E={href:"https://github.com/tree-sitter/tree-sitter/tree/master/lib/binding_web",target:"_blank",rel:"noopener noreferrer"},L=e("code",null,"web-tree-sitter",-1),N={href:"https://webassembly.org/",target:"_blank",rel:"noopener noreferrer"},O=e("code",null,"node-tree-sitter",-1),R=e("code",null,"web-tree-sitter",-1),V=e("code",null,"node-tree-sitter",-1),q=e("p",null,[t("If, someday, the "),e("code",null,"node-tree-sitter"),t(" bindings were updated to be easier to use in an Electron context, we\u2019d be able to migrate back without any further loss of backward compatibility. But for now, "),e("code",null,"web-tree-sitter"),t(" is the way forward, and we\u2019re pleasantly surprised at how well it does the job.")],-1),U=i('

Nobody likes to break backward compatibility, but needing to switch to web-tree-sitter presents us with an opportunity. Tree-sitter is more stable and more robust than it was in 2017, so we\u2019re able to replace legacy Tree-sitter with something better rather than something that\u2019s merely equivalent.

Why is Tree-sitter better in general?

Here are a few reasons why Pulsar is using Tree-sitter at all, and why Pulsar is configured to prefer a Tree-sitter grammar over a TextMate grammar when both are present:

The specific ways in which Tree-sitter will make your life easier will vary based on which languages you use most often, but this post series will explore a handful of examples.

Why is this new implementation better than the old one?

An under-the-hood change like this isn\u2019t necessarily something you\u2019d notice. But Pulsar users may notice some of the downstream effects:

The benefits are much more direct to grammar authors:

I disabled Tree-sitter grammars at some point, and I don\u2019t feel like I\u2019ve missed anything. Why should I turn them back on?

TextMate grammars are still the main style of grammar in Visual Studio Code, Sublime Text, and other editors. They can\u2019t do all the things that Tree-sitter parsers can do, and most new editors on the market have chosen to use Tree-sitter instead; but even just VSCode\u2019s example tells us that TextMate grammars are no impediment to having a popular and feature-filled editor.

So I\u2019ll be clear: we have no plans to deprecate TextMate-style grammars. They still have their place in Pulsar, and the only thing we\u2019d achieve by deprecating them is to disrupt the editor experience of many of our users.

In the future, it will still be possible (as it is today) to turn off Tree-sitter grammars, either altogether or selectively for certain kinds of files, and fall back to a TextMate grammar for a given language (if it exists).

But our hope is that you\u2019ll give this new Tree-sitter system a chance, even if you\u2019d disabled Tree-sitter grammars in the past for any reason. We think it\u2019s got all the upsides of the legacy Tree-sitter integration without any of the downsides.

Can I use this new implementation now?

Yes, you can, as long as you\u2019re on Pulsar 1.106 or greater. Open your Pulsar settings and focus the \u201CCore\u201D pane. Find the setting named Use Modern Tree-Sitter Implementation and make sure it\u2019s checked, then make sure that the nearby setting named Use Tree-Sitter Parsers is also checked. Then restart Pulsar or reload your window.

If you routinely use the grammar selector and want to be able to switch between Tree-sitter grammars and TextMate grammars at will, locate the grammar-selector package in the \u201CPackages\u201D pane, then click on its Settings button. Uncheck the setting named Hide Duplicate TextMate Grammars. This will give you the ability to choose between modern Tree-sitter, legacy Tree-sitter, and TextMate grammars.

Which Tree-sitter grammars come with Pulsar?

Currently, these grammars are built in:

In addition, Pulsar ships with several specialty Tree-sitter parsers that can be injected into other grammars:

If you use a language that isn\u2019t on the list above and you\u2019re curious about what it would take to give that language a Tree-sitter grammar, you\u2019ll get extra value out of this post series.

The old grammar highlighted my code in a way that I liked. Now things are colored differently and it\u2019s driving me nuts. Should I turn off Tree-sitter?

Please don\u2019t! It\u2019d be like amputating your finger to get rid of a hangnail.

',26),Y={href:"https://github.com/orgs/pulsar-edit/discussions",target:"_blank",rel:"noopener noreferrer"},H=e("h2",{id:"why-should-i-write-a-tree-sitter-grammar-for-pulsar",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#why-should-i-write-a-tree-sitter-grammar-for-pulsar","aria-hidden":"true"},"#"),t(" Why should I write a Tree-sitter grammar for Pulsar?")],-1),J=e("p",null,"Because it\u2019s a much friendlier experience than writing your own TextMate grammar, provided that a Tree-sitter parser exists for the language in question.",-1),G=e("code",null,"nvim-treesitter",-1),F={href:"https://github.com/nvim-treesitter/nvim-treesitter#supported-languages",target:"_blank",rel:"noopener noreferrer"},z=e("p",null,"In my experience, turning a Tree-sitter parser into a full-fledged Pulsar grammar takes less than two hours.",-1),D=e("h2",{id:"why-is-this-interesting-enough-to-write-about",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#why-is-this-interesting-enough-to-write-about","aria-hidden":"true"},"#"),t(" Why is this interesting enough to write about?")],-1),Z=e("p",null,"This Tree-sitter overhaul is the biggest feature to be introduced to Pulsar since it was forked from Atom, and it\u2019s a feature that covers a lot of the surface area of the core editing experience.",-1),X={href:"https://zed.dev/",target:"_blank",rel:"noopener noreferrer"},K={href:"https://nova.app/",target:"_blank",rel:"noopener noreferrer"},Q={href:"https://lapce.dev/",target:"_blank",rel:"noopener noreferrer"},$={href:"https://en.wikipedia.org/wiki/Greenfield_project",target:"_blank",rel:"noopener noreferrer"},ee=e("p",null,[t("But we\u2019ve got a harder job. Atom embraced most of the concepts inherent to TextMate grammars and built major editor features around them. It wouldn\u2019t be very user-friendly if we introduced a parallel system with a different set of concepts \u2014 it would force users to be aware of "),e("em",null,"which kind"),t(" of language grammar they\u2019re using, and to juggle their mental model accordingly.")],-1),te=e("p",null,"But also: most Pulsar users rely on at least a few community packages that were written for Atom and aren\u2019t actively maintained. We have to be very careful to break backward compatibility as little as possible, and only when it\u2019s absolutely necessary.",-1),re=e("p",null,"For these reasons, we shouldn\u2019t just introduce brand new systems for code highlighting, contextual awareness, and the rest. Instead, we\u2019ll do whatever we can to make the new Tree-sitter system work within \u2014 or identically to \u2014 systems that Atom originally shipped with. The Tree-sitter integration can offer enhancements beyond what TextMate grammars do \u2014 and it will! \u2014 but it\u2019s still got to live in the world that TextMate grammars created.",-1),ae=e("em",null,"conventions",-1),ie={href:"https://github.com/nvim-treesitter/nvim-treesitter",target:"_blank",rel:"noopener noreferrer"},ne=e("p",null,"If you\u2019re at all interested in how we did it, stay tuned for the rest of this series.",-1);function oe(se,le){const r=h("ExternalLinkIcon");return o(),s("div",null,[u,l(" more "),c,m,g,e("p",null,[e("a",p,[t("Tree-sitter"),a(r)]),t(" is a code parsing system. It\u2019s the brainchild of "),e("a",f,[t("Max Brunsfeld"),a(r)]),t(", current "),e("a",y,[t("Zed"),a(r)]),t(" contributor and former contributor to Atom.")]),w,e("p",null,[t("Tree-sitter will continue to exist alongside Atom\u2019s original system for syntax highlighting: "),e("a",b,[_,a(r)]),t(". This grammar system is based on the one invented by "),e("a",T,[t("TextMate"),a(r)]),t(" many years ago, and it\u2019s still being used by editors like "),e("a",v,[t("Visual Studio Code"),a(r)]),t(" and "),e("a",k,[t("Sublime Text"),a(r)]),t(".")]),x,I,M,e("ol",null,[e("li",null,[P,e("p",null,[t("Tree-sitter eventually introduced a powerful "),e("a",S,[t("query language"),a(r)]),t(" that could make the job of syntax highlighting easier. But by that point, Microsoft had bought GitHub, and Atom seemed not to be a major priority anymore, so the legacy implementation was never updated to adopt this query language.")]),A]),e("li",null,[e("p",null,[j,t(" One of the goals of Pulsar is to be able to run the editor on the latest version of Electron. Unfortunately, newer Electron versions make it difficult for Pulsar to use Node modules that are not "),e("a",B,[t("context-aware"),a(r)]),t(". The legacy Tree-sitter implementation uses the "),C,t(" bindings, and it appears to be a tall task to adapt these bindings so that they can be used in newer Electron versions. Right now, Pulsar\u2019s reliance on "),W,t(" is preventing us from upgrading Electron to anything past our current version, 12.2.3 \u2014\xA0which is nearly two years old.")]),e("p",null,[t("So we decided to migrate to the "),e("a",E,[L,t(" bindings"),a(r)]),t(". They use "),e("a",N,[t("WebAssembly"),a(r)]),t(" and can run safely inside a browser or an Electron application. Using WebAssembly instead of a native C++ module like "),O,t(" involves a performance penalty, but we\u2019ve found that penalty to be very small in practice. The "),R,t(" bindings are robust and can do nearly everything that "),V,t(" can do.")]),q])]),U,e("p",null,[t("Instead, you can use your user stylesheet to apply a few lines of overrides to your syntax theme and restore the look you\u2019re used to. "),e("a",Y,[t("Open a topic in our discussion forums"),a(r)]),t(" and someone can tell you exactly how to do it.")]),H,J,e("p",null,[t("Pulsar already has built-in Tree-sitter grammars for most common programming languages. But if you\u2019re a consumer of something more obscure, you might find that someone\u2019s already written a parser for it. The "),G,t(" project \u2014 arguably the largest extant consumer of Tree-sitter \u2014 is responsible for the creation of "),e("a",F,[t("dozens of Tree-sitter parsers"),a(r)]),t(" for niche languages.")]),z,D,Z,e("p",null,[t("Other Tree-sitter\u2013integrated editors like "),e("a",X,[t("Zed"),a(r)]),t(", "),e("a",K,[t("Nova"),a(r)]),t(", and "),e("a",Q,[t("Lapce"),a(r)]),t(" are, to the best of my knowledge, "),e("a",$,[t("greenfield projects"),a(r)]),t(". They are free to invent entirely new conventions.")]),ee,te,re,e("p",null,[t("So in order to pull this off \u2014 to make modern Tree-sitter grammars work within existing systems \u2014 we had to create a brand new set of "),ae,t(" for writing Tree-sitter grammars. In some places, there was prior art from implementations like "),e("a",ie,[t("neovim"),a(r)]),t("\u2019s; in others we were flying blind and had to invent things from scratch.")]),ne])}const de=n(d,[["render",oe],["__file","20230925-savetheclocktower-modern-tree-sitter-part-1.html.vue"]]);export{de as default}; diff --git a/assets/20230927-savetheclocktower-modern-tree-sitter-part-2.html.1843c64c.js b/assets/20230927-savetheclocktower-modern-tree-sitter-part-2.html.1843c64c.js new file mode 100644 index 0000000000..05afd4de9f --- /dev/null +++ b/assets/20230927-savetheclocktower-modern-tree-sitter-part-2.html.1843c64c.js @@ -0,0 +1,41 @@ +import{_ as r}from"./tree-sitter-string-scopes-diagram.2db37c5c.js";import{_ as l,o as c,c as p,a as e,b as t,d as s,w as u,e as d,f as a,r as o}from"./app.0e1565ce.js";const h="/assets/tree-sitter-log-cursor-scope.1a459f7a.png",m={},g=e("p",null,"Today I\u2019ll try to illustrate what that system looks like and why it\u2019s important.",-1),f={href:"https://github.com/mauricioszabo/",target:"_blank",rel:"noopener noreferrer"},b=e("code",null,"web-tree-sitter",-1),k={href:"https://github.com/tree-sitter/tree-sitter-javascript/blob/master/queries/highlights.scm",target:"_blank",rel:"noopener noreferrer"},y=e("code",null,"highlights.scm",-1),w={href:"https://tree-sitter.github.io/tree-sitter/syntax-highlighting",target:"_blank",rel:"noopener noreferrer"},v=e("code",null,"tree-sitter highlight",-1),q=e("p",null,[e("em",null,"Couldn\u2019t we just use these files and save ourselves a lot of hassle?"),t(", he asked.")],-1),_=e("p",null,"I said no. And I realized that, in saying no, I was volunteering myself to do some hard work.",-1),x=e("h2",{id:"how-textmate-grammars-work",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#how-textmate-grammars-work","aria-hidden":"true"},"#"),t(" How TextMate grammars work")],-1),T={href:"https://macromates.com/",target:"_blank",rel:"noopener noreferrer"},j={href:"https://www.sublimetext.com/",target:"_blank",rel:"noopener noreferrer"},I={href:"https://code.visualstudio.com/",target:"_blank",rel:"noopener noreferrer"},M={href:"https://en.wikipedia.org/wiki/TextMate#Language_Grammars",target:"_blank",rel:"noopener noreferrer"},S=a(`

Here\u2019s a very simple example of what a TextMate grammar looks like:

{
+	"name": "JavaScript",
+	"scopeName": "source.js",
+	"fileTypes": ["js", "jsx", "mjs", "cjs"],
+
+	"patterns": [
+		{
+			"name": "string.quoted.double.js",
+			"match": "\\\\\\"(.*?)\\\\\\""
+		}
+	]
+}
+

It\u2019ll feel overwhelming to consider all of the things a grammar does, so for this example I\u2019ve removed all of its rules except for one: the one that highlights double-quoted strings "like this". You can see that it looks for a particular pattern and, when it matches, assigns the name string.quoted.double.js to that range. That\u2019s called a scope name.

When you load a JavaScript file in Pulsar \u2014 or VSCode, or Sublime Text \u2014 this grammar will find all your strings and mark them with scope names. In turn, your editor\u2019s syntax theme will hook into that scope name to make that string a special color. (It will most likely target string, rather than something more specific, but that\u2019s why scope names are divided into segments; targeting string will also match string.quoted.double.js.)

Let\u2019s make our grammar a bit more complex:

{
+	"name": "JavaScript",
+	"scopeName": "source.js",
+	"fileTypes": ["js", "jsx", "mjs", "cjs"],
+
+	"patterns": [
+		{
+			"name": "string.quoted.double.js",
+			"begin": "\\"",
+			"beginCaptures": {
+				"0": { "name": "punctuation.definition.string.begin.js" }
+			},
+			"end": "\\"",
+			"endCaptures": {
+				"0": { "name": "punctuation.definition.string.end.js" }
+			},
+			"patterns": [
+				{
+					"include": "#string_escapes"
+				}
+			]
+		}
+	]
+}
+

This is practically the exact rule for double-quoted strings from Pulsar\u2019s built-in TextMate grammar for JavaScript. It introduces some more advanced concepts:

Here\u2019s a better way to visualize this:

string scopes

If you were to put your cursor anywhere within a buffer and run the Editor: Log Cursor Scope command, you\u2019d see a list of scope names that are active at that buffer position, moving from broadest to narrowest:

log cursor scopes

',12),A={href:"https://macromates.com/manual/en/language_grammars#naming_conventions",target:"_blank",rel:"noopener noreferrer"},H=a('

Still, you\u2019d be forgiven if you felt like this was overkill. All we\u2019re doing here is applying some syntax highlighting, right? Do you need this much information just to make a string green?

How TextMate uses scope names

TextMate has a good reason for wanting such verbose scope names, and for sprinkling them so liberally throughout your source code: they aren\u2019t used just for syntax highlighting.

It\u2019s better to think of scope names as intelligent annotations for your source code. The idea isn\u2019t that you\u2019ll want to make single-quoted strings a different color from double-quoted strings \u2014 though you could! \u2014\xA0but rather that other editor features could benefit from knowing whether you were inside a string, and what kind of string you were inside.

Because in TextMate, most editor actions \u2014 commands, snippets, and macros \u2014 can be made contextually aware.

Let\u2019s use commands as an example. Here are some commands you can write in TextMate:

In fact, all of these are real examples that are enabled by default in TextMate. And because they target generic scope names that are shared across a number of grammars, they\u2019ll work in nearly any language.

Did you notice how our grammar file defined a scopeName of source.js? That\u2019s a root scope name, and it applies to the entire file. And it means that commands can restrict themselves to certain languages just like any other context.

',9),L=e("code",null,"prettier",-1),C={href:"https://www.html-tidy.org/",target:"_blank",rel:"noopener noreferrer"},B=e("code",null,"jq",-1),N=a('

In TextMate, file beautification commands canonically use the hotkey Ctrl-Shift-H. By applying the right scope selector for each of these three commands, you can assign them all to Ctrl-Shift-H, and they won\u2019t get in each other\u2019s way.

How Pulsar uses scope names

There are a number of reasons why TextMate isn\u2019t widely used anymore: it\u2019s a macOS-only editor, its releases are frustratingly sporadic, and it\u2019s never had support for crucial features like split editor panes. But TextMate was a major influence on how Atom was built.

It\u2019s not widely known, but Atom and Pulsar offer nearly as much flexibility with scope names as TextMate does:

But that\u2019s not all. If you use Pulsar, chances are very good that you use a package \u2014\xA0built-in or third-party \u2014 that inspects scope names for contextual information. Here are just a few of many examples:

',6),P=e("li",null,[e("code",null,"autocomplete-plus"),t(" inspects scopes to decide whether it should offer suggestions at the cursor; it declines to do so whenever any of the current scopes matches a user-configurable list. (This is how it decides "),e("em",null,"not"),t(" to offer suggestions if you\u2019re typing within a comment, for instance.)")],-1),J=e("li",null,[e("code",null,"autocomplete-css"),t(" decides which suggestions to offer based on the scope name of the closest token.")],-1),W=e("li",null,[e("code",null,"bracket-matcher"),t(" uses scope names to locate and highlight paired tokens like brackets and matching HTML tags.")],-1),F={href:"https://web.pulsar-edit.dev/packages/emmet",target:"_blank",rel:"noopener noreferrer"},V=e("code",null,"emmet",-1),R=e("h2",{id:"what-would-break",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#what-would-break","aria-hidden":"true"},"#"),t(" What would break?")],-1),z={href:"https://github.com/tree-sitter/tree-sitter-javascript/blob/master/queries/highlights.scm#L85-L88",target:"_blank",rel:"noopener noreferrer"},E=e("code",null,"tree-sitter-javascript/queries/highlights.scm",-1),U=a(`
[
+  (string)
+  (template_string)
+] @string
+

This rule treats three different kinds of strings \u2014\xA0single-quoted, double-quoted, and backtick-quoted \u2014 identically. If we used this rule as written, we\u2019d be applying a scope name of string to all JavaScript strings. Not string.quoted.double.js and the like; just string. And there\u2019s no rule in that file that applies scope names to string delimiters, either.

If we embraced a system that used names like string and comment instead of string.quoted.double.js and comment.block.documentation.js, the editing experience would be worse in a number of ways ways \u2014 some tiny, some large.

To drive it home for you, let\u2019s think of all the things that would break from this change alone:

I think Atom learned this lesson rather well during the original rollout of Tree-sitter grammars. After updating, users were defaulted to the new grammars. Sometimes things looked different, or behaved differently. When users reported these differences, they learned that they could work around those bugs in the short term by unchecking the \u201CUse Tree-Sitter Grammars\u201D setting and restoring the original behavior. I wonder how many did, and how many of them ever re-enabled Tree-sitter grammars in the future.

A lot of code has been written that presumes the existence of TextMate-style scopes. I think this is fine. The fact that we have a new system doesn\u2019t mean that we should leave scopes behind. For one thing, it\u2019d be a jarring experience for users \u2014\xA0we\u2019d be bragging about a new feature that was likely to break customizations that users had been relying upon for years.

`,7),G={href:"https://en.wikipedia.org/wiki/Middleware",target:"_blank",rel:"noopener noreferrer"},D=a('

The challenge

So that\u2019s why I volunteered to do this. I knew that we couldn\u2019t just reuse some syntax highlighting query files that were written with different assumptions in place. And I knew that I couldn\u2019t reasonably ask someone else to do a bunch of work just because I felt like it was important.

My goal was to create a set of grammars that had the performance and accuracy upsides of the Tree-sitter experience and the rich scope name annotations that TextMate grammars provided.

For that to be possible, Tree-sitter grammars should apply scope names as similarly as possible to TextMate grammars. There may be reasons that we choose not to do something the way that the TextMate grammar does it, but we don\u2019t want to be unable to do something the way that the TextMate grammar does it.

Syntax highlighting was the first, and hardest, task. Next time I\u2019ll talk about how we solved it.

',5);function O(Y,K){const i=o("RouterLink"),n=o("ExternalLinkIcon");return c(),p("div",null,[e("p",null,[t("In "),s(i,{to:"/blog/20230925-savetheclocktower-modern-tree-sitter-part-1.html"},{default:u(()=>[t("the last post")]),_:1}),t(", I tried to explain why the new Tree-sitter integration was worth writing about in the first place: because we needed to integrate it into a system defined by TextMate grammars, and we had to solve some challenging problems along the way.")]),g,d(" more "),e("p",null,[t("When I first started getting involved with Pulsar back in January, "),e("a",f,[t("@mauricioszabo"),s(n)]),t(" had just begun the process of migrating us to the "),b,t(" bindings, and seemed to be dreading the enormity of the task.")]),e("p",null,[t("He noticed that most Tree-sitter parsers had "),e("a",k,[t("their own built-in query files"),s(n)]),t(" for syntax highlighting. These "),y,t(" files act like a sort of stylesheet for a Tree-sitter tree: they\u2019re used when you run "),e("a",w,[v,s(n)]),t(" from the command line, and they\u2019re what GitHub uses when it highlights code in a web browser.")]),q,_,x,e("p",null,[t("You may not have ever used "),e("a",T,[t("TextMate"),s(n)]),t(", but you\u2019ve probably used an editor that benefited from its existence. Several widely used editors \u2014 first "),e("a",j,[t("Sublime Text"),s(n)]),t(", then Atom, and now "),e("a",I,[t("Visual Studio Code"),s(n)]),t(" \u2014 all based their grammar systems on TextMate\u2019s.")]),e("p",null,[t("A "),e("a",M,[t("TextMate grammar"),s(n)]),t(" doesn\u2019t aim to understand your code entirely. It uses regular expressions \u2014 pattern-matching \u2014\xA0to describe rules that identify the important parts of your code. It knows that some rules apply broadly and others are valid only in certain contexts, so it allows you to nest rules inside other rules.")]),S,e("p",null,[t("Why is it choosing those specific names? Because of "),e("a",A,[t("naming conventions established by TextMate"),s(n)]),t(" many years ago. When different language grammars try to abide by the same conventions for naming their scopes, it makes it easier to write syntax themes without constantly checking how they look in every possible language.")]),H,e("p",null,[t("For instance: to beautify your JavaScript, you might want to run it through "),L,t(". To beautify your HTML, you might want to run it through "),e("a",C,[t("HTML Tidy"),s(n)]),t(". And to beautify your JSON, you might want to run it through "),B,t(".")]),N,e("ul",null,[P,J,W,e("li",null,[e("a",F,[V,s(n)]),t(", the eighth-most-downloaded community package, relies on scope descriptors to decide whether it should try to expand an abbreviation into HTML.")])]),R,e("p",null,[t("With this in mind, let\u2019s go back to those built-in Tree-sitter query files and consider our string example. In "),e("a",z,[E,s(n)]),t(" we can see how strings are treated:")]),U,e("p",null,[t("But I also think that TextMate-style scopes function as an elegant "),e("a",G,[t("middleware"),s(n)]),t(". They allow us to change an underlying implementation without the user needing to know or care \u2014 except, perhaps, to notice downstream benefits, like faster syntax highlighting and new editor features.")]),D])}const Z=l(m,[["render",O],["__file","20230927-savetheclocktower-modern-tree-sitter-part-2.html.vue"]]);export{Z as default}; diff --git a/assets/20230927-savetheclocktower-modern-tree-sitter-part-2.html.c9b9a8bd.js b/assets/20230927-savetheclocktower-modern-tree-sitter-part-2.html.c9b9a8bd.js new file mode 100644 index 0000000000..dcaa181f8e --- /dev/null +++ b/assets/20230927-savetheclocktower-modern-tree-sitter-part-2.html.c9b9a8bd.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-5bad2e3c","path":"/blog/20230927-savetheclocktower-modern-tree-sitter-part-2.html","title":"Modern Tree-sitter, part 2: why scopes matter","lang":"en-US","frontmatter":{"title":"Modern Tree-sitter, part 2: why scopes matter","author":"savetheclocktower","date":"2023-09-27T00:00:00.000Z","category":["dev"],"tag":["modernization","tree-sitter"]},"excerpt":"

In the last post, I tried to explain why the new Tree-sitter integration was worth writing about in the first place: because we needed to integrate it into a system defined by TextMate grammars, and we had to solve some challenging problems along the way.

\\n

Today I\u2019ll try to illustrate what that system looks like and why it\u2019s important.

\\n","headers":[{"level":2,"title":"How TextMate grammars work","slug":"how-textmate-grammars-work","link":"#how-textmate-grammars-work","children":[]},{"level":2,"title":"How TextMate uses scope names","slug":"how-textmate-uses-scope-names","link":"#how-textmate-uses-scope-names","children":[]},{"level":2,"title":"How Pulsar uses scope names","slug":"how-pulsar-uses-scope-names","link":"#how-pulsar-uses-scope-names","children":[]},{"level":2,"title":"What would break?","slug":"what-would-break","link":"#what-would-break","children":[]},{"level":2,"title":"The challenge","slug":"the-challenge","link":"#the-challenge","children":[]}],"git":{"updatedTime":1695833427000,"contributors":[{"name":"Andrew Dupont","email":"github@andrewdupont.net","commits":4}]},"readingTime":{"minutes":7.84,"words":2351},"filePathRelative":"blog/20230927-savetheclocktower-modern-tree-sitter-part-2.md","localizedDate":"September 27, 2023"}');export{e as data}; diff --git a/assets/20231004-Daeraxa-OctoberUpdate.html.3b1907f4.js b/assets/20231004-Daeraxa-OctoberUpdate.html.3b1907f4.js new file mode 100644 index 0000000000..8dacc0f7ce --- /dev/null +++ b/assets/20231004-Daeraxa-OctoberUpdate.html.3b1907f4.js @@ -0,0 +1 @@ +const e=JSON.parse(`{"key":"v-6cc4d458","path":"/blog/20231004-Daeraxa-OctoberUpdate.html","title":"Community Update","lang":"en-US","frontmatter":{"title":"Community Update","author":"Daeraxa","date":"2023-10-04T00:00:00.000Z","category":["news","log"],"tag":["update"]},"excerpt":"

As the leaves turn brown and the days grow shorter, make sure you draw up a chair and settle in for a nice, warm, pumpkin spice edition of the Pulsar community update!

\\n","headers":[{"level":2,"title":"Introducing Pulsar Cooperative!","slug":"introducing-pulsar-cooperative","link":"#introducing-pulsar-cooperative","children":[]},{"level":2,"title":"Modern Tree-sitter blog posts","slug":"modern-tree-sitter-blog-posts","link":"#modern-tree-sitter-blog-posts","children":[]},{"level":2,"title":"Converting PPM's code from callbacks to async","slug":"converting-ppm-s-code-from-callbacks-to-async","link":"#converting-ppm-s-code-from-callbacks-to-async","children":[]},{"level":2,"title":"macOS binary signing issues","slug":"macos-binary-signing-issues","link":"#macos-binary-signing-issues","children":[]},{"level":2,"title":"Shields.io badges","slug":"shields-io-badges","link":"#shields-io-badges","children":[]},{"level":2,"title":"Pulsar on GitHub Desktop","slug":"pulsar-on-github-desktop","link":"#pulsar-on-github-desktop","children":[]},{"level":2,"title":"Community spotlight","slug":"community-spotlight","link":"#community-spotlight","children":[]}],"git":{"updatedTime":1696805253000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":7}]},"readingTime":{"minutes":5.58,"words":1674},"filePathRelative":"blog/20231004-Daeraxa-OctoberUpdate.md","localizedDate":"October 4, 2023"}`);export{e as data}; diff --git a/assets/20231004-Daeraxa-OctoberUpdate.html.dbc8c526.js b/assets/20231004-Daeraxa-OctoberUpdate.html.dbc8c526.js new file mode 100644 index 0000000000..6cf2c1c9a2 --- /dev/null +++ b/assets/20231004-Daeraxa-OctoberUpdate.html.dbc8c526.js @@ -0,0 +1,56 @@ +import{_ as o}from"./tree-sitter.6a39e323.js";import{_ as i}from"./spotlight.aba19e76.js";import{_ as p,o as r,c,e as l,a,b as n,d as e,f as t,r as u}from"./app.0e1565ce.js";const d="/assets/community.aacaf0a9.png",h="/assets/development.9a152ab5.png",k="/assets/signing.c58cd4da.png",m="/assets/shields-io.b3bc3a32.png",g="/assets/github-desktop.a5a19b8f.png",b={},f=a("p",null,"As the leaves turn brown and the days grow shorter, make sure you draw up a chair and settle in for a nice, warm, pumpkin spice edition of the Pulsar community update!",-1),v=t('

Welcome to the October Community Update!

A warm welcome to this edition of our monthly blog post where we tell stories about what the Pulsar team and community have been up to in the world of Pulsar. This month we have a real mixed bag of things from an initial announcement of our "Pulsar Cooperative" initiative, a large code refactor of PPM, an explanation of some recent macOS signing issues and the appearance of Pulsar on Shields.io and GitHub Desktop! Enough with the prelude and let's get on with the update!

Introducing Pulsar Cooperative!

',4),w={href:"https://github.blog/2022-06-08-sunsetting-atom/",target:"_blank",rel:"noopener noreferrer"},y=t('

The problem is that some of the more popular packages are in this exact situation and we often see members of the community asking about an issue with a package, discovering the fix and implementing the fix locally, but for whatever reason the package maintainer no longer wishes to maintain that package with Pulsar in mind.

Of course, it is always possible to fork the package and take over maintenance yourself, but not everyone wants to, or may be in a position to, maintain a package. Nor does the core Pulsar team have the ability to maintain all of these community packages as well as the main Pulsar project.

So we have come up with what we hope will be a viable solution. Pulsar Cooperative. This is a new organization that we have set up to allow joint ownership of packages. The idea is thus:

We hope to introduce a place where people can cooperatively work to make the Pulsar ecosystem better without having to worry about violating the strict vision for a package and not having their fix even accepted. It is also a place where people can submit fixes and make their favourite packages functional again without necessarily having to take on full responsibility for the ongoing maintenance.

This is not a "Pulsar team" project in the sense that members of the main organization are going to be providing bug fixes or updates in response to logged issues (of course they are free too, just like any other community member). This is an entirely community-led endeavour and we hope everyone will be encouraged to get involved!

This isn't ready just yet, but look out for an update right here on the blog (or any of our other community areas) once we are fully live and ready!

Modern Tree-sitter blog posts

[1]

',9),_={href:"https://github.com/savetheclocktower",target:"_blank",rel:"noopener noreferrer"},P={href:"https://tree-sitter.github.io/tree-sitter/",target:"_blank",rel:"noopener noreferrer"},x={href:"https://pulsar-edit.dev/blog/20230925-savetheclocktower-modern-tree-sitter-part-1.html",target:"_blank",rel:"noopener noreferrer"},T={href:"https://pulsar-edit.dev/blog/20230927-savetheclocktower-modern-tree-sitter-part-2.html",target:"_blank",rel:"noopener noreferrer"},S=a("h2",{id:"converting-ppm-s-code-from-callbacks-to-async",tabindex:"-1"},[a("a",{class:"header-anchor",href:"#converting-ppm-s-code-from-callbacks-to-async","aria-hidden":"true"},"#"),n(" Converting PPM's code from callbacks to async")],-1),j=a("img",{src:h,height:"150"},null,-1),M={href:"https://github.com/twocolours",target:"_blank",rel:"noopener noreferrer"},N={href:"https://github.com/pulsar-edit/ppm",target:"_blank",rel:"noopener noreferrer"},A=a("code",null,"async/await",-1),C=t(`

The PPM repo, while modern and accepted when written, is now mostly viewed as being outdated due to the pervasiveness of deeply nested callbacks. In some instances having nesting six levels deep, and as anyone who's been programming in JavaScript prior to ES6 knows, this can be cumbersome, difficult, and frustrating to work with. That is exactly why the effort has been made to convert the entire codebase into a shallow nested, early return patterned, modern ES6 source utilizing async/await properly. In hopes that a greatly simplified codebase can pave the way for easier modifications in the future.

To show what we are talking about, take this (simplified but real) code as an example:

From this:

this.registerPackage(pack, (error, firstTimePublishing) => {
+	if (error != null) {
+		return callback(error);
+	}
+
+	this.renamePackage(pack, rename, (error) => {
+		if (error != null) {
+			return callback(error);
+		}
+
+		this.versionPackage(version, (error, tag) => {
+			if (error != null) {
+				return callback(error);
+			}
+
+			this.pushVersion(tag, pack, (error) => {
+				if (error != null) {
+					return callback(error);
+				}
+
+				this.waitForTagToBeAvailable(pack, tag, () => {
+					if (originalName != null) {
+						rename = pack.name;
+						pack.name = originalName;
+					}
+					this.publishPackage(pack, tag, { rename }, (error) => {
+						if (firstTimePublishing && error == null) {
+							this.logFirstTimePublishMessage(pack);
+						}
+						return callback(error);
+					});
+				});
+			});
+		});
+	});
+});
+

To this:

const firstTimePublishing = await this.registerPackage(pack);
+await this.renamePackage(pack, rename);
+const tag = await this.versionPackage(version);
+await this.pushVersion(tag, pack);
+
+await this.waitForTagToBeAvailable(pack, tag);
+if (originalName != null) {
+	rename = pack.name;
+	pack.name = originalName;
+}
+
+try {
+	await this.publishPackage(pack, tag, { rename });
+} catch (error) {
+	if (firstTimePublishing) {
+		this.logFirstTimePublishMessage(pack);
+	}
+	throw error;
+}
+

This has been a huge piece of work, which began because they overheard complaints about the PPM codebase being hard to read and understand due to its outdated style. As they had already previously assisted in PPM with the "decaffeination" of the codebase, they were already fairly familiar with it and thought they could help further by "upgrading" the codebase to this modern style.

There has been a lot of focus on code flow during this conversion in order to make the codebase as easy to read and understand as possible for future contributors. Not only was the structure changed, but some code and modules could be removed entirely as their only purpose was to support the previous callback style.

From a Pulsar user's perspective you shouldn't notice anything different at all. This is all about maintenance and modernization of the codebase. We want to make Pulsar as hackable and as easy to contribute as possible and these kinds of efforts go a long way to achieving that goal.

`,9),D={href:"https://github.com/pulsar-edit/ppm/pull/95/",target:"_blank",rel:"noopener noreferrer"},q=a("h2",{id:"macos-binary-signing-issues",tabindex:"-1"},[a("a",{class:"header-anchor",href:"#macos-binary-signing-issues","aria-hidden":"true"},"#"),n(" macOS binary signing issues")],-1),I=a("img",{src:k,height:"200"},null,-1),E={href:"https://github.com/pulsar-edit/pulsar/pull/743",target:"_blank",rel:"noopener noreferrer"},O={href:"https://github.com/DeeDeeG",target:"_blank",rel:"noopener noreferrer"},z={href:"https://github.com/confused-Techie",target:"_blank",rel:"noopener noreferrer"},W={href:"https://github.com/Meadowsys",target:"_blank",rel:"noopener noreferrer"},F=a("p",null,[n("This issue would only have affected the "),a("code",null,"1.109.0"),n(" releases; the current and upcoming releases will all be signed as normal.")],-1),V=a("h2",{id:"shields-io-badges",tabindex:"-1"},[a("a",{class:"header-anchor",href:"#shields-io-badges","aria-hidden":"true"},"#"),n(" Shields.io badges")],-1),B=a("img",{src:m,height:"100"},null,-1),G={href:"https://github.com/confused-Techie",target:"_blank",rel:"noopener noreferrer"},R={href:"https://shields.io/",target:"_blank",rel:"noopener noreferrer"},H={href:"https://shields.io/badges/pulsar-downloads",target:"_blank",rel:"noopener noreferrer"},U={href:"https://shields.io/badges/pulsar-stargazers",target:"_blank",rel:"noopener noreferrer"},L={href:"https://web.pulsar-edit.dev/packages/x-terminal-reloaded",target:"_blank",rel:"noopener noreferrer"},J=a("img",{alt:"Pulsar Downloads",src:"https://img.shields.io/pulsar/dt/x-terminal-reloaded"},null,-1),$=a("p",null,"So go grab your badges now to show off your package's stats!",-1),Y=a("h2",{id:"pulsar-on-github-desktop",tabindex:"-1"},[a("a",{class:"header-anchor",href:"#pulsar-on-github-desktop","aria-hidden":"true"},"#"),n(" Pulsar on GitHub Desktop")],-1),K={href:"https://desktop.github.com/",target:"_blank",rel:"noopener noreferrer"},Q=a("img",{src:g,height:"500"},null,-1),X={href:"https://github.com/mdibella-dev",target:"_blank",rel:"noopener noreferrer"},Z={href:"https://github.com/confused-Techie",target:"_blank",rel:"noopener noreferrer"},nn={href:"https://github.com/Daeraxa",target:"_blank",rel:"noopener noreferrer"},an=a("h2",{id:"community-spotlight",tabindex:"-1"},[a("a",{class:"header-anchor",href:"#community-spotlight","aria-hidden":"true"},"#"),n(" Community spotlight")],-1),sn=a("img",{src:i,height:"200"},null,-1),en={href:"https://github.com/twocolours",target:"_blank",rel:"noopener noreferrer"},tn=a("a",{href:"#converting-ppm-s-code-from-callbacks-to-async"},"changes to the PPM codebase",-1),on=a("hr",null,null,-1),pn=a("p",null,"And that wraps things up for this month. We hope you have enjoyed reading about all these changes and updates to Pulsar-related areas. As always, a huge thank you to all of our wonderful community members and a special thanks to all those who donate to the project, which makes this all possible. We hope to see you here again next month!",-1),rn=a("hr",{class:"footnotes-sep"},null,-1),cn={class:"footnotes"},ln={class:"footnotes-list"},un={id:"footnote1",class:"footnote-item"},dn={href:"https://tree-sitter.github.io/tree-sitter/",target:"_blank",rel:"noopener noreferrer"},hn=a("a",{href:"#footnote-ref1",class:"footnote-backref"},"\u21A9\uFE0E",-1);function kn(mn,gn){const s=u("ExternalLinkIcon");return r(),c("div",null,[f,l(" more "),v,a("p",null,[n("One of our main reasons for trying to continue the Atom editor project (rather than moving to another editor or making one from scratch) was to preserve the huge number of community packages that had been created for Atom over the years. Unfortunately, as development slowed on Atom (and especially when the "),a("a",w,[n("sunset"),e(s)]),n(" was announced), some maintainers seem to have either archived their packages or are no longer looking to maintain them.")]),y,a("p",null,[n("If you hadn't already seen or read them, "),a("a",_,[n("@savetheclocktower"),e(s)]),n(" has been writing a series of blog posts that go into detail about all the work that has been going on in Pulsar to support a modern implementation of "),a("a",P,[n("Tree-sitter"),e(s)]),n(". Parts "),a("a",x,[n("one"),e(s)]),n(" and "),a("a",T,[n("two"),e(s)]),n(" are available right now and keep an eye on the Pulsar blog for more updates!")]),S,j,a("p",null,[n("Community member "),a("a",M,[n("@Nemokosch/@twocolours"),e(s)]),n(" \xA0has been spearheading the conversion of our "),a("a",N,[n("PPM codebase"),e(s)]),n(" to upgrade old JavaScript callback style to modern ES6 "),A,n(".")]),C,a("p",null,[n("So a big thank you again to [@Nemokosch] for working on this. You can see the progress in "),a("a",D,[n("this pull request"),e(s)])]),q,I,a("p",null,[n("We discovered an issue recently where it turned out that our macOS binaries weren't being signed by our CI process. After some investigation and "),a("a",E,[n("changes to the CI environment"),e(s)]),n(" by "),a("a",O,[n("@DeeDeeG"),e(s)]),n(", "),a("a",z,[n("@confused-techie"),e(s)]),n(" and "),a("a",W,[n("@Meadowsys"),e(s)]),n(", the binaries are now correctly signed again.")]),F,V,B,a("p",null,[n("Thanks to "),a("a",G,[n("@confused-techie"),e(s)]),n(" and the team at "),a("a",R,[n("Shields.io"),e(s)]),n(", badges for Pulsar packages are now available! This means that if you make a package for Pulsar and publish it to the Pulsar Package Repository, you can now display download and stargazer stats on your README page.")]),a("p",null,[n("Stats can be shown both for "),a("a",H,[n("downloads"),e(s)]),n(" and "),a("a",U,[n("stargazers"),e(s)]),n(". Here is an example of a downloads badge for "),a("a",L,[n("x-terminal-reloaded"),e(s)]),n(":")]),J,$,Y,a("p",null,[n("For those of you who use "),a("a",K,[n("GitHub Desktop"),e(s)]),n(" you will now find (or will soon find) that Pulsar is available as an option for you to select as your editor of choice!")]),Q,a("p",null,[n("Thanks to "),a("a",X,[n("@mdibella-dev"),e(s)]),n(" for adding this for the macOS version, "),a("a",Z,[n("@confused-techie"),e(s)]),n(" for Windows and "),a("a",nn,[n("@Daeraxa"),e(s)]),n(" for the Linux version!")]),an,sn,a("p",null,[n("This month we want to give special attention to "),a("a",en,[n("@Nemokosch/@twocolours"),e(s)]),n(" with their "),tn,n(" already discussed on this update. We love to see these kinds of contributions to Pulsar!")]),on,pn,rn,a("section",cn,[a("ol",ln,[a("li",un,[a("p",null,[n("Image from "),a("a",dn,[n("https://tree-sitter.github.io/tree-sitter/"),e(s)]),n(" - Copyright (c) 2018-2021 Max Brunsfeld "),hn])])])])])}const wn=p(b,[["render",kn],["__file","20231004-Daeraxa-OctoberUpdate.html.vue"]]);export{wn as default}; diff --git a/assets/20231013-savetheclocktower-modern-tree-sitter-part-3.html.479e8a22.js b/assets/20231013-savetheclocktower-modern-tree-sitter-part-3.html.479e8a22.js new file mode 100644 index 0000000000..b08c27cbdb --- /dev/null +++ b/assets/20231013-savetheclocktower-modern-tree-sitter-part-3.html.479e8a22.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-8b6c6124","path":"/blog/20231013-savetheclocktower-modern-tree-sitter-part-3.html","title":"Modern Tree-sitter, part 3: syntax highlighting via queries","lang":"en-US","frontmatter":{"title":"Modern Tree-sitter, part 3: syntax highlighting via queries","author":"savetheclocktower","date":"2023-10-13T00:00:00.000Z","category":["dev"],"tag":["modernization","tree-sitter"]},"excerpt":"

Last time I laid out the case for why we chose to embrace TextMate-style scope names, even in newer Tree-sitter grammars. I set a difficult challenge for Pulsar: make it so that a Tree-sitter grammar can do anything a TextMate grammar can do.

\\n

Today I\u2019d like to show you the specific problems that we had to solve in order to pull that off.

\\n","headers":[{"level":2,"title":"The problems","slug":"the-problems","link":"#the-problems","children":[]},{"level":2,"title":"The first challenge: robust querying","slug":"the-first-challenge-robust-querying","link":"#the-first-challenge-robust-querying","children":[{"level":3,"title":"How captures work","slug":"how-captures-work","link":"#how-captures-work","children":[]},{"level":3,"title":"Highlights","slug":"highlights","link":"#highlights","children":[]},{"level":3,"title":"Scope tests","slug":"scope-tests","link":"#scope-tests","children":[]}]},{"level":2,"title":"The second challenge: scoping arbitrary ranges","slug":"the-second-challenge-scoping-arbitrary-ranges","link":"#the-second-challenge-scoping-arbitrary-ranges","children":[{"level":3,"title":"Scope adjustments","slug":"scope-adjustments","link":"#scope-adjustments","children":[]}]},{"level":2,"title":"What does this get us?","slug":"what-does-this-get-us","link":"#what-does-this-get-us","children":[]},{"level":2,"title":"Next time","slug":"next-time","link":"#next-time","children":[]}],"git":{"updatedTime":1697139047000,"contributors":[{"name":"Andrew Dupont","email":"github@andrewdupont.net","commits":1}]},"readingTime":{"minutes":15.79,"words":4738},"filePathRelative":"blog/20231013-savetheclocktower-modern-tree-sitter-part-3.md","localizedDate":"October 13, 2023"}');export{e as data}; diff --git a/assets/20231013-savetheclocktower-modern-tree-sitter-part-3.html.dff0e326.js b/assets/20231013-savetheclocktower-modern-tree-sitter-part-3.html.dff0e326.js new file mode 100644 index 0000000000..7e299e7c97 --- /dev/null +++ b/assets/20231013-savetheclocktower-modern-tree-sitter-part-3.html.dff0e326.js @@ -0,0 +1,100 @@ +import{_ as o}from"./tree-sitter-string-scopes-diagram.2db37c5c.js";import{_ as i,o as r,c as d,a as t,b as e,d as n,e as c,f as a,r as l}from"./app.0e1565ce.js";const p="/assets/tree-sitter-tools-string-tree.3fe45d43.png",u="/assets/tree-sitter-comment-scopes-diagram.88665730.png",h="/assets/tree-sitter-tools-comment-tree.f78df2f6.png",m="/assets/tree-sitter-snippet-context-example.f7c0921c.webm",g="/assets/tree-sitter-snippet-context-example.8124fdcd.mp4",v={},f={href:"https://pulsar-edit.dev/blog/20230927-savetheclocktower-modern-tree-sitter-part-2.html",target:"_blank",rel:"noopener noreferrer"},b=t("strong",null,[e("make it so that a Tree-sitter grammar can do "),t("em",null,"anything"),e(" a TextMate grammar can do.")],-1),w=t("p",null,"Today I\u2019d like to show you the specific problems that we had to solve in order to pull that off.",-1),y={href:"https://web.pulsar-edit.dev/packages/tree-sitter-tools",target:"_blank",rel:"noopener noreferrer"},k=t("code",null,"tree-sitter-tools",-1),_={href:"https://tree-sitter.github.io/tree-sitter/playground",target:"_blank",rel:"noopener noreferrer"},j=a('

The problems

The legacy Tree-sitter integration into Atom used its own system for mapping tree nodes to scope names, but it had two major limitations that prevented it from matching the scope names produced by a TextMate grammar:

  1. It couldn\u2019t query the right nodes: it used a CSS-like syntax that was limited in how expressively it could describe tree nodes.
  2. It couldn\u2019t describe the right ranges: it could only add scopes to ranges that corresponded to individual tree nodes.

The need to write an Atom-specific bridge between node names and scope names served as evidence that Tree-sitter need its own system for more easily working with syntax trees \u2014 one that would prevent every Tree-sitter consumer from having to reinvent the wheel.

',4),x={href:"https://tree-sitter.github.io/tree-sitter/using-parsers#pattern-matching-with-queries",target:"_blank",rel:"noopener noreferrer"},q={href:"https://en.wikipedia.org/wiki/Scheme_(programming_language)",target:"_blank",rel:"noopener noreferrer"},T={href:"https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector",target:"_blank",rel:"noopener noreferrer"},S=t("code",null,"document.querySelector",-1),I=a('

Right now, though, let\u2019s just focus on syntax highlighting. And remember those two limitations that I described above, because we\u2019ll have to solve them both before this article is done.

The first challenge: robust querying

I\u2019ll remind you of our example from part 2: the scope names applied to a double-quoted string in JavaScript.

string scopes

Our first goal is to make it so that our JavaScript Tree-sitter grammar can apply these same scopes to the same buffer ranges. But Tree-sitter works very differently to a TextMate grammar, so it\u2019s not immediately obvious how we can pull this off. Let\u2019s reason through it.

How captures work

',6),A={href:"https://tree-sitter.github.io/tree-sitter/syntax-highlighting",target:"_blank",rel:"noopener noreferrer"},W=t("code",null,"highlights.scm",-1),P=t("code",null,"tree-sitter highlight",-1),M=t("code",null,"highlights.scm",-1),L=a(`

Last time I showed you this excerpt from tree-sitter-javascript\u2019s highlights.scm file:

[
+  (string)
+  (template_string)
+] @string
+

Once you\u2019re familiar with query syntax, the outcome of this query is clear: all JavaScript strings will be captured and given a name of @string. Somewhere within the tree-sitter highlight code path, those capture names are mapped to various colors, and are applied to the captured buffer ranges. Anything that gets captured as @string will have one color in the output; anything that gets captured as @keyword will have a different color; and so on.

Let\u2019s imagine that Pulsar has a similar system. In order to keep this article from putting you to sleep, I won\u2019t get into the details of exactly how we do it, but the machinery is in place. Instead of a capture name like @string, we\u2019ll be choosing more verbose names like @string.quoted.double.js, but the principles are the same.

We also won\u2019t be talking about how Pulsar knows which areas of the buffer to re-highlight as the user makes changes, nor how Pulsar combines the results of multiple parsers (for example, JavaScript embedded in HTML). These topics may be visited in future articles, but today we\u2019re just talking about how to use Tree-sitter queries to identify arbitrary ranges and give them the scope names that we want.

Highlights

What does a string look like in a Tree-sitter tree? Let\u2019s create a new document with nothing but the following contents:

"like this";
+
`,8),C={href:"https://web.pulsar-edit.dev/packages/tree-sitter-tools",target:"_blank",rel:"noopener noreferrer"},B=t("p",null,[t("img",{src:p,alt:"string tree"})],-1),N=t("p",null,[e("So we can see that a "),t("code",null,"string"),e(" node consists of three parts: a delimiter, a "),t("code",null,"string_content"),e(" node, and another delimiter. This structure maps elegantly to the things that we want to scope.")],-1),H={href:"https://en.wikipedia.org/wiki/Abstract_syntax_tree",target:"_blank",rel:"noopener noreferrer"},J=t("em",null,"abstract",-1),O=t("em",null,"concrete",-1),F=a(`

In Tree-sitter parsers, nodes that matter to semantics (like string_content) tend to have names, whereas other nodes (like the delimiters) are \u201Canonymous\u201D nodes. Anonymous nodes can still be queried against like named nodes, so it feels like it\u2019ll be pretty easy to apply the scopes that we want.

So let\u2019s open our grammar\u2019s highlights.scm file and give it a try. (In Pulsar\u2019s \u201Cdev mode\u201D \u2014 which you can trigger with the --dev flag on the command line \u2014 grammar authors can even make changes to their query files and see them take effect instantly when they save!)

(string) @string.quoted.double.js
+

That\u2019s not bad, but it\u2019s too specific. In tree-sitter-javascript, the string node applies for both single-quoted and double-quoted strings, with the difference being reflected only in the anonymous nodes. (Template strings, as we saw above, have their own node type.)

How do we distinguish single-quoted strings from double-quoted strings? Here\u2019s one thing we could do:

((string) @string.quoted.single.js
+  (#match? @string.quoted.single.js "^'"))
+((string) @string.quoted.double.js
+  (#match? @string.quoted.single.js "^\\""))
+

The built-in #match? predicate allows us to reject possible matches when their contents don\u2019t match a given regular expression. Here we\u2019re telling the query engine to distinguish between string nodes whose text starts with ' and those whose text starts with ".

We\u2019ll be using the #match? predicate a lot. Unlike some of the other predicates we\u2019ll see shortly, it\u2019s implemented by the web-tree-sitter bindings, so Tree-sitter on its own is able to reject would-be captures that don\u2019t pass it. By the time we see the list of captures, those that have failed a #match? test have already been filtered out.

In this case, though, there\u2019s an even easier way to tell these strings apart:

(string "'") @string.quoted.single.js
+(string "\\"") @string.quoted.double.js
+

As I mentioned, we can query for the presence of anonymous nodes. So the first line will match any string that contains at least one anonymous node child named ', and the second line will match any string that contains at least one anonymous node child named ".

Since the capture name is on the outside of the closing parenthesis, the capture name applies to the whole string.

We don\u2019t have to be more specific; if that anonymous node exists at all, then it\u2019s used as a delimiter on both sides of the string. And this query won\u2019t match any potential false positives \u2014 like a double-quoted string that happens to have a ' somewhere inside of it \u2014 because the parser is too smart to get tripped up by that sort of thing.

This is one reason why it\u2019s very easy to make a new Tree-sitter grammar if someone has done the work of writing a Tree-sitter parser for the given language. If we were writing a TextMate grammar, we\u2019d have to care about a lot more of these edge cases, but a Tree-sitter parser will have handled them for us already.

Scope tests

We can already tell that the expressiveness of Tree-sitter\u2019s query system will go a long way toward solving the first of the two problems we described above. Last time around, Atom developers had to invent a system for querying the tree, but we get a much more powerful system for free.

Tools like anonymous nodes and #match? predicates can get us quite far on their own, but they can\u2019t solve all of our problems. We still have to scope the quotation marks themselves, and we may think we know how to do it:

(string "'" @punctuation.definition.string.begin.js)
+

By putting the capture name immediately after the "'", we can target that anonymous node and give it a name. But remember that there are two delimiters! We want to give one scope name to the beginning delimiter and a different scope name to the ending delimiter. As we\u2019ve written it, this rule would match both delimiters.

`,19),$={href:"https://tree-sitter.github.io/tree-sitter/using-parsers#anchors",target:"_blank",rel:"noopener noreferrer"},E=t("em",null,"named",-1),Y=t("strong",null,"We need a way to introduce our own filtering criteria into Tree-sitter queries.",-1),R=a(`

Luckily, Tree-sitter gives us the tools to write our own predicates. Instead of trying to make it aware of our application-specific concerns, we can use the generic predicates #is? and #is-not? to mark certain query captures with data, then use that data to filter the results however we like.

The downside is that Tree-sitter can\u2019t approve or reject captures with these predicates on its own like it can with #match?. Instead, we\u2019ll have to \u201Cprocess\u201D these captures after the fact and filter them manually. But it\u2019s worth the effort because\xA0it lets us use whatever logic we want \u2014\xA0even Pulsar-specific logic that means nothing to Tree-sitter.

Let\u2019s illustrate.

(string
+  "'" @punctuation.definition.string.begin.js
+  (#is? test.first "true"))
+

The two parameters after #is? are arbitrary values of my own invention. Tree-sitter simply treats them as a key and value and applies some metadata to this capture. Any nodes that get captured by this query will contain some data under assertedProperties:

capture.assertedProperties["test.first"]; //-> "true"
+

In fact, I can omit that second argument; for boolean tests like this one, the presence of the property is all we need:

(string
+  "'" @punctuation.definition.string.begin.js
+  (#is? test.first))
+(string
+  "'" @punctuation.definition.string.end.js
+  (#is? test.last))
+

Using (#is? test.first) is like having Tree-sitter attach a Post-It note to a capture object with the text \u201Cremember to assert test.first later\u201D written on it. Tree-sitter doesn\u2019t know or care what that means, but it assumes we will.

And in this case, test.first corresponds to a function we\u2019ve written that looks like this:

function first(node) {
+	if (!node.parent) return true;
+	return node?.parent?.firstChild?.id === node.id;
+}
+

This function first ensures that root nodes (which have no parent) will always pass the test. Then it compares our node to its parent\u2019s first child. If they\u2019re equal, the test passes. If they\u2019re not, then the captured node isn\u2019t the first child of its parent, and we can ignore it.

The logic for our last function is identical, except that it compares our node to node.parent.lastChild.

Even better: the existence of #is-not? means that we get negation practically for free. Suppose I did this:

(string
+  "'" @punctuation.definition.string.end.js
+  (#is-not? test.first))
+

Then the metadata would exist in a different place\u2026

"test.first" in capture.refutedProperties; //-> true
+

\u2026and I\u2019d know to ignore this capture unless my first function fails for this node.

So now we\u2019ve got a way to filter capture names by any criteria we can think of. If we can test for it in JavaScript, it can be used as a predicate in Tree-sitter queries.

We call these custom predicates scope tests, and we\u2019ve grouped them under a test. namespace for reasons that may make more sense later. Scope tests are a crucial tool for solving the first of those two problems we described earlier: they let us query for tree nodes in arbitrary ways that the legacy system simply couldn\u2019t.

And because scope tests are just JavaScript, we\u2019re able to use some oddball criteria for accepting or rejecting captures. Consider these examples:

((program) @source.js.embedded
+  (#is? test.injection))
+
+(variable_declarator
+  name: (identifier) @constant.other.foo
+  (#match? @constant.other.foo "^[_A-Z]+$")
+  (#is? test.config "language-foo.highlightAllCapsIdentifiersAsConstants"))
+

Both of these are scope tests that grammar query files can use. The first one applies a scope only if we\u2019re in an injection layer \u2014 for instance, if this is JavaScript inside of a SCRIPT tag in an HTML file. The second one applies a scope only if the user has enabled a certain configuration option. Neither one has anything to do with Tree-sitter itself, but we can use them in Tree-sitter query files all the same.

`,23),U=t("code",null,"ScopeResolver",-1),V=t("code",null,"first",-1),z={href:"https://github.com/pulsar-edit/pulsar/blob/v1.109.0/src/scope-resolver.js#L583-L591",target:"_blank",rel:"noopener noreferrer"},D=t("code",null,"ScopeResolver.TESTS.first",-1),X=t("code",null,"ScopeResolver",-1),Z=t("code",null,"test.first",-1),G=a(`

There are other scope tests that we\u2019ve found to be quite useful \u2014\xA0tests which any grammar author can use in their own query files:

But for our current goal \u2014 applying punctuation scopes to string delimiters \u2014 implementing test.first and test.last is enough to get us the outcome we want.

You may remember from earlier that the legacy Tree-sitter integration used a CSS-like syntax to describe nodes. It supported combinators like > and even pseudoclasses like :nth-child, but not much else. Tree-sitter\u2019s own query system can do much more than that \u2014 and it\u2019s extensible, so we can add our own logic wherever we need it.

So we\u2019ve done it! We now have the ability to scope a JavaScript string identically between our two different grammar systems. And we\u2019ve moved past the legacy system\u2019s first drawback: a brittle query system. Our first challenge has been vanquished.

The second challenge: scoping arbitrary ranges

To solve the second drawback \u2014\xA0inability to scope the correct ranges \u2014 let\u2019s create another challenge.

TextMate grammars will scope comments differently based on whether they\u2019re line comments or block comments. There\u2019s also a convention to annotate comment.line scopes with their delimiter type:

// this is a comment
+

A typical TextMate grammar for JavaScript would scope this comment as comment.line.double-slash.js, and would further scope the // as punctuation.definition.comment.js.

comment scopes

Can we do that in a Tree-sitter grammar with the tools we\u2019ve already got? Let\u2019s inspect what our example comment looks like in a Tree-sitter tree:

comment tree

Hmm. No anonymous nodes or anything. Just one node called comment.

It\u2019s a strong Tree-sitter convention that various sorts of code comments are all represented by nodes called comment. So this query would do the right thing in our example\u2026

(comment) @comment.line.double-slash.js
+

\u2026but would be incorrect in other scenarios because it would match block comments as well as line comments.

But we can use #match? to distinguish between the two kinds of comments:

((comment) @comment.line.double-slash.js
+  (#match? @comment.line.double-slash.js "^\\/\\/"))
+
+((comment) @comment.block.js
+  (#match? @comment.block.js "^\\/*"))
+

In the latter case, we don\u2019t have to test for the presence of */ at the end. If the contents of the comment begin with /*, that\u2019s all the information we need; we know it must end with */, or else the parser wouldn\u2019t have classified it as a comment.

But what about our punctuation.definition.comment.js scope? Sadly, tree-sitter-javascript (and most other parsers) don\u2019t make it easy to target the comment delimiters themselves. Comment delimiters, unlike string delimiters, usually aren\u2019t available as anonymous nodes.

Hence the legacy Tree-sitter system has never been able to annotate that // with the punctuation scope it needed. We\u2019ll need to solve this ourselves.

Scope adjustments

In this case, it\u2019d be more convenient if there were a node for the // we want to scope. But we control the internals of the editor, and we can tell it to apply scope names to whatever buffer ranges we want. When the tree doesn\u2019t do all of our work for us, it just means we have to try a bit harder.

In this case, the comment node tells us where the comment starts in the buffer. And once it passes the #match? predicate, we know that it starts with //, so it must end two characters later. Not the hardest problem to solve!

The trickiest part will be figuring out how to describe these buffer positions in a query.

Adjusting by pattern

What if we did this?

((comment) @comment.line.double-slash.js
+  (#match? @comment.line.double-slash.js "^//"))
+
+((comment) @punctuation.definition.comment.js
+  (#match? @punctuation.definition.comment.js "^//")
+  (#set! adjust.endAfterFirstMatchOf "^//"))
+

Here we\u2019re capturing the same thing twice under different names. In the second case, we\u2019re using #set! \u2014\xA0a predicate very similar to #is? and #is-not? \u2014 to attach a qualifier: instead of stopping at the end of the line comment, stop\xA0right after the // near the beginning.

Just like #is? predicates are represented on captures under assertedProperties and #is-not? predicates are represented under refutedProperties, #set! predicates are represented in their own bucket simply called properties.

Note the semantic difference between a predicate that ends in ! and one that ends in ?. In this case we\u2019re not setting up a test for the capture to pass or fail; we\u2019re attaching a side effect to the capture. Imagine a Post-It note attached to the capture that says \u201Cremember to adjust the range for this capture to end at X.\u201D

I mentioned earlier that all nodes remember their corresponding buffer range \u2014 starting at row W and column X, ending at row Y and column Z. By default, this is the range against which a scope is applied. But in this case, the adjust.endAfterFirstMatchOf predicate reminds us to execute a regular expression match on the node\u2019s contents and move the ending position to the end of that match, instead of the node\u2019s natural ending point.

`,33),K={href:"https://github.com/pulsar-edit/pulsar/blob/v1.109.0/src/scope-resolver.js#L883-L897",target:"_blank",rel:"noopener noreferrer"},Q=a(`

And how would we handle the delimiters of a block comment?

((comment) @comment.block.js
+	(#match? @comment.block.js "^/\\\\*"))
+
+((comment) @punctuation.definition.comment.begin.js
+  (#match? @punctuation.definition.comment.begin.js "^/\\\\*")
+  (#set! adjust.endAfterFirstMatchOf "^/\\\\*"))
+
+((comment) @punctuation.definition.comment.end.js
+  (#match? @punctuation.definition.comment.end.js "\\\\*/$")
+  (#set! adjust.startBeforeFirstMatchOf "\\\\*/$"))
+
+

We can scope the opening delimiter the same way. To scope the ending delimiter, we move the head of the range to the position at the beginning of a regex match. (And for situations where you want to move both the head and the tail at once, there\u2019s adjust.startAndEndAroundFirstMatchOf).

Adjusting by node position descriptor

Let\u2019s look at another example.

function SomeComponent(props) {
+	return <SomeOtherComponent {...props} />;
+}
+

Let\u2019s say we want to scope the /> at the end of the self-closing tag. Tree-sitter represents that as two separate anonymous nodes \u2014 / and >.

So we\u2019ve got a different problem here: there\u2019s no single node that includes both boundaries of the range we want to scope. How can we make the scope span two adjacent nodes?

We could probably use a pattern-based solution here like we did above. But we could also leverage a useful feature of how nodes are represented in the tree.

`,9),ee={href:"https://developer.mozilla.org/en-US/docs/Web/API/Node",target:"_blank",rel:"noopener noreferrer"},te=t("code",null,"parentNode",-1),se=t("code",null,"nextSibling",-1),ne=a(`

A similar system exists for Tree-sitter nodes, and it gives us a simple way to describe relationships between nodes:

((jsx_self_closing_element
+  ; The "/>" in \`<Foo />\`, extended to cover both anonymous nodes at once.
+  "/") @punctuation.definition.tag.end.js
+  (#set! adjust.startAt lastChild.previousSibling.startPosition)
+  (#set! adjust.endAt lastChild.endPosition))
+

Here we\u2019re capturing the entire self-closing element, then moving the ends of the range to the specific boundaries that we need: starting at the beginning of second-to-last node child, and ending at the end of the last node child. The / and > will always be the last two children of the node we captured, so this is a simple and repeatable way to describe the boundaries we want.

`,3),ae=t("em",null,"node position descriptor",-1),oe={href:"https://docs-lodash.com/v4/get/",target:"_blank",rel:"noopener noreferrer"},ie=t("code",null,"_.get",-1),re=a(`

So in order to pass our \u201CJavaScript line comment\u201D challenge, we\u2019ve had to invent another feature called scope adjustments. With the infrastructure we\u2019ve already got, scope adjustments are an easy thing to add: a scope adjustment is just a function that accepts a capture object and returns a range. We process them in the post-capture phase immediately before we apply scope tests.

Scope adjustments are our answer to the legacy system\u2019s second limitation. They allow us to embrace the syntax tree when it works in our favor, but still break free of it whenever we need to.

What does this get us?

Some of you, having gotten this far, might wonder to yourselves: if it takes this much effort just to get feature parity with a TextMate grammar, why not just stick with TextMate grammars?

One answer is simple: despite how complex this may seem, and how many implementation details I\u2019ve hidden from you, the effort it\u2019s taken to integrate Tree-sitter grammars into Pulsar is much, much less than the effort it originally took to integrate TextMate grammars into Atom.

But the message here can\u2019t just be \u201Cwe implemented a cool new thing that you won\u2019t notice at all!\u201D The whole point is that this is better than \u2014 not just equivalent to \u2014\xA0what we had before, and not just for the few of us who write grammars.

So let me give you an example of\xA0something that was very hard with a TextMate grammar, but is now quite easy.

When a scope name might be useful for semantic reasons, but not for syntax highlighting reasons, TextMate grammars add scope names that use the meta namespace. You might see some meta scope names when you run the \u201CLog Cursor Scope\u201D command, but most of them don\u2019t have an effect on how your code looks. Yet that information is visible to snippets, settings, and commands, and it can be very useful.

In the language-javascript package, Pulsar defines a fun snippet that expands as follows, with the string functionName highlighted:

function functionName() {}
+

That\u2019s great, but there\u2019s more than one syntax for defining functions. Imagine if you could use fun inside of a class body and have it expand to the correct syntax for defining an instance method:

class Foo {
+	functionName() {}
+}
+

Or inside of an object literal \u2014 much like the class body syntax, but with a trailing comma so as to prevent syntax errors:

const Foo = {
+	functionName() {},
+};
+

TextMate grammars weren\u2019t built with this sort of thing in mind. With much effort, you could probably pull it off, but only by making the grammar so complex that it might as well be a parser. But Tree-sitter is already a parser. It understands your code well enough to make these things easy.

With our rich syntax tree, we can now apply meta scope names rather liberally, marking sections of the buffer with useful metadata that can be used by commands and snippets. We can scope the inside of a class body and the inside of an object literal:

; The interior of a class body.
+((class_body) @meta.block.class.js
+  ; Start after \`{\`\u2026
+  (#set! adjust.startAt firstChild.endPosition)
+  ; \u2026and end before \`}\`.
+  (#set! adjust.endAt lastChild.startPosition))
+
+; The inside of an object literal.
+((object) @meta.object.js
+  ; Start after \`{\`\u2026
+  (#set! adjust.startAt firstChild.endPosition)
+  ; \u2026and end before \`}\`.
+  (#set! adjust.endAt lastChild.startPosition))
+

By defining these two scope names, we\u2019ve exposed these concepts to all the systems that consume scope names, including snippets.

The language-javascript package already defines fun one way, but we can redefine it for more specific scopes:

'.source.js .meta.block.class.js':
+  'Function':
+    'prefix': 'fun'
+    'body': '\${1:functionName}($2) {\\n\\t$0\\n}'
+
+'.source.js .meta.object.js':
+  'Function':
+    'prefix': 'fun'
+    'body': '\${1:functionName}($2) {\\n\\t$0\\n},'
+

Now we\u2019ve made the fun snippet much more useful. When we type fun and press Tab, the snippets package will pick the version that matches the context of the cursor most closely.

This will work identically whether you use tab triggers or choose your snippets from an autocomplete menu.

This change isn\u2019t just theoretical; it\u2019s been implemented in the language-javascript grammar package, and it shipped with Pulsar 1.109.

Next time

Syntax highlighting isn\u2019t the only way that Tree-sitter\u2019s query system can make our lives easier. In the next installment we\u2019ll tackle two tasks that the legacy Tree-sitter integration never addressed: indentation and code folding.

',26);function de(ce,le){const s=l("ExternalLinkIcon");return r(),d("div",null,[t("p",null,[t("a",f,[e("Last time I laid out the case"),n(s)]),e(" for why we chose to embrace TextMate-style scope names, even in newer Tree-sitter grammars. I set a difficult challenge for Pulsar: "),b]),w,c(" more "),t("p",null,[e("If you\u2019d like to follow along with these code examples in your own Pulsar installation, I\u2019d suggest installing the "),t("a",y,[k,e(" package"),n(s)]),e(". It includes a language grammar for the Tree-sitter query files we\u2019ll be spending most of this article writing, and it lets us visualize the syntax trees that Tree-sitter produces.")]),t("p",null,[e("If you just want to get a sense of what a Tree-sitter tree looks like, you can use the "),t("a",_,[e("Playground on the Tree-sitter web site"),n(s)]),e(".")]),j,t("p",null,[e("So Tree-sitter added a powerful "),t("a",x,[e("query system"),n(s)]),e(", with a Lisp-like syntax most directly influenced by "),t("a",q,[e("Scheme"),n(s)]),e(". This system wasn\u2019t around for the legacy implementation to use \u2014 but it\u2019s here for us now, and it\u2019s going to make our job much easier.")]),t("p",null,[e("Tree-sitter query syntax does the same thing for a Tree-sitter syntax tree that CSS selector syntax does for HTML: it gives us a terse way to describe a set of nodes in a tree. And just as people eventually realized that CSS selector syntax was useful for more than just styling \u2014 see "),t("a",T,[S,n(s)]),e(" \u2014 you\u2019ll soon see that Tree-sitter queries are useful for more than just making strings green.")]),I,t("p",null,[e("Tree-sitter itself "),t("a",A,[e("has demonstrated"),n(s)]),e(" how query files can be used to highlight code. If a parser has a "),W,e(" file defined in its repository, the CLI will allow you to run "),P,e(" on arbitrary input. It\u2019ll parse the input, figure out which parser should do the job, use that parser\u2019s "),M,e(" to map certain nodes to query capture names, and then emit highlighted output in your terminal.")]),L,t("p",null,[e("Using the "),t("a",C,[e("tree-sitter-tools package"),n(s)]),e(", I can open an inspector pane and look at the raw tree for this string:")]),B,N,t("p",null,[e("Most parsers build "),t("a",H,[J,e(" syntax trees"),n(s)]),e(". In the process of making the tree, they discard lots of information that isn\u2019t important to the parser\u2019s goal. But Tree-sitter builds "),O,e(" syntax trees! Every character in our JavaScript file will end up being represented by at least one node, and every node remembers the exact buffer range it covers. If it didn\u2019t, we wouldn\u2019t be able to use it for syntax highlighting.")]),F,t("p",null,[e("Surprisingly, we can\u2019t solve this problem without some external help. Tree-sitter queries "),t("a",$,[e("have a concept of \u201Canchors\u201D"),n(s)]),e(" that can enforce positioning of children \u2014 for instance, targeting only the first node child of a parent \u2014 but they can be used only for "),E,e(" nodes, not anonymous nodes. "),Y]),R,t("p",null,[e("The logic for applying tests and winnowing a list of raw captures exists in Pulsar in a class called "),U,e(". That "),V,e(" function defined above is present at "),t("a",z,[D,n(s)]),e(", and there\u2019s logic in "),X,e(" that matches up a "),Z,e(" property to that function.")]),G,t("p",null,[e("So in the absence of any node boundary at the position we want, we\u2019ve found a rather simple alternative way to express that position. We still have to write "),t("a",K,[e("our own code"),n(s)]),e(" to make it happen, but that won\u2019t be a problem. Since we already have to loop through our captures to apply scope tests, we might as well use that opportunity to tweak the range of a capture if we need to.")]),Q,t("p",null,[e("You might be familiar with how "),t("a",ee,[e("DOM nodes in the browser"),n(s)]),e(" are traversible via properties like "),te,e(", "),se,e(", and so on. If you\u2019ve got a reference to a particular DOM node, you can use those properties to jump from that node to any other node in the tree, as long as you you know how the two nodes are related.")]),ne,t("p",null,[e("This syntax for describing a position relative to a given node is something we call a "),ae,e(". If you\u2019ve ever used a function like "),t("a",oe,[e("Lodash\u2019s "),ie,n(s)]),e(", you might feel at home with this syntax \u2014 it resolves a chain of property lookups all at once, and fails gracefully if any of them aren\u2019t present. It\u2019ll come in handy later for other purposes.")]),re])}const he=i(v,[["render",de],["__file","20231013-savetheclocktower-modern-tree-sitter-part-3.html.vue"]]);export{he as default}; diff --git a/assets/20231016-Daeraxa-v1.110.0.html.1a72c098.js b/assets/20231016-Daeraxa-v1.110.0.html.1a72c098.js new file mode 100644 index 0000000000..c279fab9dd --- /dev/null +++ b/assets/20231016-Daeraxa-v1.110.0.html.1a72c098.js @@ -0,0 +1 @@ +const a=JSON.parse(`{"key":"v-e8324d12","path":"/blog/20231016-Daeraxa-v1.110.0.html","title":"Armed with a big ol' can of Raid: Pulsar 1.110.0 is available now!","lang":"en-US","frontmatter":{"title":"Armed with a big ol' can of Raid: Pulsar 1.110.0 is available now!","author":"Daeraxa","date":"2023-10-16T00:00:00.000Z","category":["dev"],"tag":["release"]},"excerpt":"

Armed with a big ol' can of Raid: Pulsar 1.110.0 is available now!

\\n","headers":[{"level":2,"title":"What do we have for you in Pulsar 1.110.0?","slug":"what-do-we-have-for-you-in-pulsar-1-110-0","link":"#what-do-we-have-for-you-in-pulsar-1-110-0","children":[{"level":3,"title":"Pulsar","slug":"pulsar","link":"#pulsar","children":[]},{"level":3,"title":"ppm","slug":"ppm","link":"#ppm","children":[]}]}],"git":{"updatedTime":1697474370000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":3.97,"words":1192},"filePathRelative":"blog/20231016-Daeraxa-v1.110.0.md","localizedDate":"October 16, 2023"}`);export{a as data}; diff --git a/assets/20231016-Daeraxa-v1.110.0.html.5b3b1354.js b/assets/20231016-Daeraxa-v1.110.0.html.5b3b1354.js new file mode 100644 index 0000000000..47d5ebc39a --- /dev/null +++ b/assets/20231016-Daeraxa-v1.110.0.html.5b3b1354.js @@ -0,0 +1 @@ +import{_ as n,o as l,c as a,a as e,b as t,d as r,e as i,f as s,r as p}from"./app.0e1565ce.js";const d={},u={href:"https://github.com/pulsar-edit/pulsar/releases/tag/v1.110.0",target:"_blank",rel:"noopener noreferrer"},c=e("h2",{id:"what-do-we-have-for-you-in-pulsar-1-110-0",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#what-do-we-have-for-you-in-pulsar-1-110-0","aria-hidden":"true"},"#"),t(" What do we have for you in Pulsar 1.110.0?")],-1),h=e("p",null,"Here we are with another Pulsar release, and this month we have quite a number of fixes and improvements. This time around, the focus has really been on bug fixes in order to improve the overall experience.",-1),f=e("code",null,"node-gyp",-1),_={href:"https://pulsar-edit.dev/blog/20230801-Daeraxa-AugustUpdate.html#moving-ppm-to-our-own-npm-fork",target:"_blank",rel:"noopener noreferrer"},m={href:"https://github.com/twocolours",target:"_blank",rel:"noopener noreferrer"},g={href:"https://github.com/GuilleW",target:"_blank",rel:"noopener noreferrer"},b={href:"https://pulsar-edit.dev/blog/20230916-Daeraxa-v1.109.0.html",target:"_blank",rel:"noopener noreferrer"},w=e("p",null,"Onto Pulsar itself: we have a few new features that have been added. The first is a new autocomplete API that works on ranges rather than the previous prefix system, which will improve LSP support. (And on the topic of autocomplete, if anyone had been editing EJS files and noticed errors popping up, these have now been greatly reduced in this update).",-1),k=e("p",null,"Onto the fixes. This first one solves an issue where, if you attempted to start Pulsar with an invalid configuration file, then it would silently fail but still present a running process. Error checking has now been added so that the error can actually be exposed to the user.",-1),v=e("p",null,[t("Next, we have a problem introduced with our "),e("code",null,"snippets"),t(" package update, which now includes variables indicated by a "),e("code",null,"$"),t(", which is also used by PHP, so some escaping of these characters needed to be added.")],-1),D={href:"https://pulsar-edit.dev/blog/20231004-Daeraxa-OctoberUpdate.html#macos-binary-signing-issues",target:"_blank",rel:"noopener noreferrer"},y=s('

And that just about covers things for this release. We hope you have enjoyed reading this and continue to enjoy using Pulsar. As ever, a huge thank you to our generous donors and community, without whom this project would not be possible.

Until next time, happy coding, and see you amongst the stars!


Pulsar

',6),x={href:"https://github.com/pulsar-edit/pulsar/pull/762",target:"_blank",rel:"noopener noreferrer"},P={href:"https://github.com/pulsar-edit/pulsar/pull/735",target:"_blank",rel:"noopener noreferrer"},W={href:"https://github.com/pulsar-edit/pulsar/pull/758",target:"_blank",rel:"noopener noreferrer"},G={href:"https://github.com/pulsar-edit/pulsar/pull/750",target:"_blank",rel:"noopener noreferrer"},S=e("code",null,"try/catch",-1),A={href:"https://github.com/pulsar-edit/pulsar/pull/753",target:"_blank",rel:"noopener noreferrer"},T={href:"https://github.com/pulsar-edit/pulsar/pull/752",target:"_blank",rel:"noopener noreferrer"},C={href:"https://github.com/pulsar-edit/pulsar/pull/745",target:"_blank",rel:"noopener noreferrer"},F={href:"https://github.com/pulsar-edit/pulsar/pull/743",target:"_blank",rel:"noopener noreferrer"},I={href:"https://github.com/pulsar-edit/pulsar/pull/738",target:"_blank",rel:"noopener noreferrer"},M={href:"https://github.com/pulsar-edit/pulsar/pull/725",target:"_blank",rel:"noopener noreferrer"},U={href:"https://github.com/pulsar-edit/pulsar/pull/479",target:"_blank",rel:"noopener noreferrer"},O=e("h3",{id:"ppm",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#ppm","aria-hidden":"true"},"#"),t(" ppm")],-1),B={href:"https://github.com/pulsar-edit/ppm/pull/94",target:"_blank",rel:"noopener noreferrer"},E={href:"https://github.com/pulsar-edit/ppm/pull/92",target:"_blank",rel:"noopener noreferrer"},R={href:"https://github.com/pulsar-edit/ppm/pull/90",target:"_blank",rel:"noopener noreferrer"},V=e("code",null,"second-mate",-1),L=e("code",null,"first-mate",-1),N={href:"https://github.com/pulsar-edit/ppm/pull/86",target:"_blank",rel:"noopener noreferrer"},j=e("code",null,"visualStudioIsInstalled()",-1),H={href:"https://github.com/pulsar-edit/ppm/pull/85",target:"_blank",rel:"noopener noreferrer"},J={href:"https://github.com/pulsar-edit/ppm/pull/84",target:"_blank",rel:"noopener noreferrer"},q=e("code",null,"postinstall",-1),z={href:"https://github.com/pulsar-edit/ppm/pull/83",target:"_blank",rel:"noopener noreferrer"},$=e("code",null,"master",-1),Y={href:"https://github.com/pulsar-edit/ppm/pull/81",target:"_blank",rel:"noopener noreferrer"},K={href:"https://github.com/pulsar-edit/ppm/pull/79",target:"_blank",rel:"noopener noreferrer"},Q={href:"https://github.com/pulsar-edit/ppm/pull/78",target:"_blank",rel:"noopener noreferrer"},X={href:"https://github.com/pulsar-edit/ppm/pull/77",target:"_blank",rel:"noopener noreferrer"},Z={href:"https://github.com/pulsar-edit/ppm/pull/76",target:"_blank",rel:"noopener noreferrer"},ee={href:"https://github.com/pulsar-edit/ppm/pull/75",target:"_blank",rel:"noopener noreferrer"},te={href:"https://github.com/pulsar-edit/ppm/pull/74",target:"_blank",rel:"noopener noreferrer"},oe={href:"https://github.com/pulsar-edit/ppm/pull/73",target:"_blank",rel:"noopener noreferrer"},re={href:"https://github.com/pulsar-edit/ppm/pull/72",target:"_blank",rel:"noopener noreferrer"},ne={href:"https://github.com/pulsar-edit/ppm/pull/71",target:"_blank",rel:"noopener noreferrer"},le={href:"https://github.com/pulsar-edit/ppm/pull/70",target:"_blank",rel:"noopener noreferrer"},ae={href:"https://github.com/pulsar-edit/ppm/pull/69",target:"_blank",rel:"noopener noreferrer"},ie={href:"https://github.com/pulsar-edit/ppm/pull/68",target:"_blank",rel:"noopener noreferrer"},se={href:"https://github.com/pulsar-edit/ppm/pull/67",target:"_blank",rel:"noopener noreferrer"},pe={href:"https://github.com/pulsar-edit/ppm/pull/66",target:"_blank",rel:"noopener noreferrer"},de={href:"https://github.com/pulsar-edit/ppm/pull/65",target:"_blank",rel:"noopener noreferrer"},ue={href:"https://github.com/pulsar-edit/ppm/pull/64",target:"_blank",rel:"noopener noreferrer"},ce={href:"https://github.com/pulsar-edit/ppm/pull/63",target:"_blank",rel:"noopener noreferrer"};function he(fe,_e){const o=p("ExternalLinkIcon");return l(),a("div",null,[e("p",null,[t("Armed with a big ol' can of Raid: Pulsar "),e("a",u,[t("1.110.0"),r(o)]),t(" is available now!")]),i(" more "),c,h,e("p",null,[t("Starting with changes to PPM (Pulsar Package Manager), it has been updated to use a newer version of "),f,t(" (a tool for compiling native modules for node.js), which will allow use of newer C/C++ compiler toolchains as well as newer versions of Python, namely 3.11, which introduced an issue for PPM requiring downgrading to 3.10. For Windows users, it also now supports Visual Studio 2022! We previously covered (part of) this topic in one of our community update "),e("a",_,[t("blog posts"),r(o)]),t(" in a bit more detail, so be sure to have a read of that too.")]),e("p",null,[t("On the topic of PPM, the decaffeination (conversion of CoffeeScript to JavaScript) has now been completed thanks to community members "),e("a",m,[t("@nemokosch/@twocolours"),r(o)]),t(" & "),e("a",g,[t("@GuilleW"),r(o)]),t(". In the "),e("a",b,[t("last release"),r(o)]),t(", we announced this had been completed in the core editor and packages; now this has been extended to PPM!")]),w,k,v,e("p",null,[t("And lastly, we had a problem with macOS binary signing in version '1.109.0'; this was already covered in the last "),e("a",D,[t("community update"),r(o)]),t(", and now this fix applies to our regular releases.")]),y,e("ul",null,[e("li",null,[t("Fixed: Fixed filtering of suggestions with ranges "),e("a",x,[t("@mauricioszabo"),r(o)])]),e("li",null,[t("Added: Tree-sitter running fixes for September "),e("a",P,[t("@savetheclocktower"),r(o)])]),e("li",null,[t("Added: Add escapement to variable literals within php snippets "),e("a",W,[t("@Spiker985"),r(o)])]),e("li",null,[t("Added: [core] Handle invalid config on load "),e("a",G,[t("@confused-Techie"),r(o)])]),e("li",null,[t("Added: [autocomplete-html] Wrap completions in "),S,t(" handler "),e("a",A,[t("@confused-Techie"),r(o)])]),e("li",null,[t("Bumped: Update dependency postcss to v8.4.31 [SECURITY] "),e("a",T,[t("@renovate"),r(o)])]),e("li",null,[t("Fixed: CI: Sign macOS binaries for branch pushes, not PRs "),e("a",C,[t("@DeeDeeG"),r(o)])]),e("li",null,[t("Fixed: CI: Use Python 3.11 to fix macOS signing "),e("a",F,[t("@DeeDeeG"),r(o)])]),e("li",null,[t("Fixed: [meta] Fix Windows Builds in CI "),e("a",I,[t("@confused-Techie"),r(o)])]),e("li",null,[t("Bumped: ppm: Update ppm submodule to commit a2ade745bfbc5f "),e("a",M,[t("@DeeDeeG"),r(o)])]),e("li",null,[t("Added: Making autocomplete-plus work to replace ranges "),e("a",U,[t("@mauricioszabo"),r(o)])])]),O,e("ul",null,[e("li",null,[t("Bumped: Update npm and node-gyp, for macOS signing fix "),e("a",B,[t("@DeeDeeG"),r(o)])]),e("li",null,[t("Removed: Remove remnants of Coffeescript building "),e("a",E,[t("@2colours"),r(o)])]),e("li",null,[t("Added: Update unpublishing wording "),e("a",R,[t("@Daeraxa"),r(o)])]),e("li",null,[t("Added: Migrate to "),V,t(" and remove "),L,t(),e("a",N,[t("@confused-Techie"),r(o)])]),e("li",null,[t("Added: Cleanup "),j,t(),e("a",H,[t("@confused-Techie"),r(o)])]),e("li",null,[t("Decafed: Decaf Source "),e("a",J,[t("@confused-Techie"),r(o)])]),e("li",null,[t("Fixed: Make "),q,t(" scripts work on Windows with spaces in cwd path "),e("a",z,[t("@confused-Techie"),r(o)])]),e("li",null,[t("Added: Move Spec Decaf PRs into "),$,t(),e("a",Y,[t("@confused-Techie"),r(o)])]),e("li",null,[t("Bumped: Switch to our npm fork, to get newer node-gyp (node-gyp 9.x) "),e("a",K,[t("@DeeDeeG"),r(o)])]),e("li",null,[t("Decafed: Decaffeinate remaining spec files from list-spec on "),e("a",Q,[t("@GuilleW and @2colours"),r(o)])]),e("li",null,[t("Decafed: Decaffeinate link spec "),e("a",X,[t("@GuilleW and @2colours"),r(o)])]),e("li",null,[t("Decafed: Decaffeinate install spec "),e("a",Z,[t("@GuilleW and @2colours"),r(o)])]),e("li",null,[t("Decafed: Decaffeinate init spec "),e("a",ee,[t("@GuilleW and @2colours"),r(o)])]),e("li",null,[t("Decafed: Decaffeinate help spec "),e("a",te,[t("@GuilleW and @2colours"),r(o)])]),e("li",null,[t("Decafed: Decaffeinate featured spec "),e("a",oe,[t("@GuilleW and @2colours"),r(o)])]),e("li",null,[t("Decafed: Decaffeinate enable spec "),e("a",re,[t("@GuilleW and @2colours"),r(o)])]),e("li",null,[t("Decafed: Decaffeinate docs spec "),e("a",ne,[t("@GuilleW and @2colours"),r(o)])]),e("li",null,[t("Decafed: Decaffeinate disable spec "),e("a",le,[t("@GuilleW and @2colours"),r(o)])]),e("li",null,[t("Decafed: Decaffeinate develop spec "),e("a",ae,[t("@GuilleW and @2colours"),r(o)])]),e("li",null,[t("Decafed: Decaffeinate config spec "),e("a",ie,[t("@GuilleW and @2colours"),r(o)])]),e("li",null,[t("Decafed: Decaffeinate command spec "),e("a",se,[t("@GuilleW and @2colours"),r(o)])]),e("li",null,[t("Decafed: Decaffeinate clean spec "),e("a",pe,[t("@GuilleW and @2colours"),r(o)])]),e("li",null,[t("Decafed: Decaffeinate ci spec "),e("a",de,[t("@GuilleW and @2colours"),r(o)])]),e("li",null,[t("Decafed: Decaffeinate apm cli spec "),e("a",ue,[t("@GuilleW and @2colours"),r(o)])]),e("li",null,[t("Decafed: Decaffeinate spec helper (updated) "),e("a",ce,[t("@GuilleW and @2colours"),r(o)])])])])}const ge=n(d,[["render",he],["__file","20231016-Daeraxa-v1.110.0.html.vue"]]);export{ge as default}; diff --git a/assets/20231031-savetheclocktower-modern-tree-sitter-part-4.html.ac9522c5.js b/assets/20231031-savetheclocktower-modern-tree-sitter-part-4.html.ac9522c5.js new file mode 100644 index 0000000000..eb76d13b38 --- /dev/null +++ b/assets/20231031-savetheclocktower-modern-tree-sitter-part-4.html.ac9522c5.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-5fc19751","path":"/blog/20231031-savetheclocktower-modern-tree-sitter-part-4.html","title":"Modern Tree-sitter, part 4: indentation and code folding","lang":"en-US","frontmatter":{"title":"Modern Tree-sitter, part 4: indentation and code folding","author":"savetheclocktower","date":"2023-10-31T00:00:00.000Z","category":["dev"],"tag":["modernization","tree-sitter"]},"excerpt":"

Last time we looked at Tree-sitter\u2019s query system and showed how it can be used to make a syntax highlighting engine in Pulsar. But syntax highlighting is simply the most visible of the various tasks that a language package performs.

\\n

Today we\u2019ll look at two other systems \u2014 indentation hinting and code folding \u2014 and I\u2019ll explain how queries can be used to support each one.

\\n","headers":[{"level":2,"title":"Indentation","slug":"indentation","link":"#indentation","children":[{"level":3,"title":"How TextMate grammars do it","slug":"how-textmate-grammars-do-it","link":"#how-textmate-grammars-do-it","children":[]},{"level":3,"title":"How legacy Tree-sitter grammars do it","slug":"how-legacy-tree-sitter-grammars-do-it","link":"#how-legacy-tree-sitter-grammars-do-it","children":[]},{"level":3,"title":"Working within the system","slug":"working-within-the-system","link":"#working-within-the-system","children":[]},{"level":3,"title":"Getting creative","slug":"getting-creative","link":"#getting-creative","children":[]}]},{"level":2,"title":"Code folding","slug":"code-folding","link":"#code-folding","children":[{"level":3,"title":"How TextMate grammars do it","slug":"how-textmate-grammars-do-it-1","link":"#how-textmate-grammars-do-it-1","children":[]},{"level":3,"title":"How legacy Tree-sitter grammars do it","slug":"how-legacy-tree-sitter-grammars-do-it-1","link":"#how-legacy-tree-sitter-grammars-do-it-1","children":[]},{"level":3,"title":"Using queries to define fold ranges","slug":"using-queries-to-define-fold-ranges","link":"#using-queries-to-define-fold-ranges","children":[]}]},{"level":2,"title":"Why is this so complicated?","slug":"why-is-this-so-complicated","link":"#why-is-this-so-complicated","children":[]},{"level":2,"title":"Next time","slug":"next-time","link":"#next-time","children":[]}],"git":{"updatedTime":1698809922000,"contributors":[{"name":"Andrew Dupont","email":"github@andrewdupont.net","commits":4}]},"readingTime":{"minutes":13.28,"words":3983},"filePathRelative":"blog/20231031-savetheclocktower-modern-tree-sitter-part-4.md","localizedDate":"October 31, 2023"}');export{e as data}; diff --git a/assets/20231031-savetheclocktower-modern-tree-sitter-part-4.html.c1f7638e.js b/assets/20231031-savetheclocktower-modern-tree-sitter-part-4.html.c1f7638e.js new file mode 100644 index 0000000000..a4603ad412 --- /dev/null +++ b/assets/20231031-savetheclocktower-modern-tree-sitter-part-4.html.c1f7638e.js @@ -0,0 +1,86 @@ +import{_ as l,o as c,c as h,a as t,b as e,d as n,w as u,e as p,f as a,r as o}from"./app.0e1565ce.js";const i="/assets/tree-sitter-simple-indentation.6a2dba5c.webm",r="/assets/tree-sitter-simple-indentation.7d0eae68.mp4",m="/assets/tree-sitter-tools-indentation-example.52c421af.png",v="/assets/tree-sitter-advanced-indentation-part-1.98936604.webm",b="/assets/tree-sitter-advanced-indentation-part-1.33a3a459.mp4",g="/assets/tree-sitter-advanced-indentation-part-2.e941a3ba.webm",f="/assets/tree-sitter-advanced-indentation-part-2.9c4500ba.mp4",w="/assets/tree-sitter-simple-fold-example.8070404a.png",y="/assets/tree-sitter-tools-css-block.47b94e88.png",k="/assets/tree-sitter-javascript-block-comment.812590a9.png",x="/assets/tree-sitter-block-comment-code-fold.b6a82fbf.webm",_="/assets/tree-sitter-block-comment-code-fold.28ab19f5.mp4",T="/assets/tree-sitter-simple-folding.5bf37994.webm",q="/assets/tree-sitter-simple-folding.ac70c9fb.mp4",I={},S=t("p",null,"Today we\u2019ll look at two other systems \u2014 indentation hinting and code folding \u2014 and I\u2019ll explain how queries can be used to support each one.",-1),j=a(`

Indentation

Our programmer predecessors might scoff at how much our editors coddle us, but I\u2019ll say it anyway: I hate it when my editor doesn\u2019t know when to indent. In particular, C-style languages like JavaScript have predictable and simple rules about indentation, so when an editor guesses wrong, it\u2019s like a burst of static in the middle of a song.

If I type

if {
+

and press Return, all major code editors will indent the next line for me by one level. There\u2019s no reason for an editor to force me to press Tab myself \u2014 it\u2019s obvious from context.

And once I\u2019m done with the conditional and type } on its own line, my editor should recognize that this line isn\u2019t part of the indented block, and should decrease the indentation by one level automatically.

How does Pulsar do this now? And how can we swap in our own system for doing it with Tree-sitter?

How TextMate grammars do it

Pulsar, like Atom before it, uses an indentation hinting system based on the system from TextMate grammars. It works a bit like this:

For JavaScript, imagine that increaseIndentPattern can be described as \u201Can opening brace without a matching closing brace,\u201D and decreaseIndentPattern can be described as \u201Ca closing brace that is not preceded by an opening brace.\u201D Each pattern would probably need to match more than just those situations, but that\u2019s the most important pattern to recognize by far.

Let\u2019s look at that screencast again to see these rules in action:

The first rule comes into play when we press Return at the end of line 2. A newline is inserted, and the cursor is correctly indented by one additional level.

The second rule reacts after we type a } on line 4: now the line\u2019s content matches decreaseIndentPattern, and the line is dedented automatically.

How legacy Tree-sitter grammars do it

The same way. No, seriously.

It was obviously meant to be temporary, but legacy Tree-sitter grammars rely on the same increaseIndentPattern and decreaseIndentPattern that TextMate grammars use. We should come up with a Tree-sitter\u2013based system instead.

TextMate\u2019s deep embrace of regular expressions is a double-edged sword: it makes simple things easy, but it makes complex things nearly impossible. Instead of testing lines against a single regular expression \u2014 which can quickly get unwieldy as it expands to handle all possible indentation hinting scenarios \u2014 we can use Tree-sitter\u2019s query system to identify code features that would affect indentation.

',20),P={href:"https://github.com/mauricioszabo",target:"_blank",rel:"noopener noreferrer"},H={href:"https://github.com/nvim-treesitter/nvim-treesitter/blob/v0.9.1/doc/nvim-treesitter.txt#L210",target:"_blank",rel:"noopener noreferrer"},M=a(`

Working within the system

We could\u2019ve just adopted Neovim\u2019s system wholesale. But I wanted a system that kept the same decision points as the TextMate system for conceptual simplicity. So we\u2019ve built a system that will abide by the contract described above\u2026

  1. To figure out whether to indent a line, look at the content of the line above it.
  2. To figure out whether to dedent a line, look at the content of that line itself.

\u2026except using its own logic:

  1. When a user presses Return, instead of checking the previous line against increaseIndentPattern, we\u2019ll instead run a query capture against the previous line and look at the results to figure out whether to indent the new line.
  2. When a user types on the current line, instead of checking it against decreaseIndentPattern, we\u2019ll run a query capture against that line with each keystroke and look for results that imply we should dedent that line relative to the line above.

Remember that Tree-sitter produces concrete syntax trees; every node represents an actual buffer range. The things that are typically stripped from abstract syntax trees, like punctuation, are still present in a Tree-sitter tree, and can be queried. That\u2019s good news for us: looking at punctuation is a great way to predict how lines should be indented.

So let\u2019s start here. Imagine this content exists in a file called indents.scm:

"{" @indent
+"}" @dedent
+

This is maybe the simplest possible system for describing indentation in a C-style language. For some languages, like CSS, this gets us 99% of the way to a perfect indentation system. But how does it work?

`,9),A={href:"https://web.pulsar-edit.dev/packages/tree-sitter-tools",target:"_blank",rel:"noopener noreferrer"},L=t("code",null,"tree-sitter-tools",-1),C={href:"https://pulsar-edit.dev/blog/20230925-savetheclocktower-modern-tree-sitter-part-1.html#can-i-use-this-new-implementation-now",target:"_blank",rel:"noopener noreferrer"},B=t("li",null,"Open a new document, change the grammar to CSS, and type some sample CSS.",-1),R=t("li",null,[e("Run the "),t("strong",null,"Tree Sitter Tools: Open Inspector For Editor"),e(" command.")],-1),W=t("li",null,[e("Toggle the \u201CAnonymous nodes\u201D option to "),t("strong",null,"Show"),e(".")],-1),E=t("li",null,[e("Paste that code block into the appropriate text box and click on "),t("kbd",null,"Run Query"),e(".")],-1),N=a('

tree-sitter-tools indentation example

You can see the query matches illustrated in the text editor in real time, and you can match up the colors to the @indent and @dedent captures. You can even type new content (as in the examples below) and see the captures update in real time!

Let\u2019s say the user is writing a CSS file, and the cursor is represented by the | character:

body {|
+

If the user were to press Return, we\u2019d run a query capture on a specific range of the buffer: from the start of row 1 to wherever the cursor was. The opening brace on row 1 would produce a single capture called @indent. Based on that information, we\u2019d know that row 2 should be indented by one level.

But what if the file looked like this instead?

body { font-family: 'Helvetica', sans-serif; }|
+

If the user were to press Return, we\u2019d run the same query capture, and it would match twice: one @indent capture and one @dedent capture. Those captures would cancel each other out. We\u2019d know that the opening brace we saw had already been balanced by a later closing brace, and we\u2019d know that row 2 should not increase its indentation level.

Now let\u2019s look at one more example:

body {
+  font-family: 'Helvetica', sans-serif;
+  |
+

After typing one property-value pair and pressing Return, we\u2019re on row 3. Should this line be dedented? It depends on what we\u2019re about to type! If we\u2019re about to type }, then the answer is yes \u2014 but if we\u2019re typing anything else, like another property-value pair, then the answer is no. That\u2019s why Pulsar won\u2019t decide whether to dedent until we start typing. If we were to type }, our Tree-sitter grammar would run a query capture on the current line, spot the @dedent match, and respond by dedenting the current line one level from the line above.

For more complex C-style languages like JavaScript, here\u2019s a better starting point for indents.scm:

; Any of these characters should trigger indent\u2026
+[ "{" "(" "[" ] @indent
+
+; \u2026and any of these should trigger dedent.
+[ "}" ")" "]" ] @dedent
+

There are major simplicity benefits to targeting these anonymous nodes instead of more abstract nodes. Most folks\u2019 indentation styles tend to align with delimiter usage, so the more tightly we can bind to the delimiters themselves, the more accurate the hinting will be. And anonymous nodes are safer to rely upon as the user types and the syntax tree is in flux. Sometimes we have to \u201Cfilter out\u201D false positives \u2014 for instance, curly braces in JavaScript that signify template string interpolations instead of statement blocks \u2014 but that\u2019s a small price to pay.

I\u2019m hiding some of the complexity from you, but less than you\u2019d think. This is a much friendlier way to describe indentation hinting than making a grammar author maintain an ever-more-complex set of regular expressions. It allows the author to describe each kind of indentation hint as its own rule.

Getting creative

And it allows us to do some more complex things that weren\u2019t possible before.

TextMate\u2019s system will let us indent or dedent one level at a time. But consider a switch statement:

switch (foo) {
+	case "bar":
+		// one thing
+		break;
+	case "baz":
+		// another thing
+		break;
+	default:
+	// a third thing
+}
+

Between the second-to-last line and the last line, there\u2019s a two-level change in indentation. How can we express that?

Here\u2019s where our syntax tree comes in handy. Instead of describing our desired indentation level relative to the previous line, we can describe it relative to a line of our choosing:

; The closing brace of a switch statement's body should match the indentation
+; of the line where the switch statement starts.
+(switch_statement
+  body: (switch_body "}" @match)
+  (#set! indent.matchIndentOf parent.startPosition))
+
+; 'case' and 'default' need to be indented one level more than their containing
+; \`switch\`.
+(["case" "default"] @match
+  (#set! indent.matchIndentOf parent.parent.startPosition)
+  (#set! indent.offsetIndent 1))
+
+["case" "default"] @indent
+
`,22),O=t("code",null,"@match",-1),z=t("em",null,"node position descriptor",-1),Y={href:"https://pulsar-edit.dev/blog/20231013-savetheclocktower-modern-tree-sitter-part-3.html#adjusting-by-node-position-descriptor",target:"_blank",rel:"noopener noreferrer"},D=a(`

In the first rule, we\u2019re matching the closing } of a switch statement. We\u2019re using a #set! predicate to describe the starting position of its switch_body parent. The switch_body starts on row 1, so @match understands this to mean \u201Cadjust the current line to match the indentation of row 1.\u201D This will happen automatically when the user types the closing brace.

In the second rule, we\u2019re using similar logic. If we were typing the above switch statement, we\u2019d find ourselves over-indented as we started typing on line 5. We\u2019d want our editor to dedent that line once it saw that we were typing a new branch of the switch statement. So we can write another @match rule \u2014 still targeting the indentation level of the starting row of switch_body \u2014\xA0but with an extra rule to offset the indent by one level. In other words, we want to be indented one level more than the indent level of row 1.

The third rule is simpler: it\u2019s how we ensure that the editor indents by one level after case and default statements.

You might\u2019ve had your hackles raised by this example. After all, there\u2019s another school of thought on how to indent switch statements:

switch (foo) {
+case "bar":
+	// one thing
+	break;
+case "baz":
+	// another thing
+	break;
+default:
+	// a third thing
+}
+

This faction thinks that case and default statements should be indented to the same level as the original switch statement. How can we please both camps?

One way might be to write two different versions of our second rule, and decide which one to use based on configuration:


+; Some say 'case' and 'default' need to be indented one level more than their
+; containing \`switch\`.
+(["case" "default"] @match
+  (#is? test.config "language-javascript.doubleIndentSwitchStatements")
+  (#set! indent.matchIndentOf parent.parent.startPosition)
+  (#set! indent.offsetIndent 1))
+
+; Others say 'case' and 'default' should match their containing \`switch\`.
+(["case" "default"] @match
+  (#is-not? test.config "language-javascript.doubleIndentSwitchStatements")
+  (#set! indent.matchIndentOf parent.parent.startPosition))
+

Here we\u2019re using a test.config scope test. I told you about scope tests last time, but I haven\u2019t yet mentioned that they don\u2019t just apply to syntax highlighting queries; they apply to indentation queries, too!

The test.config scope test gives us a way to approve or reject a capture based on the user\u2019s chosen configuration. If they\u2019ve enabled the doubleIndentSwitchStatements config option, we can indent their code one way; if they\u2019ve disabled it, we can indent their code another way.

This particular example isn\u2019t yet implemented, but it could be. This would be another advantage that the new Tree-sitter system has over TextMate-style indentation hinting: more room for configurability.

Here\u2019s another edge case of indentation: a braceless if statement.

How did we pull this off? Haven\u2019t we been targeting nodes like { and }?

Yes, but we can also write rules to handle one-line conditionals like these:

(if_statement
+  condition: (parenthesized_expression ")" @indent
+  (#is? test.lastTextOnRow true)))
+

Here we\u2019ve written a query that captures the closing ) of a braceless if statement and uses that as the indentation hint. We\u2019re also using a scope test to ensure the capture is ignored when it isn\u2019t the last text on the row; that\u2019s how we can avoid a false positive in this scenario:

if (notificationsAreDisabled) return;
+

A braceless if or else applies only to the next statement. The real feat is knowing to dedent immediately after that statement:

(if_statement
+  condition: (_) @indent
+  consequence: [
+    (expression_statement)
+    (return_statement)
+    (continue_statement)
+    (break_statement)
+    (throw_statement)
+    (debugger_statement)
+  ] @dedent.next)
+

An if clause with braces will have a node named statement_block in its consequence slot. An if clause without braces will have its consequence slot filled with one of these six kinds of nodes instead.

The @dedent.next capture is only rarely needed, but this is a textbook case: it signals when we should dedent the next line without waiting to see the content of the line. Because we know that the next line should always be dedented in this scenario.

How well does this work? Amazingly well:

Tree-sitter isn\u2019t confused by the line comment! It won\u2019t dedent until after the user types an actual statement.

Does this matter a great deal? Is it worth creating detailed rules to cover a breadth of unusual indentation scenarios? Probably not. But this is just one of a handful of low-hanging fruit that the new system has made possible. Even the slightly verbose query above is much easier to write (and for other people to reason about) than an inscrutable regular expression.

Code folding

I\u2019m not someone who uses code folding very much, but I want it to be there when I need it. Collapsing entire code branches helps me see the big picture more easily.

How TextMate grammars do it

Much like indentation, code folding markers in TextMate grammars are determined with regular expressions. Any line that matches foldingStartMarker is treated as the start of a fold, and any line that matches foldingEndMarker is treated as the end of a fold. This offers similar tradeoffs to the indentation patterns described above: simple for simple cases, but nearly impossible for complex cases.

How legacy Tree-sitter grammars do it

',31),F={href:"https://github.com/pulsar-edit/pulsar/blob/v1.110.0/packages/language-javascript/grammars/tree-sitter-javascript.cson#L18-L53",target:"_blank",rel:"noopener noreferrer"},J=t("h3",{id:"using-queries-to-define-fold-ranges",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#using-queries-to-define-fold-ranges","aria-hidden":"true"},"#"),e(" Using queries to define fold ranges")],-1),V=t("code",null,"nvim-treesitter",-1),U={href:"https://github.com/mauricioszabo",target:"_blank",rel:"noopener noreferrer"},G=a(`
(block) @fold
+

That\u2019s the entirety of the contents of the folds.scm file inside the language-css package. This works because a starting position and an ending position are all you need to describe a fold, and that\u2019s what Tree-sitter gives us. Every node in a tree reports its buffer positions, so every node can be the target of a fold.

tree sitter simple fold example

Let\u2019s go a bit deeper and figure out what this does.

When it opens a file, Pulsar needs to figure out where possible fold ranges are so that it can show a small chevron in the gutter on each line where a fold can start. So it\u2019ll run a query capture against each line, testing it to see if any @fold captures result.

Any results will have their buffer ranges inspected. If the range starts and ends on the same line, the candidate fold is ignored. (This saves grammar authors from having to manually exclude things like one-line conditionals.) But if the range starts on one line and ends on another, the fold is valid, and Pulsar knows to place a chevron in the gutter where the fold would start.

The beginning of a code fold is the very last column on its starting line. In most cases, the range in question will have delimiters on each end \u2014 the backticks of a template string, the curly braces of an if or else condition, et cetera. That\u2019s why, by default, the end of a code fold is the starting position of its very last node child. This works as intended in the vast majority of cases, as in our CSS example above:

tree-sitter-tools node hierarchy illustration

But because this isn\u2019t always true \u2014 especially for languages like Python that don\u2019t use delimiters for blocks \u2014 we provide ways to tweak a fold\u2019s ending position.

For instance, let\u2019s look at a JavaScript block comment:

JavaScript block comment

Since comment nodes don\u2019t have children, we should set a custom ending position for the fold with fold.endAt so that it doesn\u2019t try to look up a child node that doesn\u2019t exist. Then we can use fold.offsetEnd to move the ending point of the fold two columns to the left so that the fold ends before the comment\u2019s ending delimiter:

((comment) @fold
+  (#set! fold.endAt endPosition)
+  (#set! fold.offsetEnd -2))
+

Now we can fold up the block comment the way we want:

Folding in JavaScript is still pretty simple, but not as simple as CSS. We\u2019ve got to account for some edge cases. For example, when an if statement is followed by an else, we should adjust the fold so that it ends on the line before the else, so that each fold can be toggled independently without interfering with one another:

; Target \`if\` consequence blocks that are followed by \`else\`s.
+((if_statement
+  consequence: (statement_block) @fold
+  alternative: (else_clause)
+  (#set! fold.adjustToEndOfPreviousRow true)
+))
+
+; Other \`if\` consequence blocks will get caught here.
+(statement_block) @fold
+

You can see how this works in the screencast below \u2014 the else block\u2019s closing delimiter folds up to be on the same line as the starting delimiter, but the if block\u2019s fold stops before the newline.

\u201CEnd the fold at the end of the previous line\u201D is a common enough case to have its own shorthand predicate. We\u2019ve put this special-case query above the simpler one because Pulsar will use the first capture that matches for a given line.

Why is this so complicated?

I\u2019ll say it again: this tour through the machinery of Pulsar is aimed at Tree-sitter aficionados and at those who might want to write their own language packages for Pulsar. If that doesn\u2019t describe you, don\u2019t let yourself get overwhelmed by this information dump \u2014 just make note of the new features that this system makes possible.

There are pieces of the indentation and folding systems that I didn\u2019t even try to explain in this post. But all this complexity has a purpose, and users reap the benefits in small increments \u2014 for instance, every time they don\u2019t have to go back and reformat code they paste into an editor.

These systems only work with the assistance of language grammars, so we owe it to the authors of those grammars to hide as much of that complexity as we can. If we can make these systems seem simple on the surface, they\u2019ll work better, and users will be happier.

Next time

Cue up your DVD of Inception! Next time we\u2019re delving into language injections \u2014 the feature that lets you write CSS inside of HTML inside of JavaScript inside of HTML inside of PHP.

',26);function Q(K,X){const d=o("RouterLink"),s=o("ExternalLinkIcon");return c(),h("div",null,[t("p",null,[e("Last time we looked at Tree-sitter\u2019s query system and showed how it can be used "),n(d,{to:"/blog/20231013-savetheclocktower-modern-tree-sitter-part-3.html"},{default:u(()=>[e("to make a syntax highlighting engine in Pulsar")]),_:1}),e(". But syntax highlighting is simply the most visible of the various tasks that a language package performs.")]),S,p(" more "),j,t("p",null,[t("a",P,[e("@mauricioszabo"),n(s)]),e(" had noticed "),t("a",H,[e("Neovim\u2019s prior art here"),n(s)]),e(", and had started to implement a similar system, so it was easy to pick up where he left off.")]),M,t("p",null,[e("If you\u2019re following along with "),t("a",A,[L,n(s)]),e(", you can visualize it:")]),t("ol",null,[t("li",null,[e("First, "),t("a",C,[e("make sure you\u2019ve enabled modern Tree-sitter"),n(s)]),e(".")]),B,R,W,E]),N,t("p",null,[e("Here we\u2019re using a new capture called "),O,e(". It can do exactly what we just described by using a "),z,e(" (an idea we introduced "),t("a",Y,[e("in the last installment"),n(s)]),e(") to refer to an earlier line.")]),D,t("p",null,[e("Legacy Tree-sitter grammars introduced a system for defining folds "),t("a",F,[e("within the grammar definition file itself"),n(s)]),e(" \u2014\xA0one that hooks into named nodes from the tree. It\u2019s a good start, but we can make something more expressive and flexible with queries.")]),J,t("p",null,[e("Again, credit goes to the "),V,e(" developers and to "),t("a",U,[e("@mauricioszabo"),n(s)]),e(" for envisioning how Tree-sitter queries can describe folds more simply. Here\u2019s how simple it can be in a language like CSS:")]),G])}const $=l(I,[["render",Q],["__file","20231031-savetheclocktower-modern-tree-sitter-part-4.html.vue"]]);export{$ as default}; diff --git a/assets/20231109-Daeraxa-NovemberUpdate.html.38101774.js b/assets/20231109-Daeraxa-NovemberUpdate.html.38101774.js new file mode 100644 index 0000000000..1e4d59b475 --- /dev/null +++ b/assets/20231109-Daeraxa-NovemberUpdate.html.38101774.js @@ -0,0 +1 @@ +import{_ as r}from"./tools.52491106.js";import{_ as s}from"./detective.e7e89ed0.js";import{_ as i}from"./tree-sitter.6a39e323.js";import{_ as h}from"./spotlight.aba19e76.js";import{_ as l,o as c,c as d,e as p,a as t,b as e,d as n,f as a,r as u}from"./app.0e1565ce.js";const m="/assets/computer.f3068a14.png",g="/assets/pulsar-runner.3044a99a.png",f={},_=t("p",null,"What month is most important for a Pulsar? A supernova-ember! ...What isn't a bad joke is this, the Pulsar community update!",-1),b=a('

Welcome to the November Community Update!

Another warm welcome to the Pulsar community update, where we cover new developments and events in the world of Pulsar. This month we have a couple of really significant changes to how Pulsar works internally by creating a couple of new APIs that can be used throughout the application, a new package to help you run code directly within Pulsar and our usual community spotlight to say thank you to those community members contributing to Pulsar's development!

New UI API

',4),w={href:"https://github.com/confused-Techie",target:"_blank",rel:"noopener noreferrer"},k=t("code",null,"UI",-1),y={href:"https://marked.js.org/",target:"_blank",rel:"noopener noreferrer"},v={href:"https://web.pulsar-edit.dev/",target:"_blank",rel:"noopener noreferrer"},P={href:"https://github.com/pulsar-edit/package-frontend",target:"_blank",rel:"noopener noreferrer"},x={href:"https://github.com/markdown-it/markdown-it",target:"_blank",rel:"noopener noreferrer"},z=a('

By removing all these independent implementations across various core packages, we should be able to reap the following benefits:

Of course this new API will also be available for use by community packages for developers to take advantage of if they wish, so that Markdown parsing doesn't need to be independently implemented any more.

This new feature is availble in our newest rolling releases and will soon be in our regular release too!

Exposing a fuzzy-native API

',6),A={href:"https://github.com/pulsar-edit/fuzzy-native",target:"_blank",rel:"noopener noreferrer"},I=t("code",null,"fuzzy-finder",-1),T=t("code",null,"command-palette",-1),E=t("code",null,"autocomplete-plus",-1),N=t("code",null,"fuzzy-native",-1),U={href:"https://github.com/pulsar-edit/pulsar/pull/774",target:"_blank",rel:"noopener noreferrer"},W=t("h2",{id:"a-new-code-runner-package",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#a-new-code-runner-package","aria-hidden":"true"},"#"),e(" A new code runner package")],-1),D=t("img",{src:m,height:"200"},null,-1),C={href:"https://github.com/confused-Techie",target:"_blank",rel:"noopener noreferrer"},M={href:"https://github.com/confused-Techie/pulsar-runner",target:"_blank",rel:"noopener noreferrer"},R=a('

Further installments in the modern Tree-sitter blog post series

[1]

',3),V={href:"https://pulsar-edit.dev/blog/20231004-Daeraxa-OctoberUpdate.html#modern-tree-sitter-blog-posts",target:"_blank",rel:"noopener noreferrer"},j={href:"https://github.com/savetheclocktower",target:"_blank",rel:"noopener noreferrer"},q={href:"https://pulsar-edit.dev/blog/20231013-savetheclocktower-modern-tree-sitter-part-3.html",target:"_blank",rel:"noopener noreferrer"},B={href:"https://pulsar-edit.dev/blog/20231031-savetheclocktower-modern-tree-sitter-part-4.html",target:"_blank",rel:"noopener noreferrer"},L=t("h2",{id:"community-spotlight",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#community-spotlight","aria-hidden":"true"},"#"),e(" Community spotlight")],-1),F=t("img",{src:h,height:"200"},null,-1),H={href:"https://github.com/danfuzz",target:"_blank",rel:"noopener noreferrer"},S={href:"https://github.com/pulsar-edit/pulsar/issues/775",target:"_blank",rel:"noopener noreferrer"},O={href:"https://github.com/pulsar-edit/pulsar/pull/776",target:"_blank",rel:"noopener noreferrer"},G={href:"https://github.com/kiskoza",target:"_blank",rel:"noopener noreferrer"},Y={href:"https://github.com/pulsar-edit/pulsar/pull/785",target:"_blank",rel:"noopener noreferrer"},$={href:"https://github.com/pulsar-edit/pulsar/issues/784",target:"_blank",rel:"noopener noreferrer"},J=t("code",null,"pulsar-updater",-1),K=t("hr",null,null,-1),Q=t("p",null,"And once again we wrap things up for this month. As always, a big thank you to all of our community members and a special thanks to those who donate to the project and make this possible. We hope to see you here next month for our December edition!",-1),X=t("hr",{class:"footnotes-sep"},null,-1),Z={class:"footnotes"},ee={class:"footnotes-list"},te={id:"footnote1",class:"footnote-item"},oe={href:"https://tree-sitter.github.io/tree-sitter/",target:"_blank",rel:"noopener noreferrer"},ne=t("a",{href:"#footnote-ref1",class:"footnote-backref"},"\u21A9\uFE0E",-1);function ae(re,se){const o=u("ExternalLinkIcon");return c(),d("div",null,[_,p(" more "),b,t("p",null,[e("Pulsar has a number of different methods of rendering UI components, which tend to differ between the various core packages that make up the main Pulsar editor. This is particularly evident when dealing with Markdown rendering, where different packages may be using different versions of the same dependencies, which all have to be taken into account when updating them. To this end, "),t("a",w,[e("@confused-techie"),n(o)]),e(" has been implementing a new "),k,e(" API that will simplify all of these disparate methods into a single new API.")]),t("p",null,[e("The focus will be on Markdown to start with, simply because this is where we are having to spend a lot of effort re-implementing features. For example, our 'settings-view' package needs to display package READMEs and relies on a library called "),t("a",y,[e("marked"),n(o)]),e(". However, this isn't the only place where READMEs will be displayed. Not only do we have our own "),t("a",v,[e("Pulasr Package Registry"),n(o)]),e(" website powered by "),t("a",P,[e("package-frontend"),n(o)]),e(" using the "),t("a",x,[e("markdown-it"),n(o)]),e(" parser, but we also have to consider how it is displayed on GitHub, which is currently the only method of publishing packages to the PPR.")]),z,t("p",null,[e("In a similar vein to the above, we also have a "),t("a",A,[e("fuzzy-native"),n(o)]),e(" package used to provide fuzzy string matching for Pulsar. This package is used as a dependency for a large number of Pulsar packages, including core packages such as "),I,e(", "),T,e(" and "),E,e(". The goals here are very similar to those for the new UI API mentioned above; instead of each package needing to specify (and bundle) "),N,e(" as a dependency, they can instead simply reference this new global API, saving the need to bundle it as a dependency and having to maintain each package that uses it.")]),t("p",null,[e("You can view the progress of this work over on "),t("a",U,[e("its pull request"),n(o)]),e(" by [@mauricio szabo].")]),W,D,t("p",null,[t("a",C,[e("@confused-techie"),n(o)]),e(" has been working on a new package for running scripts and code directly within Pulsar. The inspiration comes from some existing packages that still work and can be installed, but few of the most popular ones are still actively maintained. Having an integrated code runner package is a common request from new Pulsar users wondering how they can run code directly in the editor in much the same way as is possible within other editors such as VSCode. We are currently discussing the best way to bring this kind of functionality to users; should we publish this as a package, include it as a new core package, or something in-between? Whatever the outcome, you can look forward to more news on this package soon!")]),t("p",null,[e("If you want to check it out in its current, in-development state, then have a look at the "),t("a",M,[e("pulsar-runner"),n(o)]),e(" repository.")]),R,t("p",null,[e("Continuing on from "),t("a",V,[e("last month's entry"),n(o)]),e(" we have posted two new blog posts by "),t("a",j,[e("@savetheclocktower"),n(o)]),e(" on the topic of Pulsar's modern Tree-sitter implementation. So if you haven't seen those posts, then you can continue onto parts "),t("a",q,[e("three"),n(o)]),e(" and "),t("a",B,[e("four"),n(o)]),e(" on the topics of syntax highlighting and indentation & code folding respectively.")]),L,F,t("p",null,[e("A big thanks this month to "),t("a",H,[e("@danfuzz"),n(o)]),e(" for the "),t("a",S,[e("identification"),n(o)]),e(" and "),t("a",O,[e("fix"),n(o)]),e(" for a problem with shell script syntax highlighting.")]),t("p",null,[e("Another big thanks goes out to "),t("a",G,[e("@kiskoza"),n(o)]),e(" for finding a "),t("a",Y,[e("problem"),n(o)]),e(" and providing a "),t("a",$,[e("fix"),n(o)]),e(" for an issue with the "),J,e(` package where it wasn't correctly caching the "Dismiss this version" notification to prevent it from showing again.`)]),K,Q,X,t("section",Z,[t("ol",ee,[t("li",te,[t("p",null,[e("Image from "),t("a",oe,[e("https://tree-sitter.github.io/tree-sitter/"),n(o)]),e(" - Copyright (c) 2018-2021 Max Brunsfeld "),ne])])])])])}const pe=l(f,[["render",ae],["__file","20231109-Daeraxa-NovemberUpdate.html.vue"]]);export{pe as default}; diff --git a/assets/20231109-Daeraxa-NovemberUpdate.html.e36ef3bb.js b/assets/20231109-Daeraxa-NovemberUpdate.html.e36ef3bb.js new file mode 100644 index 0000000000..13f083cbf0 --- /dev/null +++ b/assets/20231109-Daeraxa-NovemberUpdate.html.e36ef3bb.js @@ -0,0 +1 @@ +const e=JSON.parse(`{"key":"v-879bed80","path":"/blog/20231109-Daeraxa-NovemberUpdate.html","title":"Community Update","lang":"en-US","frontmatter":{"title":"Community Update","author":"Daeraxa","date":"2023-11-09T00:00:00.000Z","category":["news","log"],"tag":["update"]},"excerpt":"

What month is most important for a Pulsar? A supernova-ember! ...What isn't a bad joke is this, the Pulsar community update!

\\n","headers":[{"level":2,"title":"New UI API","slug":"new-ui-api","link":"#new-ui-api","children":[]},{"level":2,"title":"Exposing a fuzzy-native API","slug":"exposing-a-fuzzy-native-api","link":"#exposing-a-fuzzy-native-api","children":[]},{"level":2,"title":"A new code runner package","slug":"a-new-code-runner-package","link":"#a-new-code-runner-package","children":[]},{"level":2,"title":"Further installments in the modern Tree-sitter blog post series","slug":"further-installments-in-the-modern-tree-sitter-blog-post-series","link":"#further-installments-in-the-modern-tree-sitter-blog-post-series","children":[]},{"level":2,"title":"Community spotlight","slug":"community-spotlight","link":"#community-spotlight","children":[]}],"git":{"updatedTime":1699577764000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":3}]},"readingTime":{"minutes":3.85,"words":1156},"filePathRelative":"blog/20231109-Daeraxa-NovemberUpdate.md","localizedDate":"November 9, 2023"}`);export{e as data}; diff --git a/assets/20231110-savetheclocktower-modern-tree-sitter-part-5.html.bb998452.js b/assets/20231110-savetheclocktower-modern-tree-sitter-part-5.html.bb998452.js new file mode 100644 index 0000000000..48013ee9ea --- /dev/null +++ b/assets/20231110-savetheclocktower-modern-tree-sitter-part-5.html.bb998452.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-168f28ae","path":"/blog/20231110-savetheclocktower-modern-tree-sitter-part-5.html","title":"Modern Tree-sitter, part 5: injections","lang":"en-US","frontmatter":{"title":"Modern Tree-sitter, part 5: injections","author":"savetheclocktower","date":"2023-11-10T00:00:00.000Z","category":["dev"],"tag":["modernization","tree-sitter"]},"excerpt":"

One annoying thing that software developers do is insist on writing in more than one language at once. Web developers are espeically obnoxious about this \u2014 routinely, for instance, putting CSS inside their HTML, or HTML inside their JavaScript, or CSS inside their HTML inside their JavaScript.

\\n

Code editors like Pulsar need to roll with this, so today we\u2019ll talk about how the modern Tree-sitter system handles what we call injections.

\\n","headers":[{"level":2,"title":"A mental model for injections","slug":"a-mental-model-for-injections","link":"#a-mental-model-for-injections","children":[{"level":3,"title":"Language layers","slug":"language-layers","link":"#language-layers","children":[]}]},{"level":2,"title":"How Tree-sitter envisions injections","slug":"how-tree-sitter-envisions-injections","link":"#how-tree-sitter-envisions-injections","children":[]},{"level":2,"title":"How Pulsar implements injections","slug":"how-pulsar-implements-injections","link":"#how-pulsar-implements-injections","children":[{"level":3,"title":"How does it know which language to use?","slug":"how-does-it-know-which-language-to-use","link":"#how-does-it-know-which-language-to-use","children":[]}]},{"level":2,"title":"Architecture","slug":"architecture","link":"#architecture","children":[{"level":3,"title":"Challenges","slug":"challenges","link":"#challenges","children":[]}]},{"level":2,"title":"Strange injection tricks","slug":"strange-injection-tricks","link":"#strange-injection-tricks","children":[{"level":3,"title":"\u201CRedaction\u201D in injections","slug":"redaction-in-injections","link":"#redaction-in-injections","children":[]},{"level":3,"title":"Macros in Rust/C/C++","slug":"macros-in-rust-c-c","link":"#macros-in-rust-c-c","children":[]},{"level":3,"title":"Injecting highlighting for URLs and TODOs","slug":"injecting-highlighting-for-urls-and-todos","link":"#injecting-highlighting-for-urls-and-todos","children":[]},{"level":3,"title":"Markdown and front matter","slug":"markdown-and-front-matter","link":"#markdown-and-front-matter","children":[]}]},{"level":2,"title":"Next time","slug":"next-time","link":"#next-time","children":[]}],"git":{"updatedTime":1699689463000,"contributors":[{"name":"Andrew Dupont","email":"github@andrewdupont.net","commits":6}]},"readingTime":{"minutes":16.04,"words":4812},"filePathRelative":"blog/20231110-savetheclocktower-modern-tree-sitter-part-5.md","localizedDate":"November 10, 2023"}');export{e as data}; diff --git a/assets/20231110-savetheclocktower-modern-tree-sitter-part-5.html.c7b10bdf.js b/assets/20231110-savetheclocktower-modern-tree-sitter-part-5.html.c7b10bdf.js new file mode 100644 index 0000000000..e0c8b198b8 --- /dev/null +++ b/assets/20231110-savetheclocktower-modern-tree-sitter-part-5.html.c7b10bdf.js @@ -0,0 +1,132 @@ +import{_ as o,o as i,c as p,e as c,a,b as n,d as s,f as e,r as l}from"./app.0e1565ce.js";const r="/assets/tree-sitter-tools-style-element.6c7c3556.png",u="/assets/tree-sitter-injection-illustration.c0de6ada.png",d="/assets/tree-sitter-tools-tree-list.7475c4eb.png",h="/assets/tree-sitter-select-larger-syntax-node.522d277a.webm",k="/assets/tree-sitter-select-larger-syntax-node.07d04699.mp4",g="/assets/tree-sitter-tools-template-string.34be245b.png",m="/assets/tree-sitter-html-parser-injection.b2a40e1c.png",v="/assets/tree-sitter-todo-url-injection.231723ac.png",f={},b=a("p",null,[n("One annoying thing that software developers do is insist on writing in "),a("em",null,"more than one language at once"),n(". Web developers are espeically obnoxious about this \u2014 routinely, for instance, putting CSS inside their HTML, or HTML inside their JavaScript, or CSS inside their HTML inside their JavaScript.")],-1),y=a("p",null,[n("Code editors like Pulsar need to roll with this, so today we\u2019ll talk about how the modern Tree-sitter system handles what we call "),a("em",null,"injections"),n(".")],-1),w=e(`

The TextMate grammar system understands injections. In any context, a TextMate grammar can include a subset of its own rules\u2026 or an entirely separate grammar.

But Tree-sitter needs something a bit more elaborate. If I\u2019ve got CSS inside a style tag in my HTML file, now I\u2019ve got two different parsers, each responsible for a different range of code. If I make some changes inside that style block, both parsers need to react to it.

Injections cover a wide range of use cases \u2014\xA0from the examples above to fenced code blocks in Markdown files to special-purpose injections that recognize things like URLs. Injections allow us to do some powerful and useful things that would be hard to do otherwise \u2014 including some things that TextMate injections can\u2019t do at all.

A mental model for injections

Let\u2019s pretend we have a simple HTML file that looks like this:

<!DOCTYPE html>
+<html lang="en" dir="ltr">
+	<head>
+		<meta charset="utf-8" />
+		<title>Sample</title>
+
+		<style>
+			body {
+				padding: 0;
+			}
+		</style>
+
+		<script type="text/javascript">
+			window.dataLayer = window.dataLayer || [];
+			function gtag() {
+				dataLayer.push(arguments);
+			}
+			gtag("js", new Date());
+			gtag("config", "G-ABCDEFGHIJ");
+		</script>
+	</head>
+	<body></body>
+</html>
+

We haven\u2019t gotten very close to the machinery so far in this series, but I\u2019ve been content to have you model this as a single document with a single Tree-sitter HTML parser responsible for all syntax highlighting. That works fine until we get to the contents of the style and script elements.

To the Tree-sitter HTML parser, a style element looks like this:

visualization of a style element in tree-sitter

You can see that it performs the usual parsing on the start and end tags, but punts on parsing the CSS itself \u2014 instead marking it as raw_text. This is what it should do! It\u2019s not a CSS parser, after all. It treats the inline script element similarly, marking its contents as raw_text because it doesn\u2019t understand JavaScript.

To apply syntax highlighting to these areas, we need to bring in parsers that understand these languages.

So our mental model needs to evolve. Instead of one buffer with one parser, we have one buffer with three parsers. We need a name for \u201Ca region of the buffer that uses a specific grammar to be understood,\u201D so let\u2019s call it a language layer, because that\u2019s what Pulsar calls it under the hood.

Language layers

Imagine a simpler HTML file that doesn\u2019t have any inline style or script tags:

<!DOCTYPE html>
+<html lang="en" dir="ltr">
+	<head>
+		<meta charset="utf-8" />
+		<title>Sample</title>
+	</head>
+	<body></body>
+</html>
+

Here we\u2019re looking at a buffer with a single language layer at the root. When I type a new keystroke in this buffer, only one Tree-sitter parser has to do any re-parsing work, and only one layer needs to be consulted when re-applying syntax highlighting.

Once I add a style block\u2026

<!DOCTYPE html>
+<html lang="en" dir="ltr">
+	<head>
+		<meta charset="utf-8" />
+		<title>Sample</title>
+
+		<style>
+			body {
+				padding: 0;
+			}
+		</style>
+	</head>
+	<body></body>
+</html>
+

\u2026I trigger the creation of a second language layer. This new layer is a child of the root HTML layer \u2014\xA0because the HTML layer is its reason for being, and the CSS layer might go away in the future if the HTML layer changes. The new language layer uses a Tree-sitter CSS parser.

When I add back the script block\u2026

<!DOCTYPE html>
+<html lang="en" dir="ltr">
+	<head>
+		<meta charset="utf-8" />
+		<title>Sample</title>
+
+		<style>
+			body {
+				padding: 0;
+			}
+		</style>
+
+		<script type="text/javascript">
+			window.dataLayer = window.dataLayer || [];
+			function gtag() {
+				dataLayer.push(arguments);
+			}
+			gtag("js", new Date());
+			gtag("config", "G-ABCDEFGHIJ");
+		</script>
+	</head>
+	<body></body>
+</html>
+

\u2026I trigger the creation of another language layer: a JavaScript layer that is also a child of the root HTML layer. Now there are three layers that might need to be consulted for syntax highlighting and other tasks.

tree-sitter injection illustration

And it doesn\u2019t stop here! Certain constructs inside of the JavaScript, like regular expressions or tagged template literals, might carry their own injections, in which case new language layers would be created as children of the JavaScript layer. The result is a tree of language layers which cooperate to apply syntax highlighting to our buffer.

This might feel impossibly complex, but it isn\u2019t. It\u2019s just a different approach to what was already being done with TextMate grammars. In a minute I\u2019ll explain how Pulsar manages this complexity.

How Tree-sitter envisions injections

',26),j={href:"https://tree-sitter.github.io/tree-sitter/syntax-highlighting#language-injection",target:"_blank",rel:"noopener noreferrer"},T=a("code",null,"injections.scm",-1),_=a("code",null,"injections.scm",-1),x=e(`
((script_element
+  (raw_text) @injection.content)
+ (#set! injection.language "javascript"))
+
+((style_element
+  (raw_text) @injection.content)
+ (#set! injection.language "css"))
+
`,1),q={href:"https://en.wikipedia.org/wiki/Here_document#Perl-influenced",target:"_blank",rel:"noopener noreferrer"},I=e(`
list_items.each do |item|
+  puts <<~HTML
+    <li>#{item.name}</li>
+  HTML
+end
+

This is a thorough system, and we\u2019ve already shown how query files can solve problems like syntax highlighting and injection hinting. So it can solve our language injection problem, right?

How Pulsar implements injections

The stuff I just described makes a lot of sense, and it\u2019s possible we\u2019ll do it someday. But here\u2019s how Pulsar does it now:

atom.grammars.addInjectionPoint("text.html.basic", {
+	type: "script_element",
+	language() {
+		return "javascript";
+	},
+	content(node) {
+		return node.child(1);
+	},
+});
+
+atom.grammars.addInjectionPoint("text.html.basic", {
+	type: "style_element",
+	language() {
+		return "css";
+	},
+	content(node) {
+		return node.child(1);
+	},
+});
+

This code block is the equivalent of the above query file for HTML injections. It\u2019s got a similar amount of flexibility for selecting a target (e.g., \u201Cthe second child of a style_element node\u201D) and the ability to determine the language name either dynamically or statically. But we\u2019ve done everything else via queries; why do we do injections this way instead?

  1. The addInjectionPoint API was added by the legacy Tree-sitter implementation. For reasons of continuity, it makes a lot of sense to keep using this API rather than switch to something that\u2019s functionally the same.

  2. In fact, there\u2019s one thing that the addInjectionPoint API does that a hypothetical injections.scm file can\u2019t: it can be used to add injections to Language X even by someone who doesn\u2019t control the language-x bundle. This makes it far more useful to Pulsar! It means that someone can write their own parser that injects itself into another language, whether in the form of a community package or a few lines in a user\u2019s init.js.

To me, it doesn\u2019t make sense to deprecate the addInjectionPoint approach when it can do things that a query-based approach can\u2019t. Still, lots of Tree-sitter parsers include that query file, so I imagine that Pulsar will eventually support it in addition to the addInjectionPoint API.

How does it know which language to use?

You might\u2019ve noticed that both the Tree-sitter query file example and our addInjectionPoint example refer to the to-be-injected language rather casually \u2014 javascript and css. Internally, grammars tend to refer to one another via their root scope \u2014 as in the text.html.basic case above. So why not just use the root scope?

Two reasons:

`,11),S=e("
  • In our example, the HTML grammar shouldn\u2019t necessarily hard-code references to the injectable grammars it wants. It makes more abstract sense to describe the language it wants as javascript instead of source.js \u2014 because more than one grammar could theoretically identify as a JavaScript grammar, and because a grammar might want to respond to more than one name. (js and javascript, c++ and cpp, and so forth.)
  • ",1),L={href:"https://en.wikipedia.org/wiki/Heredoc",target:"_blank",rel:"noopener noreferrer"},M=a("code",null,"javascript",-1),P=a("code",null,"source.js",-1),H={href:"https://github.com/pulsar-edit/pulsar/blob/v1.110.0/packages/language-javascript/grammars/tree-sitter-2-javascript.cson#L6",target:"_blank",rel:"noopener noreferrer"},C=a("h2",{id:"architecture",tabindex:"-1"},[a("a",{class:"header-anchor",href:"#architecture","aria-hidden":"true"},"#"),n(" Architecture")],-1),O=a("p",null,[n("No matter which approach we use for "),a("em",null,"describing"),n(" injections, the job of "),a("em",null,"processing"),n(" injections is roughly the same. All of Pulsar\u2019s Tree-sitter support is written with the understanding that there could be an arbitrary number of grammars that need to be consulted when an edit happens. So the job of parsing a document is divided up into some number of "),a("code",null,"LanguageLayer"),n(" classes arranged hierarchically.")],-1),A={href:"https://web.pulsar-edit.dev/packages/tree-sitter-tools",target:"_blank",rel:"noopener noreferrer"},R=a("strong",null,"Tree Sitter Tools: Open Inspector For Editor",-1),D=e('

    a list of language layers in a buffer

    The first item in the list will always be the \u201Croot\u201D tree. Other items, if present, represent injections. And because injected languages can have their own injections, this list can grow to arbitrary length.

    When the user edits the buffer, even with a single keystroke, we re-parse the document from the root down as follows:

    1. The root layer re-parses.
    2. When that\u2019s done, Pulsar looks for possible injections by querying for nodes that have been specified in calls to addInjectionPoint.
    3. If those nodes match the criteria of addInjectionPoint (does the injection describe a language whose name we can match to a grammar? does the node specified by the content callback exist?), then we try to match them up to the layers that already exist. Layers that can\u2019t be matched to current injection nodes are disposed of, and nodes that can\u2019t be matched to existing layers get new layers created for them.
    4. The process starts over for each layer at the next level of depth in the tree until all injections are current and all parsers have re-parsed.

    Any keystroke can create a brand new injection or invalidate one that used to exist. If I put the cursor inside of <style> and insert an x, changing it to <styxle>, then the CSS injection would no longer exist, and its corresponding LanguageLayer instance would need to be destroyed. If I then undo my change, the parse tree restores the style_element node, and a new language layer is created.

    Does this feel overwhelming? That\u2019s fair. After all, I\u2019m writing this blog post in a buffer with 32 different language layers across the various code examples, and you\u2019d think that would add up to one hell of a performance penalty on each keystroke. But it doesn\u2019t.

    Here are a few reasons why:

    1. We don\u2019t revisit every single language layer on every single keystroke because we can determine when a given buffer change cannot possibly affect a given injection. For instance, if I\u2019m editing a section of my HTML file outside of the style block, then Pulsar knows it doesn\u2019t have to re-parse the CSS inside of that style block yet. It knows that the layer\u2019s parse tree, though technically stale, is not invalid, and will defer re-parsing until an edit happens within its extent of the buffer. (This is true even if those edits cause the style block to shift its position in the document!) As a result, lots of buffer changes can short-circuit the exhaustive process I just described.

    2. Syntax highlighting in particular is designed for performance, even in injection scenarios. After a buffer\u2019s initial highlighting pass, a given section of code will retain its highlighting indefinitely, even if its position in the buffer shifts as the result of other edits.

      Syntax re-highlighting only happens when a buffer range is specifically invalidated. When an edit happens, Tree-sitter tells us how that edit affects the syntax tree, which in turn tells us which parts of the buffer need to be re-highlighted \u2014 and, just as importantly, which parts don\u2019t need to be re-highlighted.

    3. Tree-sitter is faster than you think it is. The smaller the edit, the more the parser can reuse its old work, and the faster the re-parse happens.

    4. Hardly anything in this process happens synchronously, so buffer operations will feel fast even in the rare case where Tree-sitter needs time to catch up.

    Challenges

    ',9),J={href:"https://pulsar-edit.dev/blog/20231013-savetheclocktower-modern-tree-sitter-part-3.html",target:"_blank",rel:"noopener noreferrer"},N={href:"https://pulsar-edit.dev/blog/20231031-savetheclocktower-modern-tree-sitter-part-4.html",target:"_blank",rel:"noopener noreferrer"},B=e('

    Annoyingly, the answer is different for each system. For instance:

    So sometimes we need to aggregate across layers, but other times we need to pick a winner.

    Picking a winner is the obvious approach for indentation when you think it through. If I hit Return when the cursor is in a script block, then I\u2019m writing JavaScript, and the JavaScript layer should be the one making indentation decisions. More generally, this means that if more than one layer is active at a given buffer position, we should pick the deepest layer and ask it to decide. (Sometimes this means the deepest layer that fulfills a certain criterion \u2014\xA0in this case, the deepest layer that actually defines an indentation query.)

    But aggregating is the obvious approach for other scenarios. Tree-sitter grammars get to support Pulsar\u2019s Editor: Select Smaller Syntax Node and Editor: Select Larger Syntax Node commands (you don\u2019t know you need them in your life until you give them a try!) and those commands should work properly across injection boundaries. So when either command is invoked with the cursor at a given position, we should figure out which nodes contain that point regardless of which parse tree owns them. Then we can arrange those nodes from smallest to largest.

    You can see the results here. As I expand the selection by invoking Select Larger Syntax Node over and over, the selection starts with nodes in the CSS injection, jumps to nodes in the parent JavaScript injection, then jumps again to nodes in the root HTML injection.

    Strange injection tricks

    Mixing languages in a single buffer is messy, so injections need some unusual features in order to deal with that messiness. These features can be used in surprising and powerful ways.

    \u201CRedaction\u201D in injections

    One thing that makes Tree-sitter injections more powerful than their TextMate equivalents is their ability to ignore content that isn\u2019t relevant to their jobs. The injection engine \u201Credacts\u201D all content except what it wants a given layer to see.

    Redacting children

    Suppose you had an html tagged template literal:

    let markup = html` <p>Copyright 2020\u20132023 John Doe</p> `;\n

    Since the tag hints at the language name, Pulsar will give you HTML syntax highlighting for free inside the literal. But that literal is still JavaScript, so what happens if we do this?

    let now = new Date();\nlet markup = html` <p>Copyright 2020\u2013${now.getFullYear()} John Doe</p> `;\n

    That ${now.getFullYear()} part isn\u2019t actually HTML. This example won\u2019t confuse an HTML parser, so it\u2019s not a big deal; but there does exist valid content inside of a template interpolation that definitely would flummox the injection:

    let evil = html` <p>this might not ${"</p>"} get parsed correctly</p> `;\n

    Ideally, the HTML injection wouldn\u2019t see that interpolation at all. So what if we could hide it?

    We can. In fact, we do! Here\u2019s what that template string looks like in tree-sitter-tools:

    tree-sitter-tools visualization of a JavaScript template string

    Our injection is defined such that we specify the template_string node as the content. That means Pulsar will use the buffer range of that node, but will subtract the ranges of any of the node\u2019s children!

    We can visualize this with the \u201CShow injection ranges\u201D option in tree-sitter-tools:

    disjoint content ranges in a Tree-sitter injection

    You can see that the HTML injection layer has two disjoint content ranges on either side of the interpolation. The Tree-sitter HTML parser won\u2019t even know the interpolation is there.

    This behavior makes sense as a default, but it can be opted out of with includeChildren: true in addInjectionPoint if it gets in your way.

    Redacting via content callback

    A grammar author has another tool to control what gets redacted: the content callback. It\u2019s not limited to returning a single node! It can return an array of nodes, each with its own range; there\u2019s no obligation for those ranges to be adjacent.

    Our first HTML injection example earlier applied its own subtle redaction. We specified a type of script_element, but a content callback that returns that element\u2019s second child (the raw_text node). So the type property tells Pulsar which node to query for (create an injection for each script element) but content selects the node(s) that will be meaningful to the parser (omit the <script> and </script> because those aren\u2019t JavaScript).

    This flexibility means that it\u2019s possible for all your injections of a certain type to share one language layer. Instead of creating one layer for each of N different buffer ranges, you could create one layer with N disjoint content ranges. The trade-off is that an injection that covers more of the buffer will need to be re-parsed more often in response to buffer changes, but that trade-off might make sense in certain scenarios.

    Macros in Rust/C/C++

    C, C++, and Rust allow you to define macros via a preprocessor. Macros are weird for a parser: they can\u2019t be parsed as though they\u2019re valid source code, because they might be fragments of code that aren\u2019t syntactically valid until after preprocessing.

    Hence they\u2019re a situation where a language might want to inject itself into itself. Consider this code example:

    #define circleArea(r) (3.1415*(r)*(r))
    +

    The #define keyword and the circleArea(r) preprocessor function signature have to be well-formed, but everything that follows is an anything-goes nightmare for a syntax highlighter. The preprocessor won\u2019t try to parse it or make it make sense; it\u2019ll just make the appropriate substitution throughout the source file and enforce validity later.

    For the same reason, the tree-sitter-c parser doesn\u2019t attempt to do any parsing of the preprocessor argument \u2014 the (3.1415*(r)*(r)) in our above example. But that argument will often be valid C, so there\u2019s no reason why we shouldn\u2019t take a stab at it:

    atom.grammars.addInjectionPoint(\`source.c\`, {
    +	type: "preproc_function_def",
    +	language(node) {
    +		return "c";
    +	},
    +	content(node) {
    +		return node.lastNamedChild; // the \`preproc_arg\` node
    +	},
    +});
    +

    This is a low-stakes gambit for us. If the content of the macro is syntactically strange, the parser might get a bit flummoxed, and the resulting highlighting might look a bit weird. But that\u2019s OK! It won\u2019t affect the highlighting of anything outside of the macro content.

    Injecting highlighting for URLs and TODOs

    Two built-in packages called language-todo and language-hyperlink define specialized TextMate grammars. Their purpose is to provide rules that match TODO: remarks (in comments) and URLs (in comments and strings), and to inject those rules into certain contexts regardless of grammar. This is a lovely feature of TextMate that the Atom developers got for free when implementing TextMate-style grammars back in the day.

    The effect is that Pulsar can help you locate TODOs in comments by coloring them differently from the rest of the comment. It can also draw underlines under URLs and even follow a URL when you place your cursor inside of it and invoke the Link: Open command.

    illustration of highlighting of TODOs and URLs in a line comment

    This works because a TextMate grammar can \u201Cpush\u201D its injections into any scope inside any other grammar, whether that other grammar asks for it or not. For instance, the language-hyperlink grammar injects itself into strings, so any language that defines a string.* scope will have those rules injected into it.

    The legacy Tree-sitter system never had an equivalent feature. I missed it terribly, so I decided to create equivalent Tree-sitter parsers and grammars for these rules. These parsers, when given arbitrary text, can create nodes for things that look like URLs or TODO comments. Once those parsers existed, I could inject them into whichever grammars I wanted:

    for (let type of ["template_string", "string_fragment", "comment"]) {
    +	atom.grammars.addInjectionPoint("source.js", {
    +		type,
    +		language: () => {
    +			return "hyperlink";
    +		},
    +		content: (node) => node,
    +		languageScope: null,
    +	});
    +}
    +

    There\u2019s one new thing here: the languageScope option. Typically, you\u2019ll want a grammar\u2019s base scope name to be present inside of an injection; for instance, you\u2019d want a source.js scope name to exist inside of an HTML script block. But that behavior doesn\u2019t make sense for our use case. We want to add a scope name around a URL when it\u2019s present, but otherwise we want to operate stealthily. Passing null to the languageScope option lets us bypass the default behavior.

    There\u2019s one other thing to address, though. Most comments won\u2019t have URLs in them. Most strings won\u2019t have URLs in them. If I use this code as-is, I\u2019ll be creating one new injection for every string, every line comment, and every block comment in my JavaScript file, whether a URL is present or not. (This unnecessary work, believe it or not, doesn\u2019t create any sluggishness during routine editing, but we should still try to avoid it.)

    What should I do? One option would be to do what I described above: create one large injection for the entire document and have it be in charge of all comments and strings in the document. That was my first experiment, but I decided against it because the trade-off wasn\u2019t worth it: incremental re-parses were slower because every buffer change meant that my URL parser had to re-scan the whole buffer.

    I\u2019m willing to chalk part of that up to my lack of experience writing Tree-sitter parsers! I\u2019d bet there are things I can do to make those parses less costly. But in the meantime, I applied a Stupid Human Trick\u2122 to get the best of both worlds:

    const HYPERLINK_PATTERN = /\\bhttps?:/;
    +
    +for (let type of ["template_string", "string_fragment", "comment"]) {
    +	atom.grammars.addInjectionPoint("source.js", {
    +		type,
    +		language: (node) => {
    +			return HYPERLINK_PATTERN.test(node.text) ? "hyperlink" : undefined;
    +		},
    +		content: (node) => node,
    +		languageScope: null,
    +	});
    +}
    +

    I can assure you that this feels incredibly silly to do, but it works: we\u2019re pre-screening the content of the node and ignoring those that definitely don\u2019t contain a URL. Returning undefined from the language callback prevents a layer from being needlessly created. We employ a similar strategy for the TODO highlighting.

    There\u2019s another thing that feels awkward about this: it\u2019s not as automatic as the previous TextMate solution. Instead of being able to \u201Cpush\u201D these injections into other grammars, we\u2019re asking those grammars to \u201Cpull\u201D our injections into themselves.

    In an ideal world, I\u2019d be able to create a generalized injection that applied to all files as easily as in the TextMate grammars. But to create a Tree-sitter injection I\u2019ve got to describe the name of the node I want to inject into. And there aren\u2019t many safe assumptions you can make about Tree-sitter node naming conventions.

    The saving grace here is what I mentioned above: the injection API lets you inject things into someone else\u2019s language grammar. So if your favorite community language package doesn\u2019t highlight TODOs and URLs, you can fix that with about six lines of JavaScript in your init.js.

    Markdown and front matter

    `,55),W={href:"https://jekyllrb.com/",target:"_blank",rel:"noopener noreferrer"},Y={href:"https://jekyllrb.com/docs/step-by-step/03-front-matter/",target:"_blank",rel:"noopener noreferrer"},E={href:"https://github.com/MDeiml/tree-sitter-markdown",target:"_blank",rel:"noopener noreferrer"},U={href:"https://github.com/ikatyang/tree-sitter-markdown",target:"_blank",rel:"noopener noreferrer"},F=a("em",null,"doesn\u2019t",-1),z=a("em",null,"bulletproof",-1),V=a("p",null,"So how do we get around the older parser\u2019s lack of support for front matter? By writing our own Tree-sitter parser and using injections:",-1),G={href:"https://github.com/savetheclocktower/tree-sitter-frontmatter",target:"_blank",rel:"noopener noreferrer"},K=a("li",null,"Inject the YAML grammar into the front matter node.",-1),$=a("li",null,"Inject the Markdown grammar into the Markdown text node.",-1),X=a("p",null,[n("In an ideal world, the parser in step 1 would be just an ordinary Tree-sitter parser for Markdown, and we\u2019d need only the single injection for the YAML block. But this\u2019ll tide us over just fine. Documents that don\u2019t have front matter still get parsed by "),a("code",null,"tree-sitter-frontmatter"),n(" and will simply omit the "),a("code",null,"front_matter"),n(" node.")],-1),Q=a("h2",{id:"next-time",tabindex:"-1"},[a("a",{class:"header-anchor",href:"#next-time","aria-hidden":"true"},"#"),n(" Next time")],-1),Z={href:"https://tree-sitter.github.io/tree-sitter/code-navigation-systems",target:"_blank",rel:"noopener noreferrer"};function nn(an,tn){const t=l("ExternalLinkIcon");return i(),p("div",null,[b,y,c(" more "),w,a("p",null,[n("The Tree-sitter CLI tool performs its own code highlighting, so it needs "),a("a",j,[n("its own solution for injections"),s(t)]),n(". It envisions a query file called "),T,n(" that maps certain tree nodes to certain languages. For instance, here\u2019s the HTML parser\u2019s "),_,n(":")]),x,a("p",null,[n("These are simple examples, but the website documentation covers more advanced scenarios. For instance, the name of the language might not be hard-coded in the query file; it might be something that you\u2019ll need to determine from the tree itself, like by inspecting a "),a("a",q,[n("heredoc string"),s(t)]),n("\u2019s tag:")]),I,a("ol",null,[S,a("li",null,[n("There are multiple use cases for associating a shorthand token in a buffer file to a language name. I mentioned above how "),a("a",L,[n("heredoc strings"),s(t)]),n(" often hint at the language of the string content via the tag. (And as I write this document, I glance at a dozen other examples: fenced code blocks in Markdown.) So it\u2019s useful for us to be able to determine the to-be-injected language dynamically by inspecting the content of the buffer. We need to meet those use cases where they are.")])]),a("p",null,[n("So when an injection wants "),M,n(", we need to be able to match it to our "),P,n(" grammar. That happens via "),a("a",H,[n("a property in the grammar definition file"),s(t)]),n("; the grammar itself describes its \u201Cshort\u201D name.")]),C,O,a("p",null,[n("To visualize what we described above, you can once again use "),a("a",A,[n("tree-sitter-tools"),s(t)]),n(". Open your favorite HTML document, then run the "),R,n(" command. You\u2019ll be able to see all of a document\u2019s trees in a drop-down list:")]),D,a("p",null,[n("The systems we described in the last two installments \u2014 "),a("a",J,[n("syntax highlighting"),s(t)]),n(", "),a("a",N,[n("code folding, and indentation hinting"),s(t)]),n(" \u2014\xA0are much easier to explain when we don\u2019t have to think about injections. How do we make them work in a multi-language buffer?")]),B,a("p",null,[n("Markdown is how I write most of my prose, including this blog post. And for years it\u2019s been quite popular inside static site generators like "),a("a",W,[n("Jekyll"),s(t)]),n(" and its successors, but with a wrinkle: those tools support the addition of YAML metadata via "),a("a",Y,[n("a \u201Cfront matter\u201D block"),s(t)]),n(" at the start of a Markdown file.")]),a("p",null,[n("There are two major Markdown parsers for Tree-sitter, both of which are written by third parties rather than by the Tree-sitter organization. "),a("a",E,[n("One of them"),s(t)]),n(" is being actively developed, and boasts built-in support for front matter, but has a number of bugs that are show-stoppers for Pulsar at the moment. "),a("a",U,[n("The other one"),s(t)]),n(" is older, "),F,n(" support front matter, and doesn\u2019t seem to be actively maintained\u2026 but is otherwise "),z,n(". I\u2019d love to use the newer one for Pulsar, but I can\u2019t justify it until it\u2019s more stable.")]),V,a("ol",null,[a("li",null,[a("a",G,[n("Write a front matter parser"),s(t)]),n(" whose only purpose is to divide a Markdown document into two nodes: (a) front matter and (b) Markdown text.")]),K,$]),X,Q,a("p",null,[n("I could keep talking about injections, but I can\u2019t afford to test your patience while we still have other topics to visit. Next time we\u2019ll look at what Tree-sitter calls "),a("a",Z,[n("code navigation systems"),s(t)]),n(": how to use Tree-sitter to identify functions, classes, and other important parts of your code.")])])}const en=o(f,[["render",nn],["__file","20231110-savetheclocktower-modern-tree-sitter-part-5.html.vue"]]);export{en as default}; diff --git a/assets/20231116-Daeraxa-v1.111.0.html.a8f9e5b3.js b/assets/20231116-Daeraxa-v1.111.0.html.a8f9e5b3.js new file mode 100644 index 0000000000..5ea538d242 --- /dev/null +++ b/assets/20231116-Daeraxa-v1.111.0.html.a8f9e5b3.js @@ -0,0 +1 @@ +import{_ as n,o as s,c as a,a as t,b as e,d as r,e as l,f as i,r as u}from"./app.0e1565ce.js";const d={},h={href:"https://github.com/pulsar-edit/pulsar/releases/tag/v1.111.0",target:"_blank",rel:"noopener noreferrer"},c=t("h2",{id:"what-do-we-have-for-you-in-pulsar-1-111-0",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#what-do-we-have-for-you-in-pulsar-1-111-0","aria-hidden":"true"},"#"),e(" What do we have for you in Pulsar 1.111.0?")],-1),p=t("p",null,"Welcome to a new Pulsar regular release. This time we have a big new addition to Pulsar's API along with our usual set of bug fixes (with some fantastic community contributions).",-1),_=t("code",null,"UI",-1),f={href:"https://pulsar-edit.dev/blog/20231109-Daeraxa-NovemberUpdate.html#new-ui-api",target:"_blank",rel:"noopener noreferrer"},g={href:"https://marked.js.org/",target:"_blank",rel:"noopener noreferrer"},m={href:"https://github.com/markdown-it/markdown-it",target:"_blank",rel:"noopener noreferrer"},b={href:"https://web.pulsar-edit.dev/",target:"_blank",rel:"noopener noreferrer"},w=t("p",null,"On that last topic, we have found a way to reduce Pulsar's installed size by ~35.5 MB by deduping some dependencies and otherwise performing some fine tuning on them.",-1),k={href:"https://github.com/bacadra/",target:"_blank",rel:"noopener noreferrer"},y=t("code",null,"input",-1),x={href:"https://github.com/danfuzz",target:"_blank",rel:"noopener noreferrer"},v=t("code",null,"bash",-1),T={href:"https://github.com/kiskoza",target:"_blank",rel:"noopener noreferrer"},P=t("code",null,"pulsar-updater",-1),D=i('

    We hope you enjoyed reading about this update as much as we hope you continue to enjoy using Pulsar. As ever, a huge thank you to our generous donors and community, without whom this project would not be possible.

    Until next time, happy coding, and see you amongst the stars!


    Pulsar

    ',6),A={href:"https://github.com/pulsar-edit/pulsar/pull/804",target:"_blank",rel:"noopener noreferrer"},F=t("code",null,"github",-1),I=t("code",null,"dugite",-1),C={href:"https://github.com/pulsar-edit/pulsar/pull/799",target:"_blank",rel:"noopener noreferrer"},z={href:"https://github.com/pulsar-edit/pulsar/pull/789",target:"_blank",rel:"noopener noreferrer"},B={href:"https://github.com/pulsar-edit/pulsar/pull/798",target:"_blank",rel:"noopener noreferrer"},U=t("code",null,"UI",-1),G={href:"https://github.com/pulsar-edit/pulsar/pull/763",target:"_blank",rel:"noopener noreferrer"},j={href:"https://github.com/pulsar-edit/pulsar/pull/782",target:"_blank",rel:"noopener noreferrer"},q={href:"https://github.com/pulsar-edit/pulsar/pull/796",target:"_blank",rel:"noopener noreferrer"},N={href:"https://github.com/pulsar-edit/pulsar/pull/793",target:"_blank",rel:"noopener noreferrer"},S={href:"https://github.com/pulsar-edit/pulsar/pull/794",target:"_blank",rel:"noopener noreferrer"},V={href:"https://github.com/pulsar-edit/pulsar/pull/795",target:"_blank",rel:"noopener noreferrer"},W={href:"https://github.com/pulsar-edit/pulsar/pull/740",target:"_blank",rel:"noopener noreferrer"},M={href:"https://github.com/pulsar-edit/pulsar/pull/785",target:"_blank",rel:"noopener noreferrer"},R={href:"https://github.com/pulsar-edit/pulsar/pull/783",target:"_blank",rel:"noopener noreferrer"},O={href:"https://github.com/pulsar-edit/pulsar/pull/781",target:"_blank",rel:"noopener noreferrer"},L=t("code",null,"$'...'",-1),E={href:"https://github.com/pulsar-edit/pulsar/pull/776",target:"_blank",rel:"noopener noreferrer"},H=t("code",null,"setuptools",-1),$=t("code",null,"3.12.x",-1),Y={href:"https://github.com/pulsar-edit/pulsar/pull/779",target:"_blank",rel:"noopener noreferrer"},J=t("code",null,"web-tree-sitter",-1),K=t("code",null,"isalnum",-1),Q={href:"https://github.com/pulsar-edit/pulsar/pull/770",target:"_blank",rel:"noopener noreferrer"},X={href:"https://github.com/pulsar-edit/pulsar/pull/760",target:"_blank",rel:"noopener noreferrer"},Z=t("code",null,"git-utils",-1),ee=t("code",null,"5.7.1",-1),te=t("code",null,"^5.7.3",-1),oe={href:"https://github.com/pulsar-edit/pulsar/pull/772",target:"_blank",rel:"noopener noreferrer"},re={href:"https://github.com/pulsar-edit/pulsar/pull/771",target:"_blank",rel:"noopener noreferrer"},ne=t("h3",{id:"github",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#github","aria-hidden":"true"},"#"),e(" github")],-1),se=t("code",null,"whats-my-line",-1),ae=t("code",null,"dugite",-1),le={href:"https://github.com/pulsar-edit/github/pull/36",target:"_blank",rel:"noopener noreferrer"},ie=t("h3",{id:"whats-my-line",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#whats-my-line","aria-hidden":"true"},"#"),e(" whats-my-line")],-1),ue=t("code",null,"dugite",-1),de=t("code",null,"2.1.0",-1),he={href:"https://github.com/pulsar-edit/whats-my-line/pull/7",target:"_blank",rel:"noopener noreferrer"},ce=t("code",null,"package.json",-1),pe={href:"https://github.com/pulsar-edit/whats-my-line/pull/2",target:"_blank",rel:"noopener noreferrer"},_e={href:"https://github.com/pulsar-edit/whats-my-line/pull/4",target:"_blank",rel:"noopener noreferrer"},fe={href:"https://github.com/pulsar-edit/whats-my-line/pull/3",target:"_blank",rel:"noopener noreferrer"},ge={href:"https://github.com/pulsar-edit/whats-my-line/pull/1",target:"_blank",rel:"noopener noreferrer"};function me(be,we){const o=u("ExternalLinkIcon");return s(),a("div",null,[t("p",null,[e("Pulsar "),t("a",h,[e("1.111.0"),r(o)]),e(" is available now!")]),l(" more "),c,p,t("p",null,[e("One of our major changes is the new "),_,e(" API we have added to the 'atom' global class. You can read about this in detail in our recent "),t("a",f,[e("blog post"),r(o)]),e(" but essentially, this allows us to unify the way we render things in Pulsar. For this release, we have a new 'markdown' object that means packages (both core and community) no longer have to worry about performing Markdown rendering and instead can offload it to Pulsar itself. This allows us to create a unified way to render Markdown by using a single set of dependencies. We also took the opportunity to move from the previous "),t("a",g,[e("marked"),r(o)]),e(" library to the "),t("a",m,[e("markdown-it"),r(o)]),e(" parser we are already using on the "),t("a",b,[e("Pulsar Package Registry"),r(o)]),e(". As an added bonus, we also get to save some space on the installation size!")]),w,t("p",null,[e("Next, we have a fix for a really tricky bug that has been around since the Atom days, which we logged on our own repo almost a year ago. The problem is that it has been devilishly difficult to find a perfect set of reproduction steps. Thankfully, we have now managed to do this (with a great deal of help from one of our community members, "),t("a",k,[e("@asiloisad/@bacadra"),r(o)]),e(") and have a fix. This was a problem that would occasionally show up when a hidden "),y,e(' element used in the text editors would be focused when out of view, causing the screen to be "misaligned" or otherwise "shifted".')]),t("p",null,[e("And on the theme of community, we have two issue reports and subsequent bug fixes by community members. The first is by "),t("a",x,[e("@danfuzz"),r(o)]),e(" to fix a problem in our "),v,e(" Tree-sitter grammar where ANSI C quoted strings were not being properly highlighted as actual strings. The second is by "),t("a",T,[e("@kiskoza"),r(o)]),e(" who discovered a problem with our (relatively) recently introduced "),P,e(' package, which notifies you if a new release of Pulsar is available. The bug in question was a problem with the "Dismiss this version" button, which was not caching correctly and would therefore "forget" that somebody had requested to not be notified for that version again.')]),D,t("ul",null,[t("li",null,[e("Fixed: meta: Update CirrusCI GitHub Token "),t("a",A,[e("@confused-Techie"),r(o)])]),t("li",null,[e("Bumped: deps: Update "),F,e(", for "),I,e(" deduping purposes "),t("a",C,[e("@DeeDeeG"),r(o)])]),t("li",null,[e("Fixed: Tree-sitter running fixes (October) "),t("a",z,[e("@savetheclocktower"),r(o)])]),t("li",null,[e('Fixed: Prevent "half screen" bug by resetting scroll position when editor regains focus '),t("a",B,[e("@savetheclocktower"),r(o)])]),t("li",null,[e("Added: [core] New "),U,e(" API "),t("a",G,[e("@confused-Techie"),r(o)])]),t("li",null,[e("Fixed: CI: Build binaries for tag pushes (GitHub Actions) "),t("a",j,[e("@DeeDeeG"),r(o)])]),t("li",null,[e("Added: [DOCS] Add non-macOS keybindings for fuzzy-finder readme "),t("a",q,[e("@Daeraxa"),r(o)])]),t("li",null,[e("Removed: Remove Teletype from Welcome guide "),t("a",N,[e("@Daeraxa"),r(o)])]),t("li",null,[e("Fixed: CI: Python 3.12-related fixes on Cirrus CI "),t("a",S,[e("@DeeDeeG"),r(o)])]),t("li",null,[e("Fixed: CI: Work around missing 'distutils' for Python 3.12+ (GHA round two) "),t("a",V,[e("@DeeDeeG"),r(o)])]),t("li",null,[e("Added: [meta] Create Workflow to validate WASM Grammar Changes "),t("a",W,[e("@confused-Techie"),r(o)])]),t("li",null,[e('Fixed: \u{1F41B} \u2705 Fix caching for "Dismiss this Version" in pulsar-updater '),t("a",M,[e("@kiskoza"),r(o)])]),t("li",null,[e("Fixed: [tree-sitter] Fix proliferation of extra injection layers "),t("a",R,[e("@savetheclocktower"),r(o)])]),t("li",null,[e("Added: CI: Increase timeout length for macOS binary builds "),t("a",O,[e("@DeeDeeG"),r(o)])]),t("li",null,[e("Fixed: Fix the matching of "),L,e(" strings. "),t("a",E,[e("@danfuzz"),r(o)])]),t("li",null,[e("Fixed: [meta] Install Python package "),H,e(" && Use Python "),$,e(),t("a",Y,[e("@confused-Techie"),r(o)])]),t("li",null,[e("Fixed: Update "),J,e(" to include "),K,e(" builtin "),t("a",Q,[e("@savetheclocktower"),r(o)])]),t("li",null,[e("Fixed: [meta] Build x86 Linux binaries on Ubuntu 20.04, for older (more compatible) glibc "),t("a",X,[e("@confused-Techie"),r(o)])]),t("li",null,[e("Bumped: [core] Bump "),Z,e(": "),ee,e(" => "),te,e(),t("a",oe,[e("@confused-Techie"),r(o)])]),t("li",null,[e("Removed: [core] Cleanup Unused Deps "),t("a",re,[e("@confused-Techie"),r(o)])])]),ne,t("ul",null,[t("li",null,[e("Bumped: deps: Update "),se,e(" to bump "),ae,e(" to 2.1.0 "),t("a",le,[e("@DeeDeeG"),r(o)])])]),ie,t("ul",null,[t("li",null,[e("Bumped: Pin "),ue,e(" to "),de,e(),t("a",he,[e("@confused-Techie"),r(o)])]),t("li",null,[e("Bumped: Bump dugite && Bump "),ce,e(" version "),t("a",pe,[e("@confused-Techie"),r(o)])]),t("li",null,[e("Added: Add dugite tests "),t("a",_e,[e("@confused-Techie"),r(o)])]),t("li",null,[e("Removed: Remove TypeScript "),t("a",fe,[e("@confused-Techie"),r(o)])]),t("li",null,[e("Added: Setup Tests and Modernize "),t("a",ge,[e("@confused-Techie"),r(o)])])])])}const ye=n(d,[["render",me],["__file","20231116-Daeraxa-v1.111.0.html.vue"]]);export{ye as default}; diff --git a/assets/20231116-Daeraxa-v1.111.0.html.d47bf460.js b/assets/20231116-Daeraxa-v1.111.0.html.d47bf460.js new file mode 100644 index 0000000000..de024b52cc --- /dev/null +++ b/assets/20231116-Daeraxa-v1.111.0.html.d47bf460.js @@ -0,0 +1 @@ +const e=JSON.parse(`{"key":"v-77f85357","path":"/blog/20231116-Daeraxa-v1.111.0.html","title":"If you're API and you know it, clap your hands!","lang":"en-US","frontmatter":{"title":"If you're API and you know it, clap your hands!","author":"Daeraxa","date":"2023-11-16T00:00:00.000Z","category":["dev"],"tag":["release"]},"excerpt":"

    Pulsar 1.111.0 is available now!

    \\n","headers":[{"level":2,"title":"What do we have for you in Pulsar 1.111.0?","slug":"what-do-we-have-for-you-in-pulsar-1-111-0","link":"#what-do-we-have-for-you-in-pulsar-1-111-0","children":[{"level":3,"title":"Pulsar","slug":"pulsar","link":"#pulsar","children":[]},{"level":3,"title":"github","slug":"github","link":"#github","children":[]},{"level":3,"title":"whats-my-line","slug":"whats-my-line","link":"#whats-my-line","children":[]}]}],"git":{"updatedTime":1700177110000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":3.71,"words":1113},"filePathRelative":"blog/20231116-Daeraxa-v1.111.0.md","localizedDate":"November 16, 2023"}`);export{e as data}; diff --git a/assets/20231212-Daeraxa-DecemberUpdate.html.0098fa41.js b/assets/20231212-Daeraxa-DecemberUpdate.html.0098fa41.js new file mode 100644 index 0000000000..324c32eb3e --- /dev/null +++ b/assets/20231212-Daeraxa-DecemberUpdate.html.0098fa41.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-04bbc674","path":"/blog/20231212-Daeraxa-DecemberUpdate.html","title":"Community Update","lang":"en-US","frontmatter":{"title":"Community Update","author":"Daeraxa","date":"2023-12-11T00:00:00.000Z","category":["news","log"],"tag":["update"]},"excerpt":"

    If you have been good this year, you might just find something in your stocking this Christmas. Until then, you can find the Pulsar community update right here!

    \\n","headers":[{"level":2,"title":"Update on Electron updates","slug":"update-on-electron-updates","link":"#update-on-electron-updates","children":[{"level":3,"title":"Tree-sitter","slug":"tree-sitter","link":"#tree-sitter","children":[]},{"level":3,"title":"Making it available","slug":"making-it-available","link":"#making-it-available","children":[]},{"level":3,"title":"Package compatibility","slug":"package-compatibility","link":"#package-compatibility","children":[]}]},{"level":2,"title":"PPR website issues","slug":"ppr-website-issues","link":"#ppr-website-issues","children":[]},{"level":2,"title":"Community Spotlight","slug":"community-spotlight","link":"#community-spotlight","children":[{"level":3,"title":"Default file icons","slug":"default-file-icons","link":"#default-file-icons","children":[]}]}],"git":{"updatedTime":1702350476000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":3.45,"words":1036},"filePathRelative":"blog/20231212-Daeraxa-DecemberUpdate.md","localizedDate":"December 11, 2023"}');export{e as data}; diff --git a/assets/20231212-Daeraxa-DecemberUpdate.html.6af4fa5b.js b/assets/20231212-Daeraxa-DecemberUpdate.html.6af4fa5b.js new file mode 100644 index 0000000000..f74e74c9dc --- /dev/null +++ b/assets/20231212-Daeraxa-DecemberUpdate.html.6af4fa5b.js @@ -0,0 +1 @@ +import{_ as r}from"./electron.b5867f08.js";import{_ as i}from"./tools.52491106.js";import{_ as s}from"./spotlight.aba19e76.js";import{_ as h,o as l,c,e as d,a as e,b as t,d as o,f as n,r as u}from"./app.0e1565ce.js";const p="/assets/file-icons.45aa1c32.png",m={},f=e("p",null,"If you have been good this year, you might just find something in your stocking this Christmas. Until then, you can find the Pulsar community update right here!",-1),g=n('

    Welcome to the December Community Update!

    Another warm welcome to the Pulsar community update, where we cover new developments and events in the world of Pulsar. This month we have a couple of really significant changes to how Pulsar works internally by creating a couple of new APIs that can be used throughout the application, a new package to help you run code directly within Pulsar and our usual community spotlight to say thank you to those community members contributing to Pulsar's development!

    Update on Electron updates

    ',4),b={href:"https://github.com/mauricioszabo",target:"_blank",rel:"noopener noreferrer"},w=e("code",null,"github",-1),y=e("h3",{id:"tree-sitter",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#tree-sitter","aria-hidden":"true"},"#"),t(" Tree-sitter")],-1),_={href:"https://pulsar-edit.dev/tag/tree-sitter/",target:"_blank",rel:"noopener noreferrer"},v={href:"https://github.com/savetheclocktower",target:"_blank",rel:"noopener noreferrer"},k=e("p",null,"What we plan to do next is enable this modern version of tree-sitter by default, the implementation is pretty mature at this point and it is as good as, if not better than, the original implementation in many languages. Once enabled by default we can iron out any issues we haven't seen as yet with a view to completely removing the old implementation to clear the way to the new Electron version.",-1),P=e("h3",{id:"making-it-available",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#making-it-available","aria-hidden":"true"},"#"),t(" Making it available")],-1),x=e("p",null,`We haven't decided on the exact approach yet, but we hope to soon be able to publish "preview" binaries and make them available for download via our website and GitHub. That way, early adopters and people who want to help out with the future of Pulsar can do so easily. The more people we have using it and reporting issues, the faster we can have this migrated into the main version.`,-1),T={href:"https://github.com/pulsar-edit/pulsar/tree/feature/latest-electron-native-superstring",target:"_blank",rel:"noopener noreferrer"},E=e("h3",{id:"package-compatibility",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#package-compatibility","aria-hidden":"true"},"#"),t(" Package compatibility")],-1),C={href:"https://web.pulsar-edit.dev/packages/hydrogen",target:"_blank",rel:"noopener noreferrer"},D={href:"https://pulsar-edit.dev/community.html",target:"_blank",rel:"noopener noreferrer"},O={href:"https://pulsar-edit.dev/blog/20231004-Daeraxa-OctoberUpdate.html#introducing-pulsar-cooperative",target:"_blank",rel:"noopener noreferrer"},W=e("h2",{id:"ppr-website-issues",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#ppr-website-issues","aria-hidden":"true"},"#"),t(" PPR website issues")],-1),I=e("img",{src:i,height:"200"},null,-1),U={href:"https://github.com/confused-Techie",target:"_blank",rel:"noopener noreferrer"},j=n('

    Community Spotlight

    Default file icons

    ',3),A={href:"https://github.com/tthaumaturgist",target:"_blank",rel:"noopener noreferrer"},N=e("img",{src:p,height:"500"},null,-1),V={href:"https://github.com/confused-Techie",target:"_blank",rel:"noopener noreferrer"},H=e("hr",null,null,-1),S=e("p",null,"And that brings this, the final community update of the year, to a close. As always, a big thank you to all of our community members and a special thanks to those who donate to the project and make this possible. We hope to see you again in 2024!",-1);function z(B,L){const a=u("ExternalLinkIcon");return l(),c("div",null,[f,d(" more "),g,e("p",null,[t("It has been a while since we have shared an update on the progress of getting Pulsar up to date with its version of Electron. This has been a long-term goal of the project, but not an easy one due to significant changes in the Electron framework. "),e("a",b,[t("@maur\xEDcio szabo"),o(a)]),t(" has had a long-running fork of Pulsar that is working on the latest versions of Electron and by all accounts, it works just fine. One recent win has been to get one of Pulsar's core packages, the "),w,t(" package, working again.")]),y,e("p",null,[t("However, probably our main obstacle is tree-sitter, the library Pulsar uses for a number of languages to perform syntax highlighting, code folding and a number of other useful features that is not only out of date compared to modern tree-sitter but blocking the move to the new version of Electron. The good news is that Pulsar has been shipping with a brand new implementation of tree-sitter for some time now (but disabled by default) and there have been steady updates and improvements to it. If you want to read more about tree-sitter and its implementation, then we have a fantastic "),e("a",_,[t("series of blog posts"),o(a)]),t(" by "),e("a",v,[t("@savetheclocktower"),o(a)]),t(" detailing it.")]),k,P,x,e("p",null,[t("Of course, you are always welcome to build it from source using the "),e("a",T,[t("latest-electron"),o(a)]),t(" feature branch.")]),E,e("p",null,[t("One downside to moving to a new version of Electron is that some community packages do use some of these same deprecated features of Electron that have given us so much trouble with some of the core packages. On the surface, this means that we will potentially introduce some breaking changes in Pulsar, causing some of these packages to no longer work - the "),e("a",C,[t("Hydrogen"),o(a)]),t(" package being a perfect example of this.")]),e("p",null,[t("However, part of the goal of the Pulsar project was to make sure that the huge number of community packages out there were retained and it is in no way our goal to deliberately break them. To that end, any package authors who find their packages affected by the updated Electron versions are free to reach out to us via any of our "),e("a",D,[t("social channels"),o(a)]),t(". Likewise, if any users are affected by any of these changes and find themselves with an affected package that is no longer maintained by the original author, the same avenues of help and support will be available should you wish to fork and publish the package under your own account.")]),e("p",null,[t("We also have a plan we mentioned recently, the "),e("a",O,[t("Pulsar Cooperative"),o(a)]),t(" initiative, in order to have a community-maintained space for package development without the overhead of full-on ownership.")]),W,I,e("p",null,[t("Some people may have noticed we sporadically have some issues with the Pulsar Package Registry website displaying packages correctly. This is rare and tends to resolve itself fairly quickly once the faulty instance is dropped. "),e("a",U,[t("@confused-techie"),o(a)]),t(" has introduced some additional error logging to try and nail this issue down. The error page has also been updated to include some links for people to report the issues as and when they see them in order to help us capture and resolve this issue.")]),j,e("p",null,[t("Community member "),e("a",A,[t("@tthaumaturgist"),o(a)]),t(" has produced a whole bunch of amazing new icons that can be used to show that a given file will be opened within Pulsar as well as identifying the type of file at a glance.")]),N,e("p",null,[t("They have produced icons for both Windows and macOS, "),e("a",V,[t("@confused-techie"),o(a)]),t(" is currently working on implementing this within Pulsar for Windows. A huge thank you to them for this wonderful contribution!")]),H,S])}const $=h(m,[["render",z],["__file","20231212-Daeraxa-DecemberUpdate.html.vue"]]);export{$ as default}; diff --git a/assets/20231216-Daeraxa-v1.112.0.html.12b661cc.js b/assets/20231216-Daeraxa-v1.112.0.html.12b661cc.js new file mode 100644 index 0000000000..b59b5e0e14 --- /dev/null +++ b/assets/20231216-Daeraxa-v1.112.0.html.12b661cc.js @@ -0,0 +1 @@ +import{_ as n,o as a,c as l,a as e,b as t,d as r,e as i,f as s,r as u}from"./app.0e1565ce.js";const d={},p={href:"https://github.com/pulsar-edit/pulsar/releases/tag/v1.112.0",target:"_blank",rel:"noopener noreferrer"},h={href:"https://pulsar-edit.dev/blog/20221215-confused-Techie-v1.100.1-beta.html",target:"_blank",rel:"noopener noreferrer"},c=e("code",null,"fuzzyMatcher",-1),g={href:"https://github.com/Trigan2025",target:"_blank",rel:"noopener noreferrer"},f=e("code",null,"wrap-guide",-1),m={href:"https://pulsar-edit.dev/blog/20231004-Daeraxa-OctoberUpdate.html#converting-ppm-s-code-from-callbacks-to-async",target:"_blank",rel:"noopener noreferrer"},_=s('

    On to our GitHub package, we found that we had a rather common issue with people not being able to log into their GitHub account via the package. Essentially, it was possible to set scopes in such a way that, although permissions were technically granted, Pulsar was unable to read the scopes and refused to log in. To solve this, we have updated the package to provide feedback and improved the scope checking logic. We also updated the link to the Personal Access Token page to include by default the scopes that Pulsar requires.

    Continuing with the theme of last month's new "UI" API, we have another new API that all packages can now take advantage of. This time it is ui.fuzzyMatcher which will allow packages to use Pulsar's fuzzy-finder module without needing to bundle it into their own packages.

    And to finish off with a bug fix, an issue was found where Pulsar wasn't correctly inheriting the directory from which the pulsar binary was being run, leading to some slightly odd behaviour.

    It is hard to believe that it has been an entire year since we created our first tagged release of Pulsar and we never would have managed to get to this milestone without the amazing support from our donors and our community, so as ever, a massive thank you to everyone who has allowed us to get this far!

    Until next time (and next year!), happy coding, and see you amongst the stars!


    Pulsar

    ',9),b=e("code",null,"ScopeResolver",-1),w={href:"https://github.com/pulsar-edit/pulsar/pull/836",target:"_blank",rel:"noopener noreferrer"},k={href:"https://github.com/pulsar-edit/pulsar/pull/838",target:"_blank",rel:"noopener noreferrer"},v={href:"https://github.com/pulsar-edit/pulsar/pull/837",target:"_blank",rel:"noopener noreferrer"},y=e("code",null,"ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT",-1),x={href:"https://github.com/pulsar-edit/pulsar/pull/831",target:"_blank",rel:"noopener noreferrer"},P={href:"https://github.com/pulsar-edit/pulsar/pull/774",target:"_blank",rel:"noopener noreferrer"},D={href:"https://github.com/pulsar-edit/pulsar/pull/819",target:"_blank",rel:"noopener noreferrer"},T={href:"https://github.com/pulsar-edit/pulsar/pull/812",target:"_blank",rel:"noopener noreferrer"},A={href:"https://github.com/pulsar-edit/pulsar/pull/809",target:"_blank",rel:"noopener noreferrer"},I={href:"https://github.com/pulsar-edit/pulsar/pull/780",target:"_blank",rel:"noopener noreferrer"},F=e("h3",{id:"ppm",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#ppm","aria-hidden":"true"},"#"),t(" PPM")],-1),z={href:"https://github.com/pulsar-edit/ppm/pull/114",target:"_blank",rel:"noopener noreferrer"},G={href:"https://github.com/pulsar-edit/ppm/pull/110",target:"_blank",rel:"noopener noreferrer"},M=e("code",null,"rimraf",-1),R=e("code",null,"fs",-1),C={href:"https://github.com/pulsar-edit/ppm/pull/108",target:"_blank",rel:"noopener noreferrer"},N={href:"https://github.com/pulsar-edit/ppm/pull/113",target:"_blank",rel:"noopener noreferrer"},q={href:"https://github.com/pulsar-edit/ppm/pull/109",target:"_blank",rel:"noopener noreferrer"},B={href:"https://github.com/pulsar-edit/ppm/pull/112",target:"_blank",rel:"noopener noreferrer"},U={href:"https://github.com/pulsar-edit/ppm/pull/111",target:"_blank",rel:"noopener noreferrer"},E={href:"https://github.com/pulsar-edit/ppm/pull/104",target:"_blank",rel:"noopener noreferrer"},S={href:"https://github.com/pulsar-edit/ppm/pull/105",target:"_blank",rel:"noopener noreferrer"},V={href:"https://github.com/pulsar-edit/ppm/pull/107",target:"_blank",rel:"noopener noreferrer"},W={href:"https://github.com/pulsar-edit/ppm/pull/106",target:"_blank",rel:"noopener noreferrer"},L={href:"https://github.com/pulsar-edit/ppm/pull/103",target:"_blank",rel:"noopener noreferrer"},O={href:"https://github.com/pulsar-edit/ppm/pull/95",target:"_blank",rel:"noopener noreferrer"},H={href:"https://github.com/pulsar-edit/ppm/pull/101",target:"_blank",rel:"noopener noreferrer"},J={href:"https://github.com/pulsar-edit/ppm/pull/100",target:"_blank",rel:"noopener noreferrer"},Y={href:"https://github.com/pulsar-edit/ppm/pull/97",target:"_blank",rel:"noopener noreferrer"},j={href:"https://github.com/pulsar-edit/ppm/pull/99",target:"_blank",rel:"noopener noreferrer"},K=e("code",null,"request",-1),Q=e("code",null,"superagent",-1),X={href:"https://github.com/pulsar-edit/ppm/pull/87",target:"_blank",rel:"noopener noreferrer"},Z=e("h3",{id:"github",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#github","aria-hidden":"true"},"#"),t(" github")],-1),$={href:"https://github.com/pulsar-edit/github/pull/38",target:"_blank",rel:"noopener noreferrer"};function ee(te,oe){const o=u("ExternalLinkIcon");return a(),l("div",null,[e("p",null,[t("Pulsar "),e("a",p,[t("1.112.0"),r(o)]),t(" is available now!")]),i(" more "),e("p",null,[t("Welcome to our 12th regular release! It has been exactly a year since we put out "),e("a",h,[t("our first tagged release"),r(o)]),t(' and development continues. This month we have some new soft-wrapping options, some long overdue updates to PPM, improvements to our "GitHub" package, a new '),c,t(" API and our usual slew of bug fixes.")]),e("p",null,[t("Let's start with a feature added by community member "),e("a",g,[t("@Trigan2025"),r(o)]),t('. There are now new options for the "soft wrapping" feature that allows Pulsar to automatically show or hide the soft wrap guide line based on your soft wrap settings. You can find this new option within '),f,t(" package settings.")]),e("p",null,[t("We have a number of new PPM changes, including better and more secure network handling and converting PPM's code to async. You can read about this second change in much more detail in a recent "),e("a",m,[t("blog post"),r(o)]),t(" we made. We have also taken the opportunity to do some out-of-season spring cleaning to tidy up the repo and get rid of old, unused dependencies, as well as some general maintenance.")]),_,e("ul",null,[e("li",null,[t("Added: [tree-sitter] Share config caches between "),b,t("s "),e("a",w,[t("@savetheclocktower"),r(o)])]),e("li",null,[t("Bumped: deps: Update github to v0.36.19-pretranspiled (fix silent failure when inputting a token with incorrect scopes) "),e("a",k,[t("@DeeDeeG"),r(o)])]),e("li",null,[t("Bumped: ppm: Update ppm to commit 957acbd90cfc9f361c183b3c "),e("a",v,[t("@DeeDeeG"),r(o)])]),e("li",null,[t("Added: Return to original logic for "),y,t(),e("a",x,[t("@savetheclocktower"),r(o)])]),e("li",null,[t("Added: Moving fuzzy-native to core "),e("a",P,[t("@mauricioszabo"),r(o)])]),e("li",null,[t("Fixed: Tree-sitter rolling fixes for November "),e("a",D,[t("@savetheclocktower"),r(o)])]),e("li",null,[t("Fixed: CI: Update Rolling upload token for Cirrus "),e("a",T,[t("@DeeDeeG"),r(o)])]),e("li",null,[t("Bumped: ppm: Update to commit 13fb2845e00d7e04c2461f93 "),e("a",A,[t("@DeeDeeG"),r(o)])]),e("li",null,[t("Added: Ability to indicate when to automatically show or hide the wrap-guide "),e("a",I,[t("@Trigan2025"),r(o)])])]),F,e("ul",null,[e("li",null,[t("Bumped: fix(deps): update dependency semver to v7.5.2 [security] "),e("a",z,[t("@renovate"),r(o)])]),e("li",null,[t("Added: Configure Renovate "),e("a",G,[t("@confused-Techie"),r(o)])]),e("li",null,[t("Added: Migrate from "),M,t(" to NodeJS "),R,t(),e("a",C,[t("@confused-Techie"),r(o)])]),e("li",null,[t("Added: Implement Codacy Recommendations "),e("a",N,[t("@confused-Techie"),r(o)])]),e("li",null,[t("Removed: Prune outdated Deps "),e("a",q,[t("@confused-Techie"),r(o)])]),e("li",null,[t("Removed: Remove unused Variables "),e("a",B,[t("@confused-Techie"),r(o)])]),e("li",null,[t("Added: Add Codacy and Friends Configuration "),e("a",U,[t("@confused-Techie"),r(o)])]),e("li",null,[t("Removed: src: Delete unused code in uninstall.js "),e("a",E,[t("@DeeDeeG"),r(o)])]),e("li",null,[t("Fixed: src: Fix usage/help text (and error message) for -b/-t flags for ppm install "),e("a",S,[t("@DeeDeeG"),r(o)])]),e("li",null,[t("Added: Repository Cleanup "),e("a",V,[t("@confused-Techie"),r(o)])]),e("li",null,[t("Fixed: Fix Newer NodeJS CI "),e("a",W,[t("@confused-Techie"),r(o)])]),e("li",null,[t("Fixed: src: Stop pinging backend during package uninstalls "),e("a",L,[t("@DeeDeeG"),r(o)])]),e("li",null,[t("Added: Asyncify without topmost interface "),e("a",O,[t("@2colours"),r(o)])]),e("li",null,[t("Fixed: CI: Work around a weird bug in Yarn v1.x "),e("a",H,[t("@DeeDeeG"),r(o)])]),e("li",null,[t('Fixed: src: Rebrand two lines of "ppm --version" output '),e("a",J,[t("@DeeDeeG"),r(o)])]),e("li",null,[t("Bumped: deps: Bump nan for compatibility with newer NodeJS "),e("a",Y,[t("@DeeDeeG"),r(o)])]),e("li",null,[t("Fixed: Fix Error Handling "),e("a",j,[t("@confused-Techie"),r(o)])]),e("li",null,[t("Removed: Remove "),K,t(" Migrate to "),Q,t(" && Fix CI "),e("a",X,[t("@confused-Techie"),r(o)])])]),Z,e("ul",null,[e("li",null,[t("Added: lib: Allow parent scopes when checking if each required scope is set "),e("a",$,[t("@DeeDeeG"),r(o)])])])])}const ne=n(d,[["render",ee],["__file","20231216-Daeraxa-v1.112.0.html.vue"]]);export{ne as default}; diff --git a/assets/20231216-Daeraxa-v1.112.0.html.cc8771c3.js b/assets/20231216-Daeraxa-v1.112.0.html.cc8771c3.js new file mode 100644 index 0000000000..b21f883be2 --- /dev/null +++ b/assets/20231216-Daeraxa-v1.112.0.html.cc8771c3.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-6409cd37","path":"/blog/20231216-Daeraxa-v1.112.0.html","title":"Christmas has come early","lang":"en-US","frontmatter":{"title":"Christmas has come early","author":"Daeraxa","date":"2023-12-16T00:00:00.000Z","category":["dev"],"tag":["release"]},"excerpt":"

    Pulsar 1.112.0 is available now!

    \\n","headers":[{"level":3,"title":"Pulsar","slug":"pulsar","link":"#pulsar","children":[]},{"level":3,"title":"PPM","slug":"ppm","link":"#ppm","children":[]},{"level":3,"title":"github","slug":"github","link":"#github","children":[]}],"git":{"updatedTime":1702746267000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":3.57,"words":1071},"filePathRelative":"blog/20231216-Daeraxa-v1.112.0.md","localizedDate":"December 16, 2023"}');export{e as data}; diff --git a/assets/20231219-DeeDeeG-v1.112.1.html.0ec91d1c.js b/assets/20231219-DeeDeeG-v1.112.1.html.0ec91d1c.js new file mode 100644 index 0000000000..30e750a9a0 --- /dev/null +++ b/assets/20231219-DeeDeeG-v1.112.1.html.0ec91d1c.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-15a14140","path":"/blog/20231219-DeeDeeG-v1.112.1.html","title":"Hotfix: Pulsar v1.112.1","lang":"en-US","frontmatter":{"title":"Hotfix: Pulsar v1.112.1","author":"DeeDeeG","date":"2023-12-19T00:00:00.000Z","category":["dev"],"tag":["release"]},"excerpt":"

    Hotfix: Pulsar 1.112.1 is available now!

    \\n","headers":[{"level":2,"title":"What is new in 1.112.1?","slug":"what-is-new-in-1-112-1","link":"#what-is-new-in-1-112-1","children":[]}],"git":{"updatedTime":1702968522000,"contributors":[{"name":"DeeDeeG","email":"DeeDeeG@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":0.43,"words":130},"filePathRelative":"blog/20231219-DeeDeeG-v1.112.1.md","localizedDate":"December 19, 2023"}');export{e as data}; diff --git a/assets/20231219-DeeDeeG-v1.112.1.html.f5412ece.js b/assets/20231219-DeeDeeG-v1.112.1.html.f5412ece.js new file mode 100644 index 0000000000..0df9fd5702 --- /dev/null +++ b/assets/20231219-DeeDeeG-v1.112.1.html.f5412ece.js @@ -0,0 +1 @@ +import{_ as o,o as r,c as s,a as e,b as t,d as n,e as i,r as l}from"./app.0e1565ce.js";const c={},h={href:"https://github.com/pulsar-edit/pulsar/releases/tag/v1.112.1",target:"_blank",rel:"noopener noreferrer"},p=e("h2",{id:"what-is-new-in-1-112-1",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#what-is-new-in-1-112-1","aria-hidden":"true"},"#"),t(" What is new in 1.112.1?")],-1),d=e("p",null,"Hotfix for important functionality within PPM. During recent refactoring of the PPM package, a bug was accidentally introduced that made it impossible for any package maintainer to publish a new package (or to publish new versions of their existing packages). An update was needed within PPM to restore this functionality to expected working order.",-1),u=e("p",null,"Includes these PRs: https://github.com/pulsar-edit/ppm/pull/116 and https://github.com/pulsar-edit/ppm/pull/118 to fix the issue.",-1),_={href:"https://github.com/pulsar-edit/pulsar/releases/tag/v1.112.0",target:"_blank",rel:"noopener noreferrer"};function f(m,g){const a=l("ExternalLinkIcon");return r(),s("div",null,[e("p",null,[t("Hotfix: Pulsar 1.112.1 is "),e("a",h,[t("available now!"),n(a)])]),i(" more "),p,d,u,e("p",null,[t("See the "),e("a",_,[t("v1.112.0"),n(a)]),t(" release for all the other changes since v1.111.0.")])])}const w=o(c,[["render",f],["__file","20231219-DeeDeeG-v1.112.1.html.vue"]]);export{w as default}; diff --git a/assets/20240112-Daeraxa-JanuaryUpdate.html.4c5055b5.js b/assets/20240112-Daeraxa-JanuaryUpdate.html.4c5055b5.js new file mode 100644 index 0000000000..49afa87a16 --- /dev/null +++ b/assets/20240112-Daeraxa-JanuaryUpdate.html.4c5055b5.js @@ -0,0 +1 @@ +import{_ as n}from"./tree-sitter.6a39e323.js";import{_ as r}from"./package.4e7449ae.js";import{_ as s}from"./spotlight.aba19e76.js";import{_ as i,o as h,c as l,e as c,a as e,b as t,d as a,f as d,r as u}from"./app.0e1565ce.js";const p="/assets/bugsplat.ec5f7075.png",g="/assets/checklist.83a0eba5.png",m={},_=e("p",null,"Happy new year! Welcome to the first Pulsar community update of 2024!",-1),f=d('

    Welcome to the January Community Update!

    First of all, we hope everyone had a fantastic new year and here is to a new one! While the Pulsar team has been largely enjoying the holidays we still have plenty of things to update you with!

    New tree-sitter becoming default

    [1]

    ',4),b={href:"https://github.com/pulsar-edit/pulsar/pull/855",target:"_blank",rel:"noopener noreferrer"},w={href:"https://pulsar-edit.dev/blog/20231212-Daeraxa-DecemberUpdate.html#tree-sitter",target:"_blank",rel:"noopener noreferrer"},y={href:"https://github.com/savetheclocktower",target:"_blank",rel:"noopener noreferrer"},k={href:"https://pulsar-edit.dev/tag/tree-sitter/",target:"_blank",rel:"noopener noreferrer"},v=e("h2",{id:"annoying-website-bug-found-and-zapped",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#annoying-website-bug-found-and-zapped","aria-hidden":"true"},"#"),t(" Annoying website bug found and zapped!")],-1),x=e("img",{src:p,height:"150"},null,-1),P={href:"https://pulsar-edit.dev/blog/20231212-Daeraxa-DecemberUpdate.html#ppr-website-issues",target:"_blank",rel:"noopener noreferrer"},j={href:"https://github.com/confused-Techie",target:"_blank",rel:"noopener noreferrer"},T={href:"https://github.com/savetheclocktower",target:"_blank",rel:"noopener noreferrer"},D={href:"https://github.com/pulsar-edit/package-frontend/pull/127",target:"_blank",rel:"noopener noreferrer"},U=e("h2",{id:"new-ppr-owners-ownername-api-endpoint",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#new-ppr-owners-ownername-api-endpoint","aria-hidden":"true"},"#"),t(" New PPR "),e("code",null,"owners/:ownerName"),t(" API endpoint")],-1),N=e("img",{src:r,height:"150"},null,-1),I={href:"https://github.com/confused-Techie",target:"_blank",rel:"noopener noreferrer"},C={href:"https://github.com/pulsar-edit/package-backend/pull/216",target:"_blank",rel:"noopener noreferrer"},R={href:"https://github.com/savetheclocktower",target:"_blank",rel:"noopener noreferrer"},S={href:"https://github.com/pulsar-edit/package-backend/pull/215",target:"_blank",rel:"noopener noreferrer"},V={href:"https://github.com/bacadra/",target:"_blank",rel:"noopener noreferrer"},W=e("code",null,"?owner=bacadra",-1),A=e("h2",{id:"long-term-projects",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#long-term-projects","aria-hidden":"true"},"#"),t(" Long-term projects")],-1),B=e("img",{src:g,height:"150"},null,-1),L=e("p",null,`This community blog post was designed to highlight ongoing work and successes that are going on both within the Pulsar team and the community at large for "Pulsar-adjacent" items. That is, along with new features being developed for Pulsar, we also highlight things going on that might otherwise go unnoticed but are deserving of attention. Sometimes we may announce things, but due to other priorities, it may take a while until we get around to implementing or announcing any news. This doesn't mean we have forgotten about them, not by a long shot. So just to recap some of the things we are looking to progress in 2024 that have been previously mentioned on this blog and in our social channels:`,-1),E={href:"https://pulsar-edit.dev/blog/20231212-Daeraxa-DecemberUpdate.html",target:"_blank",rel:"noopener noreferrer"},z=e("li",null,"New custom website framework and website to replace our current Vuepress one",-1),J={href:"https://pulsar-edit.dev/blog/20231004-Daeraxa-OctoberUpdate.html#introducing-pulsar-cooperative",target:"_blank",rel:"noopener noreferrer"},F=e("li",null,"Iconography updates",-1),M=e("h2",{id:"community-spotlight",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#community-spotlight","aria-hidden":"true"},"#"),t(" Community Spotlight")],-1),Y=e("img",{src:s,height:"200"},null,-1),q=e("p",null,"As it is the start of the new year, this spotlight should be particularly special. We have received an absolute ton of new and repeat donations, for which we couldn't be more thankful. You really do keep this project alive. We also want to thank each and every one of our community members, no matter what you do: those who make themselves known in our social channels, publish packages, contribute code, donate to the project, and those who are just content using Pulsar or reading our blog posts. This spotlight goes out to our entire community, and here is to an even better 2024!",-1),H=e("hr",null,null,-1),O=e("p",null,"So with that, we start another year. Thanks again to the entire community, no matter how you choose to get involved, and here is to another year of Pulsar!",-1),G=e("hr",{class:"footnotes-sep"},null,-1),K={class:"footnotes"},Q={class:"footnotes-list"},X={id:"footnote1",class:"footnote-item"},Z={href:"https://tree-sitter.github.io/tree-sitter/",target:"_blank",rel:"noopener noreferrer"},$=e("a",{href:"#footnote-ref1",class:"footnote-backref"},"\u21A9\uFE0E",-1);function ee(te,oe){const o=u("ExternalLinkIcon");return h(),l("div",null,[_,c(" more "),f,e("p",null,[t("This was mentioned in the December blog post, but we have now "),e("a",b,[t("landed this change"),a(o)]),t(" in the newest rolling releases and will be coming to our regular releases in the next version. This has been available as an option for a while now but was disabled by default. You can read more info in the "),e("a",w,[t("previous blog post"),a(o)]),t(" and about the implementation in detail in "),e("a",y,[t("@savetheclocktower"),a(o)]),t("'s "),e("a",k,[t("blog post series"),a(o)]),t(".")]),v,x,e("p",null,[t("We mentioned in the "),e("a",P,[t("last blog post"),a(o)]),t(" that the Pulsar Package Registry website has sporadically encountered some kind of problem causing the website to not display any data. After "),e("a",j,[t("@confused-techie"),a(o)]),t(" implemented some changes to improve our error logging, we have actually managed to find something! To top it off, "),e("a",T,[t("@savetheclocktower"),a(o)]),t(" was able to find the exact problem and solve it. If you wish to read more about the exact issue, you can read more on the "),e("a",D,[t("pull request"),a(o)]),t(".")]),U,N,e("p",null,[t("Staying with the PPR, "),e("a",I,[t("@confused-techie"),a(o)]),t(" has implemented a "),e("a",C,[t("new endpoint for the API"),a(o)]),t(". Building off the work by "),e("a",R,[t("@savetheclocktower"),a(o)]),t(" (to make the "),e("a",S,[t("backend aware of package owners"),a(o)]),t(") that allows you to filter packages by author, this can also be used via URL in the PPR frontend website (with UI controls still to come). For example, if you want to list all packages published by "),e("a",V,[t("@bacadra"),a(o)]),t(" (one of the Pulsar community's most prolific package authors), you can add "),W,t(" to the end of the URL. e.g.https://web.pulsar-edit.dev/packages?owner=bacadra.")]),A,B,L,e("ul",null,[e("li",null,[t("Migrating Pulsar to modern Electron (more in last month's "),e("a",E,[t("blog post"),a(o)]),t(")")]),z,e("li",null,[e("a",J,[t("Pulsar-Cooperative"),a(o)])]),F]),M,Y,q,H,O,G,e("section",K,[e("ol",Q,[e("li",X,[e("p",null,[t("Image from "),e("a",Z,[t("https://tree-sitter.github.io/tree-sitter/"),a(o)]),t(" - Copyright (c) 2018-2021 Max Brunsfeld "),$])])])])])}const ie=i(m,[["render",ee],["__file","20240112-Daeraxa-JanuaryUpdate.html.vue"]]);export{ie as default}; diff --git a/assets/20240112-Daeraxa-JanuaryUpdate.html.727ac307.js b/assets/20240112-Daeraxa-JanuaryUpdate.html.727ac307.js new file mode 100644 index 0000000000..840f4c8d2c --- /dev/null +++ b/assets/20240112-Daeraxa-JanuaryUpdate.html.727ac307.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-4f70c30a","path":"/blog/20240112-Daeraxa-JanuaryUpdate.html","title":"Community Update","lang":"en-US","frontmatter":{"title":"Community Update","author":"Daeraxa","date":"2024-01-12T00:00:00.000Z","category":["news","log"],"tag":["update"]},"excerpt":"

    Happy new year! Welcome to the first Pulsar community update of 2024!

    \\n","headers":[{"level":2,"title":"New tree-sitter becoming default","slug":"new-tree-sitter-becoming-default","link":"#new-tree-sitter-becoming-default","children":[]},{"level":2,"title":"Annoying website bug found and zapped!","slug":"annoying-website-bug-found-and-zapped","link":"#annoying-website-bug-found-and-zapped","children":[]},{"level":2,"title":"New PPR owners/:ownerName API endpoint","slug":"new-ppr-owners-ownername-api-endpoint","link":"#new-ppr-owners-ownername-api-endpoint","children":[]},{"level":2,"title":"Long-term projects","slug":"long-term-projects","link":"#long-term-projects","children":[]},{"level":2,"title":"Community Spotlight","slug":"community-spotlight","link":"#community-spotlight","children":[]}],"git":{"updatedTime":1705202015000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":3}]},"readingTime":{"minutes":2.64,"words":793},"filePathRelative":"blog/20240112-Daeraxa-JanuaryUpdate.md","localizedDate":"January 12, 2024"}');export{e as data}; diff --git a/assets/20240115-Daeraxa-v1.113.0.html.1359019c.js b/assets/20240115-Daeraxa-v1.113.0.html.1359019c.js new file mode 100644 index 0000000000..438a5de46a --- /dev/null +++ b/assets/20240115-Daeraxa-v1.113.0.html.1359019c.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-3500d176","path":"/blog/20240115-Daeraxa-v1.113.0.html","title":"Unlucky for some, but not us. Our 13th release, Pulsar 1.113.0, is available now!","lang":"en-US","frontmatter":{"title":"Unlucky for some, but not us. Our 13th release, Pulsar 1.113.0, is available now!","author":"Daeraxa","date":"2024-01-15T00:00:00.000Z","category":["dev"],"tag":["release"]},"excerpt":"

    Pulsar 1.113.0 is available now!

    \\n","headers":[{"level":3,"title":"Pulsar","slug":"pulsar","link":"#pulsar","children":[]},{"level":3,"title":"find-and-replace","slug":"find-and-replace","link":"#find-and-replace","children":[]},{"level":3,"title":"symbols-view","slug":"symbols-view","link":"#symbols-view","children":[]}],"git":{"updatedTime":1705380014000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":2}]},"readingTime":{"minutes":3.46,"words":1039},"filePathRelative":"blog/20240115-Daeraxa-v1.113.0.md","localizedDate":"January 15, 2024"}');export{e as data}; diff --git a/assets/20240115-Daeraxa-v1.113.0.html.c5e2a7ca.js b/assets/20240115-Daeraxa-v1.113.0.html.c5e2a7ca.js new file mode 100644 index 0000000000..7d618c92cc --- /dev/null +++ b/assets/20240115-Daeraxa-v1.113.0.html.c5e2a7ca.js @@ -0,0 +1 @@ +import{_ as a,o as n,c as s,a as t,b as e,d as r,e as i,f as l,r as h}from"./app.0e1565ce.js";const d={},u={href:"https://github.com/pulsar-edit/pulsar/releases/tag/v1.113.0",target:"_blank",rel:"noopener noreferrer"},c=t("p",null,[e("Welcome to our first release of 2024! This is our 13th main release; let's just hope we aren't cursed by the number. This month we are enabling a feature by default that has been in the works for a long time and is a major step in moving Pulsar to current versions of Electron. We also have a significant update to our "),t("code",null,"symbols-view"),e(" package and a number of bug fixes as per usual.")],-1),p=t("code",null,"Use Modern Tree-Sitter Implementation",-1),m={href:"https://tree-sitter.github.io/tree-sitter/",target:"_blank",rel:"noopener noreferrer"},f={href:"https://pulsar-edit.dev/blog/20230601-Daeraxa-JuneUpdate.html#tree-sitter-updates-are-live",target:"_blank",rel:"noopener noreferrer"},b={href:"https://github.com/savetheclocktower",target:"_blank",rel:"noopener noreferrer"},g={href:"https://pulsar-edit.dev/tag/tree-sitter/",target:"_blank",rel:"noopener noreferrer"},w=t("code",null,"Use Legacy Tree-sitter Implementation",-1),_={href:"https://pulsar-edit.dev/community.html",target:"_blank",rel:"noopener noreferrer"},v=t("code",null,"symbols-view",-1),y=t("code",null,"symbols-view",-1),k=t("code",null,"symbol-provider-tree-sitter",-1),x={href:"https://github.com/pulsar-edit/pulsar/pull/829",target:"_blank",rel:"noopener noreferrer"},T={href:"https://github.com/claytonrcarter",target:"_blank",rel:"noopener noreferrer"},P=t("p",null,[e("On to some bug fixes. We had an issue reported about the "),t("code",null,"github"),e(" package that showed a problem with the rendering of the diff view display. This seems to have been due to a code path in our TextEditor component that would sometimes try to perform measurements on things that weren't necessarily visible. The fix here, simple as it seems, was to defer those measurements until we could be sure the editor was visible.")],-1),I=t("p",null,[e("Next, we have a fix to the "),t("code",null,"find-and-replace"),e(" package caused by an odd interaction between the package and the "),t("code",null,"Preserve Case During Replace"),e(" option, causing an error to appear when using empty strings as input (as an empty string cannot be capitalized).")],-1),E=t("code",null,"UI",-1),j={href:"https://github.com/pulsar-edit/pulsar/pull/850",target:"_blank",rel:"noopener noreferrer"},A=l('

    And that is all we have for you right now. We hope you enjoy this new release and here is to another 12 months of Pulsar!

    Until next time, happy coding, and see you amongst the stars!


    Pulsar

    ',6),D={href:"https://github.com/pulsar-edit/pulsar/pull/852",target:"_blank",rel:"noopener noreferrer"},F=t("code",null,"useExperimentalModernTreeSitter",-1),M={href:"https://github.com/pulsar-edit/pulsar/pull/855",target:"_blank",rel:"noopener noreferrer"},S={href:"https://github.com/pulsar-edit/pulsar/pull/854",target:"_blank",rel:"noopener noreferrer"},W=t("code",null,"symbols-view",-1),N={href:"https://github.com/pulsar-edit/pulsar/pull/829",target:"_blank",rel:"noopener noreferrer"},V={href:"https://github.com/pulsar-edit/pulsar/pull/850",target:"_blank",rel:"noopener noreferrer"},C=t("h3",{id:"find-and-replace",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#find-and-replace","aria-hidden":"true"},"#"),e(" find-and-replace")],-1),H=t("code",null,"capitalize",-1),U={href:"https://github.com/pulsar-edit/pulsar/pull/849",target:"_blank",rel:"noopener noreferrer"},q=t("h3",{id:"symbols-view",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#symbols-view","aria-hidden":"true"},"#"),e(" symbols-view")],-1),B={href:"https://github.com/pulsar-edit/pulsar/pull/864",target:"_blank",rel:"noopener noreferrer"};function J(L,z){const o=h("ExternalLinkIcon");return n(),s("div",null,[t("p",null,[e("Pulsar "),t("a",u,[e("1.113.0"),r(o)]),e(" is available now!")]),i(" more "),c,t("p",null,[e("First up is something we have been mentioning in the last few community update blog posts and otherwise hinting about for a while. Back in Pulsar 1.106.0, released in July last year, we added a new setting called "),p,e(" that would enable a brand new implementation of "),t("a",m,[e("Tree-sitter"),r(o)]),e(" within Pulsar. If you want to know more about the history of this change, then have a look at our detailed "),t("a",f,[e("blog post"),r(o)]),e(" on the topic as well as "),t("a",b,[e("@savetheclocktower"),r(o)]),e("'s fantastic "),t("a",g,[e("blog post series"),r(o)]),e(". A huge thank you to everyone who contributed to this feature by testing it out and submitting bug reports and pull requests, these contributions have led to this moment where we can finally activate it by default. The reason we have done this is in order to eventually move to modern versions of Electron, for which the old tree-sitter implementation is simply not compatible. We have had this as an option while we worked out the most egregious kinks in the system, but we are in a position where we feel it is strong enough to enable by default. If you encounter any significant issues with the new system, there is a new option "),w,e(" that will revert to the old system. It would be great if any problems with the new system could be brought to our attention via any of our "),t("a",_,[e("social channels"),r(o)]),e(" so we can look to fix any issues that have yet to be encountered by us.")]),t("p",null,[e("We have a rather significant update to our "),v,e(' package. This package is used to display symbols within Pulsar (e.g. function definitions) and allows you to navigate your code via those symbols. This package has had a rather major overhaul and now follows the standard "provider/consumer" model as many other Pulsar packages do. In particular, this allows for packages to provide symbols to '),y,e(", such as Tree-sitter grammars, via the new "),k,e(" package. The upshot of this, combined with the new Tree-sitter implementation, is that there will be a much richer and more accurate display of symbols in your project. More information can be found in the "),t("a",x,[e("pull request"),r(o)]),e(" for this change.")]),t("p",null,[e("A new Tree-sitter PHP grammar has been added to Pulsar as part of ongoing Tree-sitter grammar upgrades and improvements, which includes some contributions from community member "),t("a",T,[e("@claytonrcarter"),r(o)]),e(" who had a parser for PHPDoc allowing us to highlight documentation comments in PHP in a similar vein to JSDoc injection JS/TS files.")]),P,I,t("p",null,[e("And last, we have a change to the default behavior of rendering emojis in Markdown as part of the new "),E,e(" API. This issue was discovered due to an unwelcome (but admittedly rather apt) \u{1F621} emoji in the middle of an error message popup. While "),t("a",j,[e("the error"),r(o)]),e(" was rather hilarious, we did decide that it needed to be banished by default for the sake of one of our team members' sanity (and to reduce unnecessary obfuscation of errors, of course).")]),A,t("ul",null,[t("li",null,[e("Fixed: Tree-sitter fixes for December (including a PHP grammar!) "),t("a",D,[e("@savetheclocktower"),r(o)])]),t("li",null,[e("Added: Make "),F,e(" the default... "),t("a",M,[e("@savetheclocktower"),r(o)])]),t("li",null,[e("Fixed: Ensure editor is visible before measuring block decorations "),t("a",S,[e("@savetheclocktower"),r(o)])]),t("li",null,[e("Added: Overhaul "),W,e(),t("a",N,[e("@savetheclocktower"),r(o)])]),t("li",null,[e("Added: Default to no emoji when rendering Markdown "),t("a",V,[e("@savetheclocktower"),r(o)])])]),C,t("ul",null,[t("li",null,[e("Fixed: [find-and-replace] Fix "),H,e(" utility "),t("a",U,[e("@savetheclocktower"),r(o)])])]),q,t("ul",null,[t("li",null,[e("Fixed: [symbols-view] Fix issue with returning from a declaration "),t("a",B,[e("@savetheclocktower"),r(o)])])])])}const R=a(d,[["render",J],["__file","20240115-Daeraxa-v1.113.0.html.vue"]]);export{R as default}; diff --git a/assets/20240122-savetheclocktower-modern-tree-sitter-part-6.html.bc065797.js b/assets/20240122-savetheclocktower-modern-tree-sitter-part-6.html.bc065797.js new file mode 100644 index 0000000000..a61ed534c5 --- /dev/null +++ b/assets/20240122-savetheclocktower-modern-tree-sitter-part-6.html.bc065797.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-67f98f2c","path":"/blog/20240122-savetheclocktower-modern-tree-sitter-part-6.html","title":"Modern Tree-sitter, part 6: reinventing symbols-view","lang":"en-US","frontmatter":{"title":"Modern Tree-sitter, part 6: reinventing symbols-view","author":"savetheclocktower","date":"2024-01-22T00:00:00.000Z","category":["dev"],"tag":["modernization","tree-sitter"]},"excerpt":"

    We\u2019ve been telling a series of stories about all the different ways that Tree-sitter can improve the editing experience in Pulsar. Today\u2019s story about symbols-view starts a bit slowly, but it\u2019s got a great ending:\xA0the addition of a major new feature to Pulsar 1.113.

    \\n","headers":[{"level":2,"title":"Background","slug":"background","link":"#background","children":[]},{"level":2,"title":"Refactoring symbols-view","slug":"refactoring-symbols-view","link":"#refactoring-symbols-view","children":[{"level":3,"title":"A crash course in services","slug":"a-crash-course-in-services","link":"#a-crash-course-in-services","children":[]},{"level":3,"title":"A built-in example","slug":"a-built-in-example","link":"#a-built-in-example","children":[]},{"level":3,"title":"How will it work?","slug":"how-will-it-work","link":"#how-will-it-work","children":[]},{"level":3,"title":"Project symbols","slug":"project-symbols","link":"#project-symbols","children":[]},{"level":3,"title":"Go to declaration","slug":"go-to-declaration","link":"#go-to-declaration","children":[]},{"level":3,"title":"Language servers","slug":"language-servers","link":"#language-servers","children":[]},{"level":3,"title":"Anyway, back to symbols-view","slug":"anyway-back-to-symbols-view","link":"#anyway-back-to-symbols-view","children":[]}]},{"level":2,"title":"Shipping now","slug":"shipping-now","link":"#shipping-now","children":[]},{"level":2,"title":"Conclusion","slug":"conclusion","link":"#conclusion","children":[]}],"git":{"updatedTime":1705976159000,"contributors":[{"name":"Andrew Dupont","email":"github@andrewdupont.net","commits":3}]},"readingTime":{"minutes":11.98,"words":3594},"filePathRelative":"blog/20240122-savetheclocktower-modern-tree-sitter-part-6.md","localizedDate":"January 22, 2024"}');export{e as data}; diff --git a/assets/20240122-savetheclocktower-modern-tree-sitter-part-6.html.da288708.js b/assets/20240122-savetheclocktower-modern-tree-sitter-part-6.html.da288708.js new file mode 100644 index 0000000000..216cb22241 --- /dev/null +++ b/assets/20240122-savetheclocktower-modern-tree-sitter-part-6.html.da288708.js @@ -0,0 +1 @@ +import{_ as l,o as c,c as d,e as h,a as t,b as e,d as o,w as i,f as a,r as n}from"./app.0e1565ce.js";const u="/assets/symbols-view-demo.17f7e949.webm",p="/assets/symbols-view-demo.946869fd.mp4",m="/assets/service-diagram-1.38837fcc.png",g="/assets/service-diagram-2.0af85dad.png",b="/assets/service-diagram-3.c330b93e.png",f="/assets/project-symbols.f73386c6.png",y="/assets/go-to-declaration-example.87adc60b.webm",w="/assets/go-to-declaration-example.62447add.mp4",v="/assets/symbol-provider-bookmarks-example.96a7a05d.png",k="/assets/symbols-view-tree-sitter-demo.16754842.webm",_="/assets/symbols-view-tree-sitter-demo.21174d35.mp4",x="/assets/symbols-view-json.951c60f0.png",T={},I=t("p",null,[e("We\u2019ve been telling a series of stories about all the different ways that Tree-sitter can improve the editing experience in Pulsar. Today\u2019s story about "),t("code",null,"symbols-view"),e(" starts a bit slowly, but it\u2019s got a great ending:\xA0the addition of a major new feature to Pulsar 1.113.")],-1),j=t("h2",{id:"background",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#background","aria-hidden":"true"},"#"),e(" Background")],-1),S={href:"https://github.com/mauricioszabo/",target:"_blank",rel:"noopener noreferrer"},A=t("blockquote",null,[t("p",null,[e("Currently, \u201Cdefinitions\u201D are implemented using CTags in "),t("code",null,"symbols-view"),e(". What do you think about transforming this into a \u201Cservice\u201D, like "),t("code",null,"pulsar.definitions"),e(" or "),t("code",null,"editor.definitions"),e("? That way, the tokenizer can \u201Cpush\u201D definitions into this service, and "),t("code",null,"symbols-view"),e(" can query for the definitions on the current file.")])],-1),D=t("p",null,"I\u2019ll explain what he\u2019s talking about.",-1),C={href:"https://github.com/pulsar-edit/symbols-view/blob/master/README.md",target:"_blank",rel:"noopener noreferrer"},P=t("code",null,"symbols-view",-1),B=t("code",null,"render",-1),E=a('

    Choosing the render symbol in your symbols list will move the editor to the line where render is defined.

    ',3),R=t("code",null,"render",-1),L=t("code",null,"ctags",-1),q={href:"https://ctags.sourceforge.net/",target:"_blank",rel:"noopener noreferrer"},F=t("p",null,[e("(What Maur\xEDcio calls \u201Cdefinitions\u201D is what "),t("code",null,"symbols-view"),e(" calls \u201Csymbols,\u201D and what "),t("code",null,"ctags"),e(" calls, well, \u201Ctags.\u201D For simplicity I\u2019ll use the term \u201Csymbol\u201D just to align with what Pulsar calls it.)")],-1),H=t("code",null,"ctags",-1),M=t("em",null,"plenty",-1),O=t("em",null,"at all",-1),W={href:"https://github.com/pulsar-edit/symbols-view/blob/master/lib/ctags-config",target:"_blank",rel:"noopener noreferrer"},Y=t("code",null,"symbols-view",-1),N=t("code",null,"ctags",-1),G=t("em",null,"great",-1),J={href:"https://tree-sitter.github.io/tree-sitter/code-navigation-systems",target:"_blank",rel:"noopener noreferrer"},U=t("code",null,"ctags",-1),V=a('

    You might have noticed how GitHub can nowadays give you an outline-like view of a source code file, listing lines where methods are defined. That\u2019s all happening through Tree-sitter. If GitHub can use it for symbol navigation, so can we.

    Refactoring symbols-view

    But to make that happen, we need to change how symbols-view works. All it knows about is ctags! Could we rip all that out and replace it with a Tree-sitter solution? Yes, but in the process we\u2019d be abandoning support for any languages that don\u2019t yet have Tree-sitter parsers.

    A better approach would be to know about both strategies and pick the best one on the fly. So let\u2019s figure out exactly what Maur\xEDcio\u2019s request \u2014 \u201Ctransforming [symbols] into a \u2018service\u2019\u201D \u2014 means.

    A crash course in services

    In Pulsar, services are how packages talk to one another. Suppose I\u2019ve authored package-b and it depends on another package called package-a that someone else has written. I could reach into atom.packages and grab the reference to package-a, but this feels weird for a number of reasons. For one, it incorrectly assumes that package-a has already been activated. It may get activated after package-b \u2014 or else it may never get activated because the user has disabled or uninstalled it.

    But even if package-b were able to find and consume package package-a this way, it\u2019d create a tight coupling between the two. That coupling would break if package-a renamed itself, or if it changed implementation details that package-b was relying on.

    So instead of communicating directly, they can invent a service called foo and use it to communicate. One package defines itself as a provider of service foo, and the other defines itself as a consumer of service foo.

    During startup, Pulsar will activate each package, notice the match, and arrange an introduction as soon as both packages have been activated. The provider will end up returning an object that the consumer can use however it likes; this object is typically some sort of interface with methods that the consumer can call.

    Services thus act as contracts between packages. And they can be versioned, too. If it wants, package-a can provide several different versions of the service at once; this leaves the author free to make changes without breaking packages that consume the older version.

    A built-in example

    This flexibility makes new things possible. Consider a package like autocomplete-plus, the bundled package that provides an autocompletion menu in Pulsar. It doesn\u2019t try to implement the various tactics that can be used to suggest completion candidates; all it does is make the user interface for an autocomplete menu. It then defines an autocomplete.provider service so that other packages can provide completion suggestions. Packages like autocomplete-html, autocomplete-css, and others know how to suggest context-specific completions at the cursor, so they feed that data to autocomplete-plus.

    Service Diagram 1

    We like this approach because it gives users an incredible amount of control. For example, if you don\u2019t like the HTML autocompletion suggestions, you can change autocomplete-html\u2019s configuration, or even disable it entirely. Or you could write your own alternative to autocomplete-html. Or you could even write your own alternative to autocomplete-plus! By registering as a consumer of autocomplete.provider, your replacement package would be able to communicate with packages like autocomplete-html just as easily as autocomplete-plus can.

    Service Diagram 2

    This is the model we need for symbols-view. We now have a second approach for generating symbols that can compete favorably with the ctags strategy. So let\u2019s reinvent symbols-view in the style of autocomplete-plus and make it a consumer of a new service we\u2019ll invent named symbol.provider.

    Service Diagram 3

    The built-in ctags provider can be spun off into a package called symbol-provider-ctags, and our new Tree-sitter\u2013based approach can be in a package called symbol-provider-tree-sitter. These packages can provide the symbol.provider service for symbols-view to consume.

    How will it work?

    ',19),z=t("code",null,"highlights.scm",-1),X=t("code",null,"tags.scm",-1),K=a('

    When a user presses Ctrl+R / Cmd+R, we can run a query against the current buffer. Any node that is captured as @name in a tags.scm file can be represented as a symbol. Often the node will be contained in a larger capture called (for example) @definition.function; we can detect that and infer that the text captured by @name refers to a function.

    The information we get is not only richer than what ctags can provide, but also more accurate, since we\u2019re querying against the current buffer text. Even if the file hasn\u2019t been saved recently. Even if it hasn\u2019t been saved at all!

    Now, we can only do this when the file in question is using a Tree-sitter grammar, so it\u2019s not a universal solution. But we can prefer a Tree-sitter symbol provider where it\u2019s available, and fall back to our ctags provider where it isn\u2019t.

    Project symbols

    Another thing that symbols-view has long supported \u2014 theoretically \u2014 is project-based symbol navigation, allowing you to search for and jump to symbols in other files.

    Project Symbols example

    It\u2019s been able to do this because ctags can read project-wide symbol metadata \u2014 a genuine upside it has over some other approaches. But this feature only works if the user has generated a special file called a \u201Ctags file\u201D for their project. Pulsar itself can\u2019t generate this file on its own because it doesn\u2019t know which files it should crawl to find symbols (imagine if it tried to crawl your entire node_modules folder!), so the ctags strategy requires the user to regenerate that file on a regular basis.

    For now, our Tree-sitter symbol provider can only suggest symbols in the current file. If you activate Toggle Project Symbols via Ctrl+Shift+R / Cmd+Shift+R, it won\u2019t even volunteer for the job. Using Tree-sitter to list the symbols in an open buffer is very fast precisely because the buffer is open; we\u2019ve already paid the startup cost of the initial parse. But there\u2019s no way Tree-sitter could parse all of a project\u2019s files in a similar amount of time. If we want project-wide symbol search we\u2019ll have to look elsewhere.

    Go to declaration

    \u201CWho cares,\u201D you may think. And I\u2019ll admit I don\u2019t attempt a project-wide symbol search very often. But there\u2019s a related feature I\u2019m pretty sure you\u2019ll like.

    symbols-view defines a Go To Declaration command. It\u2019ll search the project for a symbol matching the word under the cursor. If there\u2019s one result, it\u2019ll get opened automatically; if there\u2019s more than one, it offers up the options in a list for you to choose. And when you\u2019re done, there\u2019s a corresponding Return From Declaration command that takes you back to the place you just were.

    Dive into a definition with Ctrl+Alt+Down / Cmd+Alt+Down, then return to the surface with Ctrl+Alt+Up / Cmd+Alt+Up:

    Here I\u2019ve demonstrated it on a TypeScript type, but it\u2019ll work on functions and classes and other types of things, too.

    Did you know this feature existed? I didn\u2019t. It\u2019s been available to you this whole time if you\u2019ve had a tags file to supply project-wide symbols, the way nobody does. But with a refactored symbols-view, another candidate for supplying these symbols enters the arena: a language server.

    Language servers

    ',16),Q={href:"https://en.wikipedia.org/wiki/Language_Server_Protocol",target:"_blank",rel:"noopener noreferrer"},Z=t("p",null,[e("There are a handful of Pulsar packages named like "),t("code",null,"ide-x"),e(", where "),t("code",null,"x"),e(" is the name of a language. Several of them were even originally developed by the Atom team. For now I\u2019ll call them "),t("strong",null,"IDE backend packages"),e(".")],-1),$=t("p",null,[e("What these packages have in common is that they all run something called a "),t("em",null,"language server"),e(" under the hood. A language server is designed to be a brain for a few dozen common features you\u2019d want from your code editor: autocompletion, code linting, refactor support, and the like. A single language server typically knows how to do these tasks for one specific language or framework.")],-1),ee={href:"https://www.npmjs.com/package/typescript-language-server",target:"_blank",rel:"noopener noreferrer"},te=t("code",null,"symbols-view",-1),oe={href:"https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_documentSymbol",target:"_blank",rel:"noopener noreferrer"},se=t("code",null,"textDocument/documentSymbol",-1),ae={href:"https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspace_symbol",target:"_blank",rel:"noopener noreferrer"},re=t("code",null,"workspace/symbol",-1),ie={href:"https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_definition",target:"_blank",rel:"noopener noreferrer"},ne=t("code",null,"textDocument/definition",-1),le=t("p",null,[e("But here\u2019s the bad news: since the "),t("code",null,"symbol.provider"),e(" service has only just been invented, those IDE backend packages need updates before they can be used for symbol navigation.")],-1),ce=t("code",null,"ide-typescript",-1),de={href:"https://web.pulsar-edit.dev/packages/pulsar-ide-typescript-alpha",target:"_blank",rel:"noopener noreferrer"},he=t("code",null,"pulsar-ide-typescript-alpha",-1),ue=t("code",null,"ide-typescript",-1),pe=a('

    And it might take a few version bumps on dependencies, but most other IDE backend packages can also be updated to take advantage of these features.

    Anyway, back to symbols-view

    Unlike autocomplete-plus, which aggregates suggestions from multiple providers and shows all of them to the user, symbols-view is mainly interested in choosing the best provider for the job. There\u2019s little point in aggregating across a language server and Tree-sitter and ctags, since they\u2019re largely going to be offering the same list of symbols with varying degrees of richness, and you\u2019d be pretty annoyed if Pulsar offered you three different list entries for the same function. Inside symbols-view they\u2019re called \u201Cexclusive\u201D providers because only one of them will be picked for the job.

    But I wanted to leave the door open for some creative and unexpected usages, so symbols-view also has a concept of \u201Csupplemental\u201D providers. A provider that marks itself as supplemental is saying it\u2019d like to contribute symbols that would probably not already be in an exclusive provider\u2019s list. You may be wondering what kinds of symbols would fit the bill, so let me give you an example\u2026

    Did you know you can bookmark lines in a buffer? Try it out: right-click on any line of your editor and select Toggle Bookmark. The built-in bookmarks package keeps track of them and will also let you navigate between them via F2 and Shift+F2.

    ',5),me={href:"https://web.pulsar-edit.dev/packages/symbol-provider-bookmarks",target:"_blank",rel:"noopener noreferrer"},ge=t("code",null,"symbol-provider-bookmarks",-1),be=t("code",null,"symbols-view",-1),fe=t("p",null,[t("img",{src:v,alt:"symbol-provider-bookmarks example"})],-1),ye={href:"https://web.pulsar-edit.dev/packages/symbol-provider-bookmarks",target:"_blank",rel:"noopener noreferrer"},we=a('

    Shipping now

    I\u2019ve had most of this article written for months, but I decided to wait to publish it until we could show this stuff off. That time is now.

    Pulsar 1.113 makes two major changes that will vastly improve the quality of the symbol searching you might already be accustomed to:

    1. The new version of symbols-view is now in place. It will offer you ctags-based symbols in grammars that don\u2019t use Tree-sitter, but it will prefer Tree-sitter\u2013supplied symbols in most grammars. If you truly don\u2019t like change, you can disable the symbol-provider-tree-sitter package and just rely on symbol-provider-ctags, or else you can configure symbols-view to prefer some providers over others.

      But I\u2019m betting you\u2019ll want to keep using the symbols provided by Tree-sitter, because\u2026

    2. As you may have heard, modern Tree-sitter grammars are now the default! The system that we shipped in experimental fashion back in Pulsar 1.106 is now ready for prime time. For now, you can opt back into legacy Tree-sitter with the new core.useLegacyTreeSitter setting \u2014 but not for long, because the legacy system will be dropped when we\u2019re finally able to migrate to a newer version of Electron.

    ',4),ve=t("kbd",null,"Ctrl+R",-1),ke=t("kbd",null,"Cmd+R",-1),_e=t("em",null,"across the board",-1),xe={href:"https://github.com/pulsar-edit/pulsar/issues",target:"_blank",rel:"noopener noreferrer"},Te=a('

    How does this actually improve the symbols-view experience? Let\u2019s see what our original example looks like with a Tree-sitter symbol provider:

    The richness of the metadata we get from these sources has allowed us to enhance the symbols-view UI, too! You\u2019ll be shown the \u201Ckind\u201D of thing that a symbol is \u2014\xA0class, function, constant, et cetera. In many cases, these kinds will be illustrated with icons. Visit the package settings page for symbols-view to explore the possibilities.

    And there are even a few killer new features. Open a symbols list on a JSON file and marvel at the entries you see:

    symbols-view JSON example

    ',5),Ie=t("em",null,"entire key path",-1),je={href:"https://github.com/pulsar-edit/pulsar/blob/master/packages/symbol-provider-tree-sitter/README.md#advanced-features",target:"_blank",rel:"noopener noreferrer"},Se=t("code",null,"symbol-provider-tree-sitter",-1),Ae={href:"https://web.pulsar-edit.dev/packages/pulsar-ide-typescript-alpha",target:"_blank",rel:"noopener noreferrer"},De=t("code",null,"pulsar-ide-typescript-alpha",-1),Ce=t("em",null,"and",-1),Pe=t("code",null,"ide-x",-1),Be={href:"https://github.com/orgs/pulsar-edit/discussions",target:"_blank",rel:"noopener noreferrer"},Ee={href:"https://discord.gg/7aEbB9dGRT",target:"_blank",rel:"noopener noreferrer"},Re={href:"https://pulsar-edit.dev/community.html",target:"_blank",rel:"noopener noreferrer"},Le=t("h2",{id:"conclusion",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#conclusion","aria-hidden":"true"},"#"),e(" Conclusion")],-1),qe=t("em",null,"yet another",-1),Fe=t("em",null,"even better",-1),He=t("p",null,"Integrating Tree-sitter has been a difficult project. I started working on it in earnest in February of 2023; it shipped in June behind an experimental flag; and it\u2019s finally the default grammar type in January of 2024.",-1),Me=t("p",null,"Our Tree-sitter series is nearing an end, but there\u2019s one more thing to cover: the challenges. Could it have been easier? Can Tree-sitter overcome its pain points and drawbacks? We\u2019ll talk about it next time.",-1);function Oe(We,Ye){const s=n("ExternalLinkIcon"),r=n("RouterLink");return c(),d("div",null,[I,h(" more "),j,t("p",null,[e("Back in March, "),t("a",S,[e("@mauricioszabo"),o(s)]),e(" gave me an assignment:")]),A,D,t("p",null,[e("You might already use the "),t("a",C,[P,e(" package"),o(s)]),e(" to navigate to important parts of your source code files. For instance, if you want to jump to the definition of "),B,e(" in a given file, you can")]),E,t("p",null,[e("This is a time-saving feature. But how does it work? How does Pulsar know which items to put in the list? How does it know where your "),R,e(" method is defined? You might be surprised: it uses an ancient program called "),L,e(" \u2014 specifically a fork called "),t("a",q,[e("Exuberant Ctags"),o(s)]),e(".")]),F,t("p",null,[H,e(" works well enough that you might never notice its drawbacks, but it\u2019s got "),M,e(" of drawbacks. It reads files from disk, so it can return inaccurate results if you use it on a file that has been modified since its last save. For the same reason, it doesn\u2019t work "),O,e(" on new files that haven\u2019t yet been saved. And it needs "),t("a",W,[e("special configuration"),o(s)]),e(" for each language it supports \u2014 meaning that, even after you\u2019ve written a Pulsar grammar for your newly-invented Language X, you won\u2019t get any symbol-based navigation unless you modify the "),Y,e(" package itself and tell "),N,e(" how to find your language\u2019s symbols.")]),t("p",null,[e("I know you\u2019re probably tired of hearing me say \u201CTree-sitter would be "),G,e(" for this task!\u201D \u2014 but "),t("a",J,[e("code navigation systems"),o(s)]),e(" really are in its wheelhouse. The trees we\u2019re already using to highlight code and do other useful tasks can be queried to supply symbols much more easily than via "),U,e(". And many parsers even come bundled with a query file that does the work of identifying the symbols we\u2019re interested in.")]),V,t("p",null,[e("I\u2019ve "),o(r,{to:"/blog/20230927-savetheclocktower-modern-tree-sitter-part-2.html"},{default:i(()=>[e("talked about why")]),_:1}),e(" Pulsar chose not to leverage the built-in "),z,e(" query files that exist for most Tree-sitter parsers: we needed richer information than they could provide. Luckily, that\u2019s not true for other kinds of files! Many parsers also provide "),X,e(" query files, and they\u2019re easy for us to consume as-is.")]),K,t("p",null,[e("I hesitate to mention "),t("a",Q,[e("language servers"),o(s)]),e(" merely in passing, because they\u2019re a deep enough topic to require their own multi-part blog post series. But let me give it a shot.")]),Z,$,t("p",null,[e("Language servers are exciting because they make it easier for weirdos like us to use editors other than the market leader. Instead of having to write all those features from scratch for, say, TypeScript, an upstart code editor could instead communicate with "),t("a",ee,[e("typescript-language-server"),o(s)]),e(" and write some glue code to wire up the language server\u2019s features to the features of the editor.")]),t("p",null,[e("The good news is that the language server specification includes several actions that are relevant to "),te,e(": "),t("a",oe,[se,o(s)]),e(" for same-file symbols, "),t("a",ae,[re,o(s)]),e(" for project-wide symbols, and even "),t("a",ie,[ne,o(s)]),e(" for finding where a symbol is defined. Some IDE backend packages already have \u201Cbrains\u201D capable of doing these tasks!")]),le,t("p",null,[e("I\u2019ve started to do a bit of that work. Inspired by "),ce,e(" \u2014\xA0but mainly starting fresh \u2014\xA0I\u2019ve been working on a package currently called "),t("a",de,[he,o(s)]),e(" that aims to be its drop-in replacement. It should be able to do everything that "),ue,e(" can already do, but it will also be able to offer project-wide symbol search and go-to-declaration functionality.")]),pe,t("p",null,[e("Anyway, to illustrate the idea of a supplemental provider, I wrote one: "),t("a",me,[ge,o(s)]),e(" will turn each of your bookmarks into a symbol, then display them in the "),be,e(" UI alongside your main provider\u2019s symbols, using the text of the bookmarked line as the symbol name.")]),fe,t("p",null,[e("This one\u2019s not bundled with Pulsar, so "),t("a",ye,[e("grab it from the package registry"),o(s)]),e(" if it sounds interesting.")]),we,t("p",null,[e("Because common languages like JavaScript, Python, Ruby, and many others have full-featured modern Tree-sitter grammars, they will also be using our new Tree-sitter symbol provider for "),ve,e(" / "),ke,e(". That means the symbol results should be better "),_e,e(" \u2014 more accurate and more comprehensive. (If it seems worse, please "),t("a",xe,[e("file a bug"),o(s)]),e(".)")]),Te,t("p",null,[e("The "),Ie,e(" is now the name of the symbol! The same sorts of query and predicate tricks we\u2019ve seen in previous installments in this series can be used for awesome features like this. "),t("a",je,[e("The "),Se,e(" README"),o(s)]),e(" has more details.")]),t("p",null,[e("And it\u2019s early days for "),t("a",Ae,[De,o(s)]),e(", but I\u2019ve been using it for a few months as a symbol provider (and a go-to-declaration provider!) on TypeScript "),Ce,e(" JavaScript projects. Feel free to give it a shot yourself. (And if you\u2019re interested in bringing one of the other "),Pe,e(" packages into the year 2024, please do broach the topic on "),t("a",Be,[e("GitHub Discussions"),o(s)]),e(", "),t("a",Ee,[e("Discord"),o(s)]),e(", or "),t("a",Re,[e("one of our other communities"),o(s)]),e(".)")]),Le,t("p",null,[e("After overhauling Pulsar\u2019s "),o(r,{to:"/blog/20231013-savetheclocktower-modern-tree-sitter-part-3.html"},{default:i(()=>[e("syntax highlighting")]),_:1}),e(", "),o(r,{to:"/blog/20231031-savetheclocktower-modern-tree-sitter-part-4.html"},{default:i(()=>[e("indentation, code folding")]),_:1}),e(", and "),o(r,{to:"/blog/20231110-savetheclocktower-modern-tree-sitter-part-5.html"},{default:i(()=>[e("language injections")]),_:1}),e(", we\u2019ve found "),qe,e(" way that Tree-sitter can improve our existing editor experience. But in this case, there\u2019s an "),Fe,e(" improvement just around the corner: IDE backend packages and language servers. I\u2019ll be sure to go into more detail on the Pulsar IDE experience in future posts.")]),He,Me])}const Ge=l(T,[["render",Oe],["__file","20240122-savetheclocktower-modern-tree-sitter-part-6.html.vue"]]);export{Ge as default}; diff --git a/assets/20240124-mauricioszabo-the-quest-for-electron-lts.html.a8c82ab1.js b/assets/20240124-mauricioszabo-the-quest-for-electron-lts.html.a8c82ab1.js new file mode 100644 index 0000000000..baefbbdb98 --- /dev/null +++ b/assets/20240124-mauricioszabo-the-quest-for-electron-lts.html.a8c82ab1.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-1181f0fe","path":"/blog/20240124-mauricioszabo-the-quest-for-electron-lts.html","title":"The quest for Electron LTS","lang":"en-US","frontmatter":{"title":"The quest for Electron LTS","author":"mauricioszabo","date":"2024-01-24T00:00:00.000Z","category":["dev"],"tag":["modernization","electron"]},"excerpt":"","headers":[{"level":2,"title":"A little bit of history","slug":"a-little-bit-of-history","link":"#a-little-bit-of-history","children":[]},{"level":2,"title":"The quest for Electron 13","slug":"the-quest-for-electron-13","link":"#the-quest-for-electron-13","children":[]},{"level":2,"title":"Superstring, and Memory Manipulation","slug":"superstring-and-memory-manipulation","link":"#superstring-and-memory-manipulation","children":[]},{"level":2,"title":"More things break","slug":"more-things-break","link":"#more-things-break","children":[]},{"level":2,"title":"Things work fine! (juuuust kidding!)","slug":"things-work-fine-juuuust-kidding","link":"#things-work-fine-juuuust-kidding","children":[]},{"level":2,"title":"The quest is (almost) over","slug":"the-quest-is-almost-over","link":"#the-quest-is-almost-over","children":[]}],"git":{"updatedTime":1706488398000,"contributors":[{"name":"Maur\xEDcio Szabo","email":"mauricio.szabo@gmail.com","commits":1},{"name":"Maur\xEDcio Szabo","email":"mauricioszabo@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":5.21,"words":1564},"filePathRelative":"blog/20240124-mauricioszabo-the-quest-for-electron-lts.md","localizedDate":"January 24, 2024"}');export{e as data}; diff --git a/assets/20240124-mauricioszabo-the-quest-for-electron-lts.html.e3a101c2.js b/assets/20240124-mauricioszabo-the-quest-for-electron-lts.html.e3a101c2.js new file mode 100644 index 0000000000..bf24a6ca4f --- /dev/null +++ b/assets/20240124-mauricioszabo-the-quest-for-electron-lts.html.e3a101c2.js @@ -0,0 +1 @@ +import{_ as r,o as s,c as i,a as t,b as e,d as a,f as n,r as l}from"./app.0e1565ce.js";const h={},d=n('

    In the beginning, Atom appeared. It created an API to make packages, but together with this API, it also allowed authors to use web APIs together with node.js packages, modules (including "native modules" - more on that later) and, finally, a special API that was used to communicate between the "main module" and the "browser part".

    That last part, eventually, split from Atom and became Electron. And for a while, the Atom development was tied to the Electron one, meaning that an update on Atom usually meant an update on Electron, and vice-versa.

    Unfortunately, that wasn't the case for a long time...

    A little bit of history

    The latest version of Atom was pulled because of some certificate issues; that means the real latest stable version of Atom was 1.60.0.

    This version of Atom used Electron 9.3.5. For comparison, at the time, Electron was at version 22, but the "master" branch of Atom (and atom-community) was at version 11.5.0.

    The first thing we did was to find the greatest version that would give us some kind of benefit, but also that we could make work. Luckily for us, the only breaking change on Electron 12 was that some crash analytics changed, and considering we would not use that anymore (because we decided to remove all telemetry) we decided to bump 12.2.3 - the version we're at today.

    This might not seem that much of a change, but Electron 12 gave us native macOS ARM binaries, and allowed us to use Playwright to test the editor - enabling what we called "visual tests" on our CI (the actual unit and integration tests from Atom, at the time, were not running reliably in our CI, so these visual tests allowed us to have some feedback if we broke anything).

    The quest for Electron 13

    The next step was to bump to Electron 13. This...

    ...crashed the editor...

    ...really bad.

    ',12),u={href:"https://www.electronjs.org/docs/latest/tutorial/application-debugging",target:"_blank",rel:"noopener noreferrer"},c=t("code",null,"require",-1),m=n('

    What was crashing was a library called superstring.

    Superstring, and Memory Manipulation

    Superstring is a library that handles text manipulation. That basically translates to editing text - so, basically, the most important library that we have.

    This library is also a "native library" - meaning that it's written on a lower-level language (C++, in this case) and that it integrates with the Node.JS code via some bindings to the runtime. These bindings basically change all the time on Node.JS, but that's not the worst part.

    The worst part is that they change even more agressively on Electron. Because in C, and C++, you manipulate memory manually, and because we're now in a "browser environment" (Electron) some changes are being pushed to avoid corrupting memory, accessing places that we're not supposed to access, and to make these libraries "play nice" in multiple contexts - meaning, one instance of the "browser" not corrupting the memory of another instance (in our case, this translates to one editor window not corrupting another). Superstring was not ready to be used in the state it was, because it manually copied memory from places.

    Fortunately, we found a fix - there was an experimental, and incomplete, version of Superstring that compiled to Web Assembly - that means a new format that every browser should run without any problem, and that's how we ported Superstring to run newer Electron.... for a while.

    More things break

    Our syntax highlighter tree-sitter also didn't work on newer Electron version - on Electron 14, it stopped working completely. So, for the time being, we disabled it, and kept trying to port things, fix stuff, etc. Fortunately, all other libraries were ready to be used in newer Electron contexts, and we just had to fix some "synchronous" calls to be "asynchronous" and that worked.

    I wasn't that worried about disabling tree-sitter because, at the time, we were already trying to make a modern tree-sitter implementation, so I knew that sometime in the future, we could re-enable tree-sitter and that it would work (spoiler alert: it DID!).

    Then I got greedy, and decided to make Pulsar work in the newest Electron at the time - Electron 23.

    Things work fine! (juuuust kidding!)

    Well, if you have been following the tone of this post... of course you know what happens next, right?

    Electron 21 enabled something called V8 memory cage. I honestly don't fully understand that that means, but what ended up happening is that another native library was crashing the editor - and the name of the library is Oniguruma.

    If you're not aware of what this is, it's a regular expression library. If you also don't know what that is, basically is the library used in TextMate grammars to do syntax highlighting.

    So, summarizing: Atom (and by definition, Pulsar) had two ways of highlighting code (coloring the keywords, functions, classes, methods, etc): one is called tree-sitter, that breaks in Electron 14, and another callled TextMate, that breaks in Electron 21...

    ...great.

    A code editor without syntax highlighting is probably not a good idea at all; we could not disable it and basically offer no syntax highlighting library. So, I started a quest to migrate TextMate grammars to something different - and what better place to start than VSCode?

    ',17),p={href:"https://github.com/microsoft/vscode-oniguruma",target:"_blank",rel:"noopener noreferrer"},g={href:"https://github.com/atom/first-mate",target:"_blank",rel:"noopener noreferrer"},w=t("code",null,"second-mate",-1),b=t("code",null,"check-mate",-1),f=t("code",null,"second-mate",-1),y={href:"https://github.com/savetheclocktower",target:"_blank",rel:"noopener noreferrer"},k=t("h2",{id:"the-quest-is-almost-over",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#the-quest-is-almost-over","aria-hidden":"true"},"#"),e(" The quest is (almost) over")],-1),v=t("p",null,[e("I know it seems hard to believe, but after all this work, we were able to bump to Electron 24, then 25, then 26, then 27, and finally, 28 (which "),t("em",null,"did break"),e(" more stuff but luckily that was easy to fix). I'm even writing this post using an experimental build on Electron 28!")],-1),_={href:"https://github.com/bongnv",target:"_blank",rel:"noopener noreferrer"},x=t("p",null,[e("The last piece of code that we needed to fix was the "),t("code",null,"github"),e(" package, but as soon as that was fixed, it was only a matter of completely removing the old implementation of tree-sitter, activating the new one by default, and test, experiment, and do more work.")],-1),q=t("em",null,"a lot",-1),A=t("code",null,"node-pty",-1),E={href:"https://github.com/Spiker985/x-terminal-reloaded/pull/41",target:"_blank",rel:"noopener noreferrer"},I={href:"https://github.com/nteract/hydrogen",target:"_blank",rel:"noopener noreferrer"},T=t("em",null,"very popular package",-1),S=t("p",null,"But for now, we're studying a way to offer the newer Electron branch as an additional download option in our page, so that authors can check if their packages work on this new version. And hopefully, we can get an up-to-date editor, at last!",-1);function M(P,C){const o=l("ExternalLinkIcon");return s(),i("div",null,[d,t("p",null,[e('Trying to open Pulsar in Electron 13 gave us the error "The renderer process crashed". There was no stacktrace, and the '),t("a",u,[e("documentation linked on how to debug this error"),a(o)]),e(" didn't help at all. There were some hidden tutorials to enable CrashReport, then dumping the report somewhere, use a version with debug symbols or download them from GitHub. But honestly, none of this actually pointed to the actual problem, so we did the worst thing that we could think of: we patched "),c,e(" to log what was being required, and hoped for the best.")]),m,t("p",null,[e("It turns out that VSCode uses a WASM version of Oniguruma - that they called "),t("a",p,[e("vscode-oniguruma"),a(o)]),e(" - and it's MIT-licensed too, which is compatible with Pulsar's license. And luckily for us, Pulsar's highlighter (again, inherited from Atom) is on its own separate library, well-tested, called "),t("a",g,[e("first-mate"),a(o)]),e(". So, I did a wordplay and called the new library "),w,e(" (and up to this day, I still reget not calling it "),b,e(" to be honest) that uses this new WASM version. After fixing some infinite loops and other edge-cases that were not fully tested on the original project, I plugged "),f,e(" in the editor, and as unreal that this might sound, it worked!")]),t("p",null,[e("Well, kinda - "),t("a",y,[e("@savetheclocktower"),a(o)]),e(" found that our tests were constantly failing, and found that the reason is that the WASM version doesn't garbage collect the regexes, so it was basically re-creating them over and over again. It is an edge-case that was never going to happen in normal usage, but still it was worth a fix, and then our tests were back to normal... after, of course, we fixed some other issue with macOS on ARM processors.")]),k,v,t("p",null,[e("Also, thanks to the amazing work of "),t("a",_,[e("@bongnv"),a(o)]),e(", we are back to a native version of Superstring (that means we won't take a performance hit from the WASM version - it was, unfortunately, really slow for big files, like the ones I had to work with more than 10,000 lines). This new version still lacks some APIs, but fortunately we don't need them - by patching the specific core packages that used these undocumented, weird APIs we were able to avoid having to implement them.")]),x,t("p",null,[e("There are still things that need to be done - native packages change "),q,e(" in the newer Electron, which translates to some packages simply refusing to load (and the risk of crashing or locking the editor) - for example, no terminal package works, because they all depend on "),A,e(" and that is not supported (luckily, again, for us, "),t("a",E,[e("we have a version of x-terminal-reloaded"),a(o)]),e(" that works); it also breaks "),t("a",I,[e("Hydrogen"),a(o)]),e(", a "),T,e(" that uses ZeroMQ to communicate with Jupyter notebooks (that currently simply doesn't work on the newer Electron branches).")]),S])}const j=r(h,[["render",M],["__file","20240124-mauricioszabo-the-quest-for-electron-lts.html.vue"]]);export{j as default}; diff --git a/assets/20240201-Daeraxa-FebruaryUpdate.html.5a1ade21.js b/assets/20240201-Daeraxa-FebruaryUpdate.html.5a1ade21.js new file mode 100644 index 0000000000..20a6cfbfa5 --- /dev/null +++ b/assets/20240201-Daeraxa-FebruaryUpdate.html.5a1ade21.js @@ -0,0 +1 @@ +const e=JSON.parse(`{"key":"v-703ed5aa","path":"/blog/20240201-Daeraxa-FebruaryUpdate.html","title":"Community Update","lang":"en-US","frontmatter":{"title":"Community Update","author":"Daeraxa","date":"2024-02-01T00:00:00.000Z","category":["news","log"],"tag":["update"]},"excerpt":"

    Here it comes sashaying into your feeds, it's the Fab-ruary community update!

    \\n","headers":[{"level":2,"title":"Update on the new Tree-sitter implementation","slug":"update-on-the-new-tree-sitter-implementation","link":"#update-on-the-new-tree-sitter-implementation","children":[]},{"level":2,"title":"New blog post about our quest for Electron stable","slug":"new-blog-post-about-our-quest-for-electron-stable","link":"#new-blog-post-about-our-quest-for-electron-stable","children":[]},{"level":2,"title":"New PPR filter options","slug":"new-ppr-filter-options","link":"#new-ppr-filter-options","children":[]},{"level":2,"title":"Major update to our OpenAPI documentation for the PPR","slug":"major-update-to-our-openapi-documentation-for-the-ppr","link":"#major-update-to-our-openapi-documentation-for-the-ppr","children":[]},{"level":2,"title":"Community Spotlight","slug":"community-spotlight","link":"#community-spotlight","children":[]}],"git":{"updatedTime":1706850199000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":3}]},"readingTime":{"minutes":3.84,"words":1151},"filePathRelative":"blog/20240201-Daeraxa-FebruaryUpdate.md","localizedDate":"February 1, 2024"}`);export{e as data}; diff --git a/assets/20240201-Daeraxa-FebruaryUpdate.html.f8bbd93f.js b/assets/20240201-Daeraxa-FebruaryUpdate.html.f8bbd93f.js new file mode 100644 index 0000000000..b055dc6850 --- /dev/null +++ b/assets/20240201-Daeraxa-FebruaryUpdate.html.f8bbd93f.js @@ -0,0 +1 @@ +import{_ as s}from"./tree-sitter.6a39e323.js";import{_ as n}from"./electron.b5867f08.js";import{_ as i}from"./package.4e7449ae.js";import{_ as l}from"./spotlight.aba19e76.js";import{_ as h,o as d,c as u,e as c,a as e,b as t,d as a,f as r,r as p}from"./app.0e1565ce.js";const f="/assets/openapi-logo.f023afce.png",m={},g=e("p",null,[t("Here it comes sashaying into your feeds, it's the "),e("em",null,"Fab"),t("-ruary community update!")],-1),_=e("h1",{id:"welcome-to-the-february-community-update",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#welcome-to-the-february-community-update","aria-hidden":"true"},"#"),t(" Welcome to the February Community Update!")],-1),b={href:"https://github.com/mauricioszabo",target:"_blank",rel:"noopener noreferrer"},w=r('

    Update on the new Tree-sitter implementation

    [1]

    With the release of Pulsar 1.113 we finally went live with our new Tree-sitter implementation, which had been in the works for a long time. We had tested this as much as we were able to with the resources we had, and we made sure that any issues raised by people enabling the new feature were dealt with. However, with it being enabled by default, we suddenly had significantly more people using this feature, and thus issues we hadn't yet seen were being found.

    ',3),y={href:"https://github.com/savetheclocktower",target:"_blank",rel:"noopener noreferrer"},k={href:"https://github.com/pulsar-edit/pulsar/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+MEGA-ISSUE+label%3Abug",target:"_blank",rel:"noopener noreferrer"},v={href:"https://pulsar-edit.dev/download.html#rolling-release",target:"_blank",rel:"noopener noreferrer"},x={href:"https://github.com/pulsar-edit/pulsar/issues/875",target:"_blank",rel:"noopener noreferrer"},P={href:"https://pulsar-edit.dev/community.html",target:"_blank",rel:"noopener noreferrer"},T=r('

    New blog post about our quest for Electron stable

    [2]

    ',2),I={href:"https://github.com/mauricioszabo",target:"_blank",rel:"noopener noreferrer"},A={href:"https://pulsar-edit.dev/blog/20240124-mauricioszabo-the-quest-for-electron-lts.html",target:"_blank",rel:"noopener noreferrer"},E=e("h2",{id:"new-ppr-filter-options",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#new-ppr-filter-options","aria-hidden":"true"},"#"),t(" New PPR filter options")],-1),j=e("img",{src:i,height:"150"},null,-1),q={href:"https://github.com/confused-Techie",target:"_blank",rel:"noopener noreferrer"},N={href:"https://github.com/savetheclocktower",target:"_blank",rel:"noopener noreferrer"},R=e("p",null,"Examples of this would be as follows:",-1),M=e("code",null,".json",-1),C={href:"https://web.pulsar-edit.dev/packages?fileExtension=json",target:"_blank",rel:"noopener noreferrer"},U=e("strong",null,[t("fileExtension="),e("em",null,"json")],-1),z={href:"https://github.com/bacadra",target:"_blank",rel:"noopener noreferrer"},F={href:"https://web.pulsar-edit.dev/packages/search?owner=bacadra",target:"_blank",rel:"noopener noreferrer"},V=e("strong",null,[t("owner="),e("em",null,"bacadra")],-1),W=e("code",null,"symbol.provider",-1),B={href:"https://web.pulsar-edit.dev/packages?serviceType=provided&service=symbol.provider",target:"_blank",rel:"noopener noreferrer"},O=e("strong",null,[t("serviceType="),e("em",null,"provided")],-1),S=e("strong",null,[t("service="),e("em",null,"symbol.provider")],-1),H=r('

    Currently, these filters are only available via the API directly, so they do not feature within Pulsar itself or in the PPR website UI, but we are looking to add this functionality in the future to make finding the exact package you want much easier.

    Now if only there was a way to find what all these different endpoints might be and how you could construct even fancier queries... well, if only there was a...

    Major update to our OpenAPI documentation for the PPR

    [3]

    ',4),L={href:"https://api.pulsar-edit.dev/swagger-ui/",target:"_blank",rel:"noopener noreferrer"},D={href:"https://github.com/confused-Techie",target:"_blank",rel:"noopener noreferrer"},G=e("p",null,"Even better, to make sure we don't get into this same situation again with outdated API information, it will now automatically update when changes are made to the backend schema.",-1),J=e("h2",{id:"community-spotlight",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#community-spotlight","aria-hidden":"true"},"#"),t(" Community Spotlight")],-1),K=e("img",{src:l,height:"200"},null,-1),Q={href:"https://github.com/claytonrcarter",target:"_blank",rel:"noopener noreferrer"},X={href:"https://github.com/pulsar-edit/pulsar/pull/860",target:"_blank",rel:"noopener noreferrer"},Y={href:"https://github.com/pulsar-edit/ppm/pull/122",target:"_blank",rel:"noopener noreferrer"},Z=e("code",null,"-f/--force",-1),$=e("code",null,"ppm link",-1),ee=e("p",null,"And a big collective thank you to all those users who have helped us by submitting issues and feedback about the new Tree-sitter implementation in general. We know this might have been unintentionally disruptive to some people, so thank you so much for sticking with us while we fix these issues!",-1),te=e("hr",null,null,-1),oe=e("p",null,"And that brings us to the end of this community update. We have a lot of exciting plans for Pulsar this year, so make sure to keep tuning into the blog for new posts from the Pulsar team!",-1),ae=e("hr",{class:"footnotes-sep"},null,-1),re={class:"footnotes"},se={class:"footnotes-list"},ne={id:"footnote1",class:"footnote-item"},ie={href:"https://tree-sitter.github.io/tree-sitter/",target:"_blank",rel:"noopener noreferrer"},le=e("a",{href:"#footnote-ref1",class:"footnote-backref"},"\u21A9\uFE0E",-1),he={id:"footnote2",class:"footnote-item"},de={href:"https://www.electronjs.org/",target:"_blank",rel:"noopener noreferrer"},ue=e("a",{href:"#footnote-ref2",class:"footnote-backref"},"\u21A9\uFE0E",-1),ce={id:"footnote3",class:"footnote-item"},pe={href:"https://www.openapis.org/",target:"_blank",rel:"noopener noreferrer"},fe=e("a",{href:"#footnote-ref3",class:"footnote-backref"},"\u21A9\uFE0E",-1);function me(ge,_e){const o=p("ExternalLinkIcon");return d(),u("div",null,[g,c(" more "),_,e("p",null,[t("Last month was our biggest update to Pulsar we have had in quite a while, so in this blog we will be addressing some of the issues people have seen and what you can expect in terms of fixes and updates. Outside of that, we have some big changes to the Pulsar Package Registry backend that give (and document) a bunch of new filters and endpoints to the API, as well as a reminder for "),e("a",b,[t("@maur\xEDcio szabo"),a(o)]),t("'s blog post detailing our biggest hurdle: the road to modern versions of Electron.")]),w,e("p",null,[e("a",y,[t("@savetheclocktower"),a(o)]),t(" was quick in starting a number of "),e("a",k,[t('"Mega-issues"'),a(o)]),t(" on GitHub for those languages where issues had been found in order to keep things in one place and make sure we didn't get swamped with duplicate issues.")]),e("p",null,[t("The vast majority of these issues have now already been addressed (or are in process), so if you are seeing any oddities or problems, then you can download one of our "),e("a",v,[t("rolling releases"),a(o)]),t(" which will already have those merged changes included. Otherwise, they will be present in the release version of Pulsar 1.114.")]),e("p",null,[t("If any of these changes are truly breaking for you and you are not in a position to upgrade to a rolling release, you can find instructions on how to do this for that particular language in the relevant "),e("a",x,[t("Mega-issue"),a(o)]),t(". Make sure to subscribe to that issue so that you are notified when it has been fixed and the configuration can be removed. The benefits of the new system are substantial, and we would hate for you to miss out on them.")]),e("p",null,[t("Thank you to all those who have submitted issues or let us know about problems via our "),e("a",P,[t("community areas"),a(o)]),t(", your help has been much appreciated. If you encounter any new problems, please check the Mega-issues and check if your issue has already been addressed. If not, then feel free to add a new post to that issue.")]),T,e("p",null,[t("If you haven't seen it already, "),e("a",I,[t("@maur\xEDcio szabo"),a(o)]),t(" has written a great new blog post on our next biggest goal for Pulsar: getting it onto a current and stable version of Electron. There are a number of challenges (the recent Tree-sitter update included) that need to be met in order to reach this, and the post explains it all.")]),e("p",null,[t("Read it now on the "),e("a",A,[t("Pulsar blog"),a(o)]),t("!")]),E,j,e("p",null,[t("Thanks to "),e("a",q,[t("@confused-techie"),a(o)]),t(" following on from some work via "),e("a",N,[t("@savetheclocktower"),a(o)]),t(", we now have a whole bunch of new ways to use the PPR API to filter packages. For example, you can now search for packages that provide a grammar for a particular file extension, consumes/provides a service, or just search by the package owner.")]),R,e("ul",null,[e("li",null,[t("To search for all packages that provide a grammar for "),M,t(" files - "),e("a",C,[t("https://web.pulsar-edit.dev/packages?"),U,a(o)])]),e("li",null,[t("To find all packages owned by "),e("a",z,[t("@bacadra"),a(o)]),t(" - "),e("a",F,[t("https://web.pulsar-edit.dev/packages/search?"),V,a(o)])]),e("li",null,[t("To find all packages that provide the "),W,t(" service - "),e("a",B,[t("https://web.pulsar-edit.dev/packages?"),O,t("&"),S,a(o)])])]),H,e("p",null,[t("Our API documentation (found at "),e("a",L,[t("https://api.pulsar-edit.dev/swagger-ui/"),a(o)]),t(") is having a major update courtesy of work by "),e("a",D,[t("@confused-techie"),a(o)]),t(". This update adds the significant number of endpoints and schemas that have been added since it was last updated.")]),G,J,K,e("p",null,[t("Thank you to "),e("a",Q,[t("@claytonrcarter"),a(o)]),t(" for the PR to Pulsar to "),e("a",X,[t("fix a breaking change"),a(o)]),t(" with our new Tree-sitter implementation as well as a second PR for "),e("a",Y,[t("adding a new "),Z,t(" flag"),a(o)]),t(" to the "),$,t(" command in order to aid in replacing linked packages without needing to manually remove original.")]),ee,te,oe,ae,e("section",re,[e("ol",se,[e("li",ne,[e("p",null,[t("Image from "),e("a",ie,[t("https://tree-sitter.github.io/tree-sitter/"),a(o)]),t(" - Copyright (c) 2018-2021 Max Brunsfeld "),le])]),e("li",he,[e("p",null,[t("Image from "),e("a",de,[t("https://www.electronjs.org/"),a(o)]),t(),ue])]),e("li",ce,[e("p",null,[t("Image from "),e("a",pe,[t("https://www.openapis.org/"),a(o)]),t(),fe])])])])])}const xe=h(m,[["render",me],["__file","20240201-Daeraxa-FebruaryUpdate.html.vue"]]);export{xe as default}; diff --git a/assets/20240215-Daeraxa-v1.114.0.html.51cb2c96.js b/assets/20240215-Daeraxa-v1.114.0.html.51cb2c96.js new file mode 100644 index 0000000000..9258b1abf5 --- /dev/null +++ b/assets/20240215-Daeraxa-v1.114.0.html.51cb2c96.js @@ -0,0 +1 @@ +const e=JSON.parse(`{"key":"v-21124b56","path":"/blog/20240215-Daeraxa-v1.114.0.html","title":"A Valentine's release bursting with love, Pulsar 1.114.0 is available now!","lang":"en-US","frontmatter":{"title":"A Valentine's release bursting with love, Pulsar 1.114.0 is available now!","author":"Daeraxa","date":"2024-02-16T00:00:00.000Z","category":["dev"],"tag":["release"]},"excerpt":"

    Pulsar 1.114.0 is available now!

    \\n","headers":[{"level":2,"title":"A Valentine's release bursting with love, Pulsar 1.114.0 is available now!","slug":"a-valentine-s-release-bursting-with-love-pulsar-1-114-0-is-available-now","link":"#a-valentine-s-release-bursting-with-love-pulsar-1-114-0-is-available-now","children":[{"level":3,"title":"Pulsar","slug":"pulsar","link":"#pulsar","children":[]},{"level":3,"title":"PPM","slug":"ppm","link":"#ppm","children":[]},{"level":3,"title":"github","slug":"github","link":"#github","children":[]}]}],"git":{"updatedTime":1708633602000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":2}]},"readingTime":{"minutes":3.7,"words":1110},"filePathRelative":"blog/20240215-Daeraxa-v1.114.0.md","localizedDate":"February 16, 2024"}`);export{e as data}; diff --git a/assets/20240215-Daeraxa-v1.114.0.html.52b3c07b.js b/assets/20240215-Daeraxa-v1.114.0.html.52b3c07b.js new file mode 100644 index 0000000000..5b082b068f --- /dev/null +++ b/assets/20240215-Daeraxa-v1.114.0.html.52b3c07b.js @@ -0,0 +1 @@ +import{_ as o,o as n,c as i,a as e,b as t,d as r,e as l,f as s,r as d}from"./app.0e1565ce.js";const h={},u={href:"https://github.com/pulsar-edit/pulsar/releases/tag/v1.114.0",target:"_blank",rel:"noopener noreferrer"},p=e("h2",{id:"a-valentine-s-release-bursting-with-love-pulsar-1-114-0-is-available-now",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#a-valentine-s-release-bursting-with-love-pulsar-1-114-0-is-available-now","aria-hidden":"true"},"#"),t(" A Valentine's release bursting with love, Pulsar 1.114.0 is available now!")],-1),c=e("p",null,[t("Welcome to a brand new Pulsar release! I think it is safe to say that this month has been one of our more eventful due to the switchover we made to the new Tree-sitter implementation. This release features a lot of updates and fixes for this new implementation, thanks to all the feedback we got from the community. We also have a number of other bug fixes and new features to introduce, such as restoring compatibility with older Linux distributions, and a new flag for an old favorite "),e("code",null,"ppm"),t(" command.")],-1),g={href:"https://web.pulsar-edit.dev/packages/semanticolor",target:"_blank",rel:"noopener noreferrer"},m={href:"https://github.com/pulsar-edit/pulsar/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+MEGA-ISSUE+label%3Abug",target:"_blank",rel:"noopener noreferrer"},f=s('

    Back in Pulsar 1.111.0, we created a new UI api to make an easy and universal way for packages to interact with Pulsar. This implementation did contain a couple of small oversights, which have now been resolved. One issue with the markdown implementation was the display of non-functional heading anchors when rendering a package README; the other was a small rendering bug that was causing a misalignment of content after a linebreak. We had also inadvertently introduced a new problem with fuzzymatcher that could cause the command palette to hang if it failed to highlight a match.

    One issue raised a while ago was that, starting with Pulsar 1.109.0, users of older LTS Linux distributions, such as AlmaLinux 8 and RockyLinux 9, were no longer able to run Pulsar. This was due to the CI runners we use being updated to newer OSs, and therefore Pulsar was being compiled against a newer version of glibc. We have changed our workflows to compile Pulsar in a Debian 10 Docker container instead, which should resolve this problem.

    It was noticed a while ago that the github package wasn't quite working correctly on ARM Linux systems. We have bumped the version of dugite (a library for using git in NodeJS and Electron) to resolve the problem.

    Lastly, we have two new features in ppm. The first is a new flag --force that can be specified on ppm link, which will forcibly replace a conflicting package without needing to manually remove it. The other is an update to ppm's syntax theme template so that, when generating a new syntax theme, it now includes some style variables that the editor has long supported, but which were not in the template, so theme authors might have been unaware of them. (As a bonus: there are two new variables supported by default in the editor, and added to the template as well! Theme authors, feel free to take advantage of these!)

    And that is it for yet another Pulsar release. We hope you enjoy this release and look forward to the next one!

    Until next time, happy coding, and see you amongst the stars!


    Pulsar

    ',10),b={href:"https://github.com/pulsar-edit/pulsar/pull/860",target:"_blank",rel:"noopener noreferrer"},_={href:"https://github.com/pulsar-edit/pulsar/pull/889",target:"_blank",rel:"noopener noreferrer"},w={href:"https://github.com/pulsar-edit/pulsar/pull/891",target:"_blank",rel:"noopener noreferrer"},k={href:"https://github.com/pulsar-edit/pulsar/pull/870",target:"_blank",rel:"noopener noreferrer"},x={href:"https://github.com/pulsar-edit/pulsar/pull/858",target:"_blank",rel:"noopener noreferrer"},v={href:"https://github.com/pulsar-edit/pulsar/pull/859",target:"_blank",rel:"noopener noreferrer"},y={href:"https://github.com/pulsar-edit/pulsar/pull/902",target:"_blank",rel:"noopener noreferrer"},D={href:"https://github.com/pulsar-edit/pulsar/pull/905",target:"_blank",rel:"noopener noreferrer"},A={href:"https://github.com/pulsar-edit/pulsar/pull/908",target:"_blank",rel:"noopener noreferrer"},F={href:"https://github.com/pulsar-edit/pulsar/pull/903",target:"_blank",rel:"noopener noreferrer"},T={href:"https://github.com/pulsar-edit/pulsar/pull/901",target:"_blank",rel:"noopener noreferrer"},U={href:"https://github.com/pulsar-edit/pulsar/pull/913",target:"_blank",rel:"noopener noreferrer"},L=e("code",null,"symbols-view",-1),P={href:"https://github.com/pulsar-edit/pulsar/pull/861",target:"_blank",rel:"noopener noreferrer"},E={href:"https://github.com/pulsar-edit/pulsar/pull/906",target:"_blank",rel:"noopener noreferrer"},M=e("code",null,"GITHUB_TOKEN",-1),I={href:"https://github.com/pulsar-edit/pulsar/pull/924",target:"_blank",rel:"noopener noreferrer"},R={href:"https://github.com/pulsar-edit/pulsar/pull/925",target:"_blank",rel:"noopener noreferrer"},G={href:"https://github.com/pulsar-edit/pulsar/pull/926",target:"_blank",rel:"noopener noreferrer"},B=e("h3",{id:"ppm",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#ppm","aria-hidden":"true"},"#"),t(" PPM")],-1),N={href:"https://github.com/pulsar-edit/ppm/pull/124",target:"_blank",rel:"noopener noreferrer"},S={href:"https://github.com/pulsar-edit/ppm/pull/123",target:"_blank",rel:"noopener noreferrer"},V={href:"https://github.com/pulsar-edit/ppm/pull/122",target:"_blank",rel:"noopener noreferrer"},W=e("h3",{id:"github",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#github","aria-hidden":"true"},"#"),t(" github")],-1),C={href:"https://github.com/pulsar-edit/github/pull/39",target:"_blank",rel:"noopener noreferrer"};function O(q,z){const a=d("ExternalLinkIcon");return n(),i("div",null,[e("p",null,[t("Pulsar "),e("a",u,[t("1.114.0"),r(a)]),t(" is available now!")]),l(" more "),p,c,e("p",null,[t("As mentioned above, we have quite a few changes relating to our new Tree-sitter implementation that we made default in Pulsar 1.113.0. First of all, we have some fixes and updates to make sure the new implementation isn't a regression from the legacy implementation. One such update is restoring support for the "),e("a",g,[t("semanticolor"),r(a)]),t(" package, which was supported by the previous implementation. Otherwise, if you had any particular issue with syntax highlighting, then we hope it has now been resolved, as a lot of changes and fixes have been made in response to community feedback. If not, then please visit our ongoing "),e("a",m,[t('"Mega-issues"'),r(a)]),t(" and see if your issue has been raised, and if not, please raise it there.")]),f,e("ul",null,[e("li",null,[t("Fixed: fix(tree-sitter): pass node text to grammar "),e("a",b,[t("@claytoncarter"),r(a)])]),e("li",null,[t("Fixed: Fix issue with Markdown rendering after line break in strict mode "),e("a",_,[t("@savetheclocktower"),r(a)])]),e("li",null,[t("Updated: Update README badges "),e("a",w,[t("@Daeraxa"),r(a)])]),e("li",null,[t("Updated: Update copyright year to 2024 "),e("a",k,[t("@Daeraxa"),r(a)])]),e("li",null,[t("Added: CI: build Linux x86-64 binaries on older Linux "),e("a",x,[t("@DeeDeeG"),r(a)])]),e("li",null,[t("Fixed: Tree-sitter rolling fixes (January edition) "),e("a",v,[t("@savetheclocktower"),r(a)])]),e("li",null,[t("Fixed: Fix failing spec "),e("a",y,[t("@savetheclocktower"),r(a)])]),e("li",null,[t("Fixed: [settings-view] Don't display heading anchor icons within a README "),e("a",D,[t("@savetheclocktower"),r(a)])]),e("li",null,[t("Updated: ppm: Update ppm to commit 241d794f326b63b5abdb9769 "),e("a",A,[t("@DeeDeeG"),r(a)])]),e("li",null,[t("Fixed: script: Update version check in Rolling release binary upload script to exclude '-dev' versions "),e("a",F,[t("@DeeDeeG"),r(a)])]),e("li",null,[t("Fixed: CI: Fix tag Linux binaries are uploaded to for Rolling "),e("a",T,[t("@DeeDeeG"),r(a)])]),e("li",null,[t("Fixed: [command-palette] Guard against failure to highlight a match "),e("a",U,[t("@savetheclocktower"),r(a)])]),e("li",null,[t("Fixed: "),L,t(" rolling fixes "),e("a",P,[t("@savetheclocktower"),r(a)])]),e("li",null,[t("Fixed: Tree-sitter rolling fixes (February) "),e("a",E,[t("@savetheclocktower"),r(a)])]),e("li",null,[t("Updated: [meta] Update Cirrus "),M,t(),e("a",I,[t("@confused-Techie"),r(a)])]),e("li",null,[t("Updated: deps: Update github to v0.36.20-pretranspiled to bump dugite "),e("a",R,[t("@DeeDeeG"),r(a)])]),e("li",null,[t("Fixed: [symbols-view] Remap go-to-declaration commands on Windows/Linux "),e("a",G,[t("@savetheclocktower"),r(a)])])]),B,e("ul",null,[e("li",null,[t("Fixed: Fix test failure due to missing atom command "),e("a",N,[t("@toddy15"),r(a)])]),e("li",null,[t("Updated: Update syntax-variables.less to include language entity colors "),e("a",S,[t("@savetheclocktower"),r(a)])]),e("li",null,[t("Added: feat(link): add --force flag "),e("a",V,[t("@claytoncarter"),r(a)])])]),W,e("ul",null,[e("li",null,[t("Updated: Bump dugite to 2.5.2 "),e("a",C,[t("@DeeDeeG"),r(a)])])])])}const J=o(h,[["render",O],["__file","20240215-Daeraxa-v1.114.0.html.vue"]]);export{J as default}; diff --git a/assets/20240323-savetheclocktower-v1.115.0.html.80b3b399.js b/assets/20240323-savetheclocktower-v1.115.0.html.80b3b399.js new file mode 100644 index 0000000000..a620ee8d86 --- /dev/null +++ b/assets/20240323-savetheclocktower-v1.115.0.html.80b3b399.js @@ -0,0 +1,10 @@ +import{_ as s,o as n,c as o,a as e,b as a,d as t,e as i,f as l,r as p}from"./app.0e1565ce.js";const c={},u={href:"https://github.com/pulsar-edit/pulsar/releases/tag/v1.115.0",target:"_blank",rel:"noopener noreferrer"},d=l(`

    A week later than you\u2019re accustomed to \u2014 but worth the wait! Pulsar 1.115.0 is available now!

    Last month\u2019s 1.114.0 release was full of fixes related to the recent migration to modern Tree-sitter. This month\u2019s release is much smaller, but still dominated by Tree-sitter fixes affecting syntax highlighting, code folding, and indentation.

    The most visible fixes are related to your ability to customize the grammar that Pulsar uses on a per-language basis. This is an approach we\u2019ve encouraged for users that want or need to revert to an older grammar for a specific language \u2014 better to do so on a targeted basis than globally. If you can edit your config file, you can do per-language customization.

    For instance, now it\u2019s even easier than before to say \u201Cuse legacy Tree-sitter, but only for Python\u201D:

    ".python.source":
    +  core:
    +    useLegacyTreeSitter: true
    +

    Or \u201Cuse modern Tree-sitter for JavaScript, but TextMate-style grammars everywhere else\u201D:

    "*":
    +  core:
    +    useTreeSitterParsers: false
    +".source.javascript":
    +  core:
    +    useTreeSitterParsers: true
    +

    Better yet, now the grammar-selector package will be attuned to these choices. When you manually reassign a buffer to use a different grammar, it will offer you only the \u201Ccorrect\u201D grammar for each language based on what you\u2019ve opted into.

    We\u2019ve also delivered our customary incremental improvements in language support, and one change that affects nearly all languages: indentation hinting will be more accurate in transactions with multiple buffer changes. The most common example of a multi-edit transaction is when the user places more than one cursor and starts typing.

    We\u2019ve made improvements to our language-ruby bundle, primarily with code folding and indentation hinting. TypeScript and C/C++ also got some small enhancements, and the language-shellscript bundle got a parser update.

    And lastly, on another note, we have a few maintenance and upkeep PR's to keep our Cirrus CI active and working, to ensure you can keep using and enjoying the latest builds Pulsar has to offer.

    Until next time, happy coding, and see you amongst the stars!


    Pulsar

    `,16),h={href:"https://github.com/pulsar-edit/pulsar/pull/956",target:"_blank",rel:"noopener noreferrer"},m={href:"https://github.com/pulsar-edit/pulsar/pull/941",target:"_blank",rel:"noopener noreferrer"},f={href:"https://github.com/pulsar-edit/pulsar/pull/960",target:"_blank",rel:"noopener noreferrer"},g={href:"https://github.com/pulsar-edit/pulsar/pull/961",target:"_blank",rel:"noopener noreferrer"},b={href:"https://github.com/pulsar-edit/pulsar/pull/944",target:"_blank",rel:"noopener noreferrer"},y={href:"https://github.com/pulsar-edit/pulsar/pull/936",target:"_blank",rel:"noopener noreferrer"},v={href:"https://github.com/pulsar-edit/pulsar/pull/937",target:"_blank",rel:"noopener noreferrer"};function k(_,w){const r=p("ExternalLinkIcon");return n(),o("div",null,[e("p",null,[a("Pulsar "),e("a",u,[a("1.115.0"),t(r)]),a(" is available now!")]),i(" more "),d,e("ul",null,[e("li",null,[a("Fixed: Fixed folds for Ruby "),e("a",h,[a("@mauricioszabo"),t(r)])]),e("li",null,[a("Fixed: Tree-sitter fixes: 1.115 edition "),e("a",m,[a("@savetheclocktower"),t(r)])]),e("li",null,[a("Updated: cirrus: Update Rolling upload token again "),e("a",f,[a("@DeeDeeG"),t(r)])]),e("li",null,[a("Fixed: cirrus: Various fixes for macOS Cirrus CI "),e("a",g,[a("@DeeDeeG"),t(r)])]),e("li",null,[a("Fixed: Fix(fuzzy-finder) fs.lstatSync throws Exception if not a file or dir "),e("a",b,[a("@schadomi7"),t(r)])]),e("li",null,[a("Updated: CI: Update Rolling upload token for Cirrus CI "),e("a",y,[a("@DeeDeeG"),t(r)])]),e("li",null,[a("Updated: Cirrus: Install older dotenv gem version ~> 2.8 (< 3) "),e("a",v,[a("@DeeDeeG"),t(r)])])])])}const T=s(c,[["render",k],["__file","20240323-savetheclocktower-v1.115.0.html.vue"]]);export{T as default}; diff --git a/assets/20240323-savetheclocktower-v1.115.0.html.f7639cd5.js b/assets/20240323-savetheclocktower-v1.115.0.html.f7639cd5.js new file mode 100644 index 0000000000..36e513409e --- /dev/null +++ b/assets/20240323-savetheclocktower-v1.115.0.html.f7639cd5.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-88d8cd3c","path":"/blog/20240323-savetheclocktower-v1.115.0.html","title":"A week later than you\u2019re accustomed to \u2014 but worth the wait! Pulsar 1.115.0 is available now!","lang":"en-US","frontmatter":{"title":"A week later than you\u2019re accustomed to \u2014 but worth the wait! Pulsar 1.115.0 is available now!","author":"savetheclocktower","date":"2024-03-23T00:00:00.000Z","category":["dev"],"tag":["release"]},"excerpt":"

    Pulsar 1.115.0 is available now!

    \\n","headers":[{"level":2,"title":"A week later than you\u2019re accustomed to \u2014 but worth the wait! Pulsar 1.115.0 is available now!","slug":"a-week-later-than-you-re-accustomed-to-\u2014-but-worth-the-wait-pulsar-1-115-0-is-available-now","link":"#a-week-later-than-you-re-accustomed-to-\u2014-but-worth-the-wait-pulsar-1-115-0-is-available-now","children":[{"level":3,"title":"Pulsar","slug":"pulsar","link":"#pulsar","children":[]}]}],"git":{"updatedTime":1711167744000,"contributors":[{"name":"DeeDeeG","email":"DeeDeeG@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":2.03,"words":609},"filePathRelative":"blog/20240323-savetheclocktower-v1.115.0.md","localizedDate":"March 23, 2024"}');export{e as data}; diff --git a/assets/20240417-confused-Techie-v1.116.0.html.5ffb1535.js b/assets/20240417-confused-Techie-v1.116.0.html.5ffb1535.js new file mode 100644 index 0000000000..ab7aaafde0 --- /dev/null +++ b/assets/20240417-confused-Techie-v1.116.0.html.5ffb1535.js @@ -0,0 +1 @@ +import{_ as o,o as s,c as n,a as e,b as t,d as a,e as i,f as l,r as d}from"./app.0e1565ce.js";const p={},h={href:"https://github.com/pulsar-edit/pulsar/releases/tag/v1.116.0",target:"_blank",rel:"noopener noreferrer"},c=l('

    Pulsar 1.116.0: Ready for all the code ninjas out there!

    Like the past few releases, this Pulsar release is full of Tree-sitter improvements and fixes! From TypeScript and Python getting improved syntax highlighting all around, to big changes like replacing the underlying Markdown Tree-sitter parser, Pulsar 1.116 is sure to make these grammars more stable and aesthetically pleasing.

    But on to some of the really exciting stuff this month: we have a flurry of new features and updates to snippets, which is sure to make otherwise repetitive coding exciting and high-tech!

    For anyone familiar with the super secret code ninja techniques of snippets, there's now support for new snippet variables and transformation flags. Be sure to read the full changelog to get caught up on all of these changes.

    As always, this release has some bug fixes and housekeeping. Linux folks will appreciate improved metadata for their installs, whereas Windows folks will be happy to see that adding Pulsar to the PATH is easier than ever whether Pulsar is installed for just one user or system-wide.

    That's just about everything this time around. We can't wait to see what people do with these powerful new snippets features and look forward to the next one!

    Until next time, happy coding, and see you amongst the stars! - The Pulsar team


    Pulsar

    ',10),u={href:"https://github.com/pulsar-edit/pulsar/pull/935",target:"_blank",rel:"noopener noreferrer"},f=e("code",null,"StyleManager",-1),m={href:"https://github.com/pulsar-edit/pulsar/pull/959",target:"_blank",rel:"noopener noreferrer"},_={href:"https://github.com/pulsar-edit/pulsar/pull/968",target:"_blank",rel:"noopener noreferrer"},g=e("code",null,"snippets",-1),w={href:"https://github.com/pulsar-edit/pulsar/pull/972",target:"_blank",rel:"noopener noreferrer"},b=e("code",null,"TextEditor",-1),y={href:"https://github.com/pulsar-edit/pulsar/pull/970",target:"_blank",rel:"noopener noreferrer"},k=e("code",null,"Machine",-1),T={href:"https://github.com/pulsar-edit/pulsar/pull/957",target:"_blank",rel:"noopener noreferrer"},v=e("h3",{id:"snippets",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#snippets","aria-hidden":"true"},"#"),t(" snippets")],-1),x=e("code",null,"LINE_COMMENT",-1),P=e("code",null,"BLOCK_COMMENT_START",-1),M=e("code",null,"BLOCK_COMMENT_END",-1),A={href:"https://github.com/pulsar-edit/snippets/pull/21",target:"_blank",rel:"noopener noreferrer"},E={href:"https://github.com/pulsar-edit/snippets/pull/20",target:"_blank",rel:"noopener noreferrer"};function C(N,B){const r=d("ExternalLinkIcon");return s(),n("div",null,[e("p",null,[t("Pulsar "),e("a",h,[t("1.116.0"),a(r)]),t(" is available now!")]),i(" more "),c,e("ul",null,[e("li",null,[t("Updated: Update Pulsar's Linux desktop & metainfo mostly from Flatpak "),e("a",u,[t("@cat-master21"),a(r)])]),e("li",null,[t("Updated: [core] Simplify/Cleanup "),f,t(),e("a",m,[t("@confused-Techie"),a(r)])]),e("li",null,[t("Fixed: Tree-sitter fixes (1.116 edition) "),e("a",_,[t("@savetheclocktower"),a(r)])]),e("li",null,[t("Bumped: Bump "),g,t(" dependency to 1.8.0 "),e("a",w,[t("@savetheclocktower"),a(r)])]),e("li",null,[t("Added: Add a "),b,t(" method for retrieving comment delimiters... "),e("a",y,[t("@savetheclocktower"),a(r)])]),e("li",null,[t("Fixed: [core] (Windows) Remove all "),k,t(" PATH handling, add safety mechanisms "),e("a",T,[t("@confused-Techie"),a(r)])])]),v,e("ul",null,[e("li",null,[t("Added: Add support for variables "),x,t(", "),P,t(" and "),M,t(),e("a",A,[t("@savetheclocktower"),a(r)])]),e("li",null,[t("Added: Extend support for simple transformation flags to sed-style replacements "),e("a",E,[t("@savetheclocktower"),a(r)])])])])}const O=o(p,[["render",C],["__file","20240417-confused-Techie-v1.116.0.html.vue"]]);export{O as default}; diff --git a/assets/20240417-confused-Techie-v1.116.0.html.7f7778b4.js b/assets/20240417-confused-Techie-v1.116.0.html.7f7778b4.js new file mode 100644 index 0000000000..b76b87c13a --- /dev/null +++ b/assets/20240417-confused-Techie-v1.116.0.html.7f7778b4.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-3ac80fd4","path":"/blog/20240417-confused-Techie-v1.116.0.html","title":"For all the code ninjas out there, Pulsar 1.116.0 is available now!","lang":"en-US","frontmatter":{"title":"For all the code ninjas out there, Pulsar 1.116.0 is available now!","author":"confused-Techie","date":"2024-04-17T00:00:00.000Z","category":["dev"],"tag":["release"]},"excerpt":"

    Pulsar 1.116.0 is available now!

    \\n","headers":[{"level":2,"title":"Pulsar 1.116.0: Ready for all the code ninjas out there!","slug":"pulsar-1-116-0-ready-for-all-the-code-ninjas-out-there","link":"#pulsar-1-116-0-ready-for-all-the-code-ninjas-out-there","children":[{"level":3,"title":"Pulsar","slug":"pulsar","link":"#pulsar","children":[]},{"level":3,"title":"snippets","slug":"snippets","link":"#snippets","children":[]}]}],"git":{"updatedTime":1713398129000,"contributors":[{"name":"DeeDeeG","email":"DeeDeeG@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":1.79,"words":536},"filePathRelative":"blog/20240417-confused-Techie-v1.116.0.md","localizedDate":"April 17, 2024"}');export{e as data}; diff --git a/assets/20240520-confused-Techie-v1.117.0.html.022d8530.js b/assets/20240520-confused-Techie-v1.117.0.html.022d8530.js new file mode 100644 index 0000000000..a9b1421b59 --- /dev/null +++ b/assets/20240520-confused-Techie-v1.117.0.html.022d8530.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-83b0b1e4","path":"/blog/20240520-confused-Techie-v1.117.0.html","title":"With special love for Markdown and Tree-sitter, Pulsar 1.117.0 is available now!","lang":"en-US","frontmatter":{"title":"With special love for Markdown and Tree-sitter, Pulsar 1.117.0 is available now!","author":"confused-Techie","date":"2024-05-20T00:00:00.000Z","category":["dev"],"tag":["release"]},"excerpt":"

    Pulsar 1.117.0 is available now!

    \\n","headers":[{"level":2,"title":"Pulsar 1.117.0: With special love for Markdown and Tree-sitter","slug":"pulsar-1-117-0-with-special-love-for-markdown-and-tree-sitter","link":"#pulsar-1-117-0-with-special-love-for-markdown-and-tree-sitter","children":[{"level":3,"title":"Pulsar","slug":"pulsar","link":"#pulsar","children":[]}]}],"git":{"updatedTime":1716339766000,"contributors":[{"name":"DeeDeeG","email":"DeeDeeG@users.noreply.github.com","commits":2}]},"readingTime":{"minutes":1.74,"words":523},"filePathRelative":"blog/20240520-confused-Techie-v1.117.0.md","localizedDate":"May 20, 2024"}');export{e as data}; diff --git a/assets/20240520-confused-Techie-v1.117.0.html.22f66452.js b/assets/20240520-confused-Techie-v1.117.0.html.22f66452.js new file mode 100644 index 0000000000..9ebde26cea --- /dev/null +++ b/assets/20240520-confused-Techie-v1.117.0.html.22f66452.js @@ -0,0 +1 @@ +import{_ as a,o as n,c as l,a as r,b as e,d as o,e as i,f as s,r as d}from"./app.0e1565ce.js";const p={},u={href:"https://github.com/pulsar-edit/pulsar/releases/tag/v1.117.0",target:"_blank",rel:"noopener noreferrer"},c=s('

    Pulsar 1.117.0: With special love for Markdown and Tree-sitter

    As you may have come to expect, this release is full of new improvements for Tree-sitter grammars! From SCSS now having Tree-sitter support, to Ruby getting an updated parser, to performance and highlighting enhancements in Markdown and TypeScript, there's hopefully something in there for everybody.

    Speaking of Markdown, the markdown-preview package has gotten significant performance increases in the preview pane (especially for documents with lots of fenced code blocks), and boasts brand new dark mode support for the "GitHub.com style" option!

    But dark mode support doesn't stop there! Pulsar can now (optionally) use the OS-level dark mode window theme, switching automatically based on the lightness or darkness of the colors in the active Pulsar theme if enabled.

    Beyond the fixes and features within the editor itself, you'll be glad to know this release also saw many improvements for our Continuous Integration to ensure we can deliver timely and bug-free updates in the future. - The Pulsar team


    Pulsar

    ',8),h={href:"https://github.com/pulsar-edit/pulsar/pull/1008",target:"_blank",rel:"noopener noreferrer"},g={href:"https://github.com/pulsar-edit/pulsar/pull/1006",target:"_blank",rel:"noopener noreferrer"},f={href:"https://github.com/pulsar-edit/pulsar/pull/1002",target:"_blank",rel:"noopener noreferrer"},m={href:"https://github.com/pulsar-edit/pulsar/pull/984",target:"_blank",rel:"noopener noreferrer"},_={href:"https://github.com/pulsar-edit/pulsar/pull/974",target:"_blank",rel:"noopener noreferrer"},b={href:"https://github.com/pulsar-edit/pulsar/pull/1000",target:"_blank",rel:"noopener noreferrer"},w={href:"https://github.com/pulsar-edit/pulsar/pull/995",target:"_blank",rel:"noopener noreferrer"},k={href:"https://github.com/pulsar-edit/pulsar/pull/993",target:"_blank",rel:"noopener noreferrer"},v={href:"https://github.com/pulsar-edit/pulsar/pull/997",target:"_blank",rel:"noopener noreferrer"},y={href:"https://github.com/pulsar-edit/pulsar/pull/973",target:"_blank",rel:"noopener noreferrer"},x={href:"https://github.com/pulsar-edit/pulsar/pull/545",target:"_blank",rel:"noopener noreferrer"},T={href:"https://github.com/pulsar-edit/pulsar/pull/983",target:"_blank",rel:"noopener noreferrer"},C=r("code",null,".org",-1),S=r("code",null,".language-clojure",-1),A={href:"https://github.com/pulsar-edit/pulsar/pull/980",target:"_blank",rel:"noopener noreferrer"};function G(P,D){const t=d("ExternalLinkIcon");return n(),l("div",null,[r("p",null,[e("Pulsar "),r("a",u,[e("1.117.0"),o(t)]),e(" is available now!")]),i(" more "),c,r("ul",null,[r("li",null,[e("Fixed: Cirrus: Fix gem install fpm on ARM Linux "),r("a",h,[e("@DeeDeeG"),o(t)])]),r("li",null,[e("Updated: [ci] Update Cirrus CI Token "),r("a",g,[e("@confused-Techie"),o(t)])]),r("li",null,[e("Fixed: CI: Fix workaround for Homebrew node in Cirrus on macOS "),r("a",f,[e("@DeeDeeG"),o(t)])]),r("li",null,[e("Added: [markdown-preview] Optimize re-rendering of content in a preview pane especially syntax highlighting "),r("a",m,[e("@savetheclocktower"),o(t)])]),r("li",null,[e("Fixed: Tree-sitter rolling fixes, 1.117 edition "),r("a",_,[e("@savetheclocktower"),o(t)])]),r("li",null,[e("Updated: Update Renovate preset name "),r("a",b,[e("@HonkingGoose"),o(t)])]),r("li",null,[e("Added: Debugging when a package service is incorrect "),r("a",w,[e("@mauricioszabo"),o(t)])]),r("li",null,[e("Added: Bundle snippets "),r("a",k,[e("@confused-Techie"),o(t)])]),r("li",null,[e("Fixed: CI: Pin to macOS 12 runner images instead of macos-latest (GitHub Actions) "),r("a",v,[e("@DeeDeeG"),o(t)])]),r("li",null,[e("Added: [markdown-preview] Add dark mode for GitHub-style preview "),r("a",y,[e("@savetheclocktower"),o(t)])]),r("li",null,[e("Added: Change Window Theme with Pulsar Theme "),r("a",x,[e("@confused-Techie"),o(t)])]),r("li",null,[e("Updated: CI: Upgrade or replace all deprecated GH Actions "),r("a",T,[e("@DeeDeeG"),o(t)])]),r("li",null,[e("Fixed: [language-clojure] Stop detecting "),C,e(" files as "),S,e(),r("a",A,[e("@confused-Techie"),o(t)])])])])}const H=a(p,[["render",G],["__file","20240520-confused-Techie-v1.117.0.html.vue"]]);export{H as default}; diff --git a/assets/20240616-confused-Techie-v1.118.0.html.8cfe0363.js b/assets/20240616-confused-Techie-v1.118.0.html.8cfe0363.js new file mode 100644 index 0000000000..5cca058131 --- /dev/null +++ b/assets/20240616-confused-Techie-v1.118.0.html.8cfe0363.js @@ -0,0 +1 @@ +import{_ as a,o as n,c as s,a as t,b as e,d as r,e as i,f as l,r as d}from"./app.0e1565ce.js";const c={},h={href:"https://github.com/pulsar-edit/pulsar/releases/tag/v1.118.0",target:"_blank",rel:"noopener noreferrer"},u=l('

    Hot dog, it's another Pulsar release!

    Get your grills ready, Pulsar v1.118.0 is cooking with gas! This release brings lots of love to syntax highlighting, along with a zesty sprinkling of features and fixes.

    We've got Tree-sitter fixes and improvements, including improved documentation around Tree-sitter's usage, an updated PHP parser, fixed syntax quoting on Clojure, as well as enhanced Clojure highlighting support for metadata and "def" elements, as well as improved injection points for Clojure. EDN files are back to being detected as Clojure, but will highlight correctly as EDN. Tasty!

    Within the Pulsar application the new setting core.allowWindowTransparency lets users set the background of the editor to support transparency, although transparent CSS styles must be set by a theme or user stylesheet for Pulsar to actually be transparent. The textChanged property is now accurate when characters are deleted.

    The Tree-sitter syntax highlighting system now has a new test for queries ancestorTypeNearerThan which helps matching the first type as an ancestor, as well as supporting a second buffer argument in the content field of addInjectionPoint for easier customization.

    Finally, for our community package developers, they will be glad to hear that first time publication issues have been resolved with a rewrite of the ppm publish command, improving the workflow, and ensuring things work properly with the updated backend. Please feel free to share what you've been cooking by publishing your packages!

    As always, thanks a ton to all of our users and supporters for sticking with the Pulsar project, and until next time, happy coding, and see you amongst the stars!

    - The Pulsar Team


    Pulsar

    ',11),p={href:"https://github.com/pulsar-edit/pulsar/pull/1010",target:"_blank",rel:"noopener noreferrer"},f={href:"https://github.com/pulsar-edit/pulsar/pull/982",target:"_blank",rel:"noopener noreferrer"},g={href:"https://github.com/pulsar-edit/pulsar/pull/729",target:"_blank",rel:"noopener noreferrer"},m=t("code",null,"onDidChangeCursorPosition",-1),b={href:"https://github.com/pulsar-edit/pulsar/pull/810",target:"_blank",rel:"noopener noreferrer"},y={href:"https://github.com/pulsar-edit/pulsar/pull/1014",target:"_blank",rel:"noopener noreferrer"},_={href:"https://github.com/pulsar-edit/pulsar/pull/1011",target:"_blank",rel:"noopener noreferrer"};function w(k,x){const o=d("ExternalLinkIcon");return n(),s("div",null,[t("p",null,[e("Pulsar "),t("a",h,[e("1.118.0"),r(o)]),e(" is available now!")]),i(" more "),u,t("ul",null,[t("li",null,[e("Fixed: Tree-sitter rolling fixes, 1.118 edition "),t("a",p,[e("@savetheclocktower"),r(o)])]),t("li",null,[e("Added: src: Allow windows to be transparent, behind a pref (off by default) "),t("a",f,[e("@DeeDeeG"),r(o)])]),t("li",null,[e("Added: Another batch of Clojure enhancements "),t("a",g,[e("@mauricioszabo"),r(o)])]),t("li",null,[e("Fixed: Fix "),m,e(" callback event property on deleting characters "),t("a",b,[e("@mauricioszabo"),r(o)])]),t("li",null,[e("Bumped: Update ppm to commit 3542dee00f4622f7458f2f65f05e5 "),t("a",y,[e("@DeeDeeG"),r(o)])]),t("li",null,[e("Updated: Cirrus: Update Rolling upload token "),t("a",_,[e("@DeeDeeG"),r(o)])])])])}const T=a(c,[["render",w],["__file","20240616-confused-Techie-v1.118.0.html.vue"]]);export{T as default}; diff --git a/assets/20240616-confused-Techie-v1.118.0.html.eef47ab0.js b/assets/20240616-confused-Techie-v1.118.0.html.eef47ab0.js new file mode 100644 index 0000000000..6402a34740 --- /dev/null +++ b/assets/20240616-confused-Techie-v1.118.0.html.eef47ab0.js @@ -0,0 +1 @@ +const e=JSON.parse(`{"key":"v-cdb5fcd6","path":"/blog/20240616-confused-Techie-v1.118.0.html","title":"Hot dog, it's another Pulsar release!","lang":"en-US","frontmatter":{"title":"Hot dog, it's another Pulsar release!","author":"confused-Techie","date":"2024-06-16T00:00:00.000Z","category":["dev"],"tag":["release"]},"excerpt":"

    Pulsar 1.118.0 is available now!

    \\n","headers":[{"level":2,"title":"Hot dog, it's another Pulsar release!","slug":"hot-dog-it-s-another-pulsar-release","link":"#hot-dog-it-s-another-pulsar-release","children":[{"level":3,"title":"Pulsar","slug":"pulsar","link":"#pulsar","children":[]}]}],"git":{"updatedTime":1718593288000,"contributors":[{"name":"DeeDeeG","email":"DeeDeeG@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":1.94,"words":581},"filePathRelative":"blog/20240616-confused-Techie-v1.118.0.md","localizedDate":"June 16, 2024"}`);export{e as data}; diff --git a/assets/20240717-confused-Techie-v1.119.0.html.002ab8fa.js b/assets/20240717-confused-Techie-v1.119.0.html.002ab8fa.js new file mode 100644 index 0000000000..fa554e2fdd --- /dev/null +++ b/assets/20240717-confused-Techie-v1.119.0.html.002ab8fa.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-d8729a94","path":"/blog/20240717-confused-Techie-v1.119.0.html","title":"Pulsar v1.119.0 is Live!","lang":"en-US","frontmatter":{"title":"Pulsar v1.119.0 is Live!","author":"confused-Techie","date":"2024-07-17T00:00:00.000Z","category":["dev"],"tag":["release"]},"excerpt":"

    Pulsar 1.119.0 is available now!

    \\n","headers":[{"level":2,"title":"Pulsar v1.119.0 is live!","slug":"pulsar-v1-119-0-is-live","link":"#pulsar-v1-119-0-is-live","children":[{"level":3,"title":"Pulsar","slug":"pulsar","link":"#pulsar","children":[]},{"level":3,"title":"superstring","slug":"superstring","link":"#superstring","children":[]}]}],"git":{"updatedTime":1721276001000,"contributors":[{"name":"DeeDeeG","email":"DeeDeeG@users.noreply.github.com","commits":2}]},"readingTime":{"minutes":1.37,"words":410},"filePathRelative":"blog/20240717-confused-Techie-v1.119.0.md","localizedDate":"July 17, 2024"}');export{e as data}; diff --git a/assets/20240717-confused-Techie-v1.119.0.html.170ba893.js b/assets/20240717-confused-Techie-v1.119.0.html.170ba893.js new file mode 100644 index 0000000000..e2e51a8e25 --- /dev/null +++ b/assets/20240717-confused-Techie-v1.119.0.html.170ba893.js @@ -0,0 +1 @@ +import{_ as a,o as n,c as l,a as e,b as o,d as r,e as i,f as s,r as c}from"./app.0e1565ce.js";const d={},u={href:"https://github.com/pulsar-edit/pulsar/releases/tag/v1.119.0",target:"_blank",rel:"noopener noreferrer"},h=s('

    Pulsar v1.119.0 is live!

    While a smaller release this time around, v1.119.0 still manages to pack a punch.

    For macOS, we've gone to great lengths to ensure Pulsar should build just fine on macOS 13+, while our Linux users get greater compatibility for DevTools on various platforms. For our programmers, there's been more of the constant incremental improvements to various languages' built-in syntax highlighting and code folding this time around, with a focus on PHP, Python, Javascript, Typescript, Shell script, and C.

    As always thanks a ton to all of those that support the project and keep it moving forward, we appreciate you all, and look forward to seeing you amongst the stars.

    - The Pulsar Team

    Notice

    Originally, the binaries from our normal "Pull Request" CI build (which produces unsigned binaries) were accidentally uploaded to this release, instead of the binaries from the special "tag push" CI build (which signs the macOS binaries). In order to provide you with signed Intel macOS binaries, we have re-uploaded them and updated SHA256SUMS.txt to reflect this. The following binaries have been swapped out for the correct versions and the checksums updated:


    Pulsar

    ',9),p={href:"https://github.com/pulsar-edit/pulsar/pull/1028",target:"_blank",rel:"noopener noreferrer"},g=e("code",null,"tree-view.js",-1),f={href:"https://github.com/pulsar-edit/pulsar/pull/1052",target:"_blank",rel:"noopener noreferrer"},_=e("code",null,"libiconv",-1),m={href:"https://github.com/pulsar-edit/pulsar/pull/1051",target:"_blank",rel:"noopener noreferrer"},b=e("code",null,"libiconv",-1),v={href:"https://github.com/pulsar-edit/pulsar/pull/1039",target:"_blank",rel:"noopener noreferrer"},k={href:"https://github.com/pulsar-edit/pulsar/pull/1035",target:"_blank",rel:"noopener noreferrer"},w={href:"https://github.com/pulsar-edit/pulsar/pull/1029",target:"_blank",rel:"noopener noreferrer"},x=e("h3",{id:"superstring",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#superstring","aria-hidden":"true"},"#"),o(" superstring")],-1),y=e("code",null,"master",-1),P={href:"https://github.com/pulsar-edit/superstring/pull/15",target:"_blank",rel:"noopener noreferrer"};function S(I,T){const t=c("ExternalLinkIcon");return n(),l("div",null,[e("p",null,[o("Pulsar "),e("a",u,[o("1.119.0"),r(t)]),o(" is available now!")]),i(" more "),h,e("ul",null,[e("li",null,[o("Tree-sitter rolling fixes, 1.119.0 edition "),e("a",p,[o("@savetheclocktower"),r(t)])]),e("li",null,[o("Rewrite "),g,o(),e("a",f,[o("@savetheclocktower"),r(t)])]),e("li",null,[o("Fix macOS binaries by vendorizing "),_,o(),e("a",m,[o("@savetheclocktower"),r(t)])]),e("li",null,[o("Link to Homebrew version of "),b,o("... "),e("a",v,[o("@savetheclocktower"),r(t)])]),e("li",null,[o('Revert "Merge pull request #810 from pulsar-edit/fix-on-change-cursor-pos" '),e("a",k,[o("@savetheclocktower"),r(t)])]),e("li",null,[o("electron-builder: Add '--no-sandbox' launch arg for Linux build targets "),e("a",w,[o("@DeeDeeG"),r(t)])])]),x,e("ul",null,[e("li",null,[o("Candidate for new "),y,o(),e("a",P,[o("@savetheclocktower"),r(t)])])])])}const N=a(d,[["render",S],["__file","20240717-confused-Techie-v1.119.0.html.vue"]]);export{N as default}; diff --git a/assets/404.html.7cbe2b72.js b/assets/404.html.7cbe2b72.js new file mode 100644 index 0000000000..0a6b5743b7 --- /dev/null +++ b/assets/404.html.7cbe2b72.js @@ -0,0 +1 @@ +import{_ as e,o as c,c as t}from"./app.0e1565ce.js";const _={};function o(r,n){return c(),t("div")}const a=e(_,[["render",o],["__file","404.html.vue"]]);export{a as default}; diff --git a/assets/404.html.83136f9a.js b/assets/404.html.83136f9a.js new file mode 100644 index 0000000000..804a4e3799 --- /dev/null +++ b/assets/404.html.83136f9a.js @@ -0,0 +1 @@ +const t=JSON.parse('{"key":"v-3706649a","path":"/404.html","title":"","lang":"en-US","frontmatter":{"layout":"NotFound"},"excerpt":"","headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null}');export{t as data}; diff --git a/assets/Linux-ARM.9adb19d5.jpg b/assets/Linux-ARM.9adb19d5.jpg new file mode 100644 index 0000000000..857621dd6f Binary files /dev/null and b/assets/Linux-ARM.9adb19d5.jpg differ diff --git a/assets/about.html.6eb730a3.js b/assets/about.html.6eb730a3.js new file mode 100644 index 0000000000..8d825ba7b9 --- /dev/null +++ b/assets/about.html.6eb730a3.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-22a39d25","path":"/about.html","title":"About Us","lang":"en-US","frontmatter":{"title":"About Us","path":"/about/","sitemap":{"priority":0.8,"changefreq":"weekly"}},"excerpt":"","headers":[{"level":2,"title":"The team","slug":"the-team","link":"#the-team","children":[]},{"level":2,"title":"The goals","slug":"the-goals","link":"#the-goals","children":[]}],"git":{"updatedTime":1698158311000,"contributors":[{"name":"Samuel Adekunle","email":"oluwatomisinadekunle@gmail.com","commits":2},{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1},{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":1},{"name":"confused-Techie","email":"dev@lhbasics.com","commits":1}]},"readingTime":{"minutes":0.86,"words":257},"filePathRelative":"about.md"}');export{e as data}; diff --git a/assets/about.html.b43feee1.js b/assets/about.html.b43feee1.js new file mode 100644 index 0000000000..ba5d70e708 --- /dev/null +++ b/assets/about.html.b43feee1.js @@ -0,0 +1 @@ +import{_ as r,o as i,c as l,a as e,b as t,d as o,w as u,e as h,r as a}from"./app.0e1565ce.js";const d={},c=e("h1",{id:"about-us",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#about-us","aria-hidden":"true"},"#"),t(" About us")],-1),m=e("h2",{id:"the-team",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#the-team","aria-hidden":"true"},"#"),t(" The team")],-1),_={href:"https://github.blog/2022-06-08-sunsetting-atom/",target:"_blank",rel:"noopener noreferrer"},p=e("p",null,"This is a true community-led project to modernize, update and improve the original Atom project into a contemporary, hackable and fully open editor.",-1),f=e("h2",{id:"the-goals",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#the-goals","aria-hidden":"true"},"#"),t(" The goals "),h("See: https://github.com/orgs/pulsar-edit/discussions/50")],-1),b=e("li",null,[t("Community developed, led and focused "),e("ul",null,[e("li",null,"Pulsar is being made by a community who came together from the stellar remnants of Atom. A community that wants to build upon the huge legacy that was left and make a uniquely hackable editor.")])],-1),g={href:"https://github.blog/2022-06-08-sunsetting-atom/",target:"_blank",rel:"noopener noreferrer"},y=e("ul",null,[e("li",null,"This means not only supporting the editor itself but also the package repository with its thousands of community contributions.")],-1),k=e("li",null,[t("Update core technologies to bring the editor up to date. "),e("ul",null,[e("li",null,"Core technologies such as Node.js, Electron etc., keeping them up to date so new features and libraries can be used and added without hacky workarounds.")])],-1),w=e("li",null,"Emphasize the elements that make the editor great to really make Pulsar stand out from the crowd, not only for ex-Atom users but for everyone.",-1);function x(v,A){const n=a("ExternalLinkIcon"),s=a("RouterLink");return i(),l("div",null,[c,m,e("p",null,[t("The team behind Pulsar is a community that came about naturally after the announcement of "),e("a",_,[t("Atom's Sunset"),o(n)]),t(" and decided that they needed to do something about it to keep their favorite editor alive.")]),p,e("p",null,[t("Pulsar is all of us, feel free to contribute, discuss, answer questions and suggest ideas in any of our "),o(s,{to:"/community.html"},{default:u(()=>[t("community areas")]),_:1}),t(".")]),f,e("ul",null,[b,e("li",null,[t("To continue and build upon the legacy of the Atom text editor which has been "),e("a",g,[t("sunset"),o(n)]),t(". "),y]),k,w])])}const C=r(d,[["render",x],["__file","about.html.vue"]]);export{C as default}; diff --git a/assets/allow-pending-pane-items.0fe3dd4a.js b/assets/allow-pending-pane-items.0fe3dd4a.js new file mode 100644 index 0000000000..71d3a57d93 --- /dev/null +++ b/assets/allow-pending-pane-items.0fe3dd4a.js @@ -0,0 +1 @@ +const e="/assets/allow-pending-pane-items.6bde8e2d.png";export{e as _}; diff --git a/assets/allow-pending-pane-items.6bde8e2d.png b/assets/allow-pending-pane-items.6bde8e2d.png new file mode 100644 index 0000000000..277221ef36 Binary files /dev/null and b/assets/allow-pending-pane-items.6bde8e2d.png differ diff --git a/assets/app.0e1565ce.js b/assets/app.0e1565ce.js new file mode 100644 index 0000000000..e6f3bbb371 --- /dev/null +++ b/assets/app.0e1565ce.js @@ -0,0 +1,132 @@ +var ku=Object.defineProperty;var yu=(e,t,i)=>t in e?ku(e,t,{enumerable:!0,configurable:!0,writable:!0,value:i}):e[t]=i;var ai=(e,t,i)=>(yu(e,typeof t!="symbol"?t+"":t,i),i);const wu="modulepreload",xu=function(e){return"/"+e},Nl={},s=function(t,i,a){return!i||i.length===0?t():Promise.all(i.map(n=>{if(n=xu(n),n in Nl)return;Nl[n]=!0;const l=n.endsWith(".css"),o=l?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${n}"]${o}`))return;const r=document.createElement("link");if(r.rel=l?"stylesheet":wu,l||(r.as="script",r.crossOrigin=""),r.href=n,document.head.appendChild(r),l)return new Promise((c,d)=>{r.addEventListener("load",c),r.addEventListener("error",()=>d(new Error(`Unable to preload CSS for ${n}`)))})})).then(()=>t())},Tu={"v-22a39d25":()=>s(()=>import("./about.html.6eb730a3.js"),[]).then(({data:e})=>e),"v-67f865c9":()=>s(()=>import("./community.html.1cb2fcef.js"),[]).then(({data:e})=>e),"v-0100cb31":()=>s(()=>import("./donate.html.21bc69cb.js"),[]).then(({data:e})=>e),"v-5395f6f8":()=>s(()=>import("./download.html.7c98c4b3.js"),[]).then(({data:e})=>e),"v-8daa1a0e":()=>s(()=>import("./index.html.64f1ae66.js"),[]).then(({data:e})=>e),"v-41185df1":()=>s(()=>import("./repos.html.ad6ff885.js"),[]).then(({data:e})=>e),"v-f7f18754":()=>s(()=>import("./20221112-Daeraxa-ExamplePost.html.20d587f5.js"),[]).then(({data:e})=>e),"v-0075970e":()=>s(()=>import("./20221127-confused-Techie-SunsetMisadventureBackend.html.dad96414.js"),[]).then(({data:e})=>e),"v-cfc0df38":()=>s(()=>import("./20221208-Daeraxa-DistroTubeVideo.html.a94d35bc.js"),[]).then(({data:e})=>e),"v-4769b7e2":()=>s(()=>import("./20221215-confused-Techie-v1.100.1-beta.html.a78b753f.js"),[]).then(({data:e})=>e),"v-59933042":()=>s(()=>import("./20230114-confused-Techie-v1.101.0-beta.html.2235fad4.js"),[]).then(({data:e})=>e),"v-3a050031":()=>s(()=>import("./20230201-Daeraxa-FebUpdate.html.78289db0.js"),[]).then(({data:e})=>e),"v-5fdcc4a1":()=>s(()=>import("./20230209-mauricioszabo-tree-sitter-part-1.html.580bcde1.js"),[]).then(({data:e})=>e),"v-f88b1c54":()=>s(()=>import("./20230215-Daeraxa-v1.102.0.html.6a8c9a26.js"),[]).then(({data:e})=>e),"v-5999d3d4":()=>s(()=>import("./20230216-Daeraxa-ReleaseStrategyUpdate.html.97dbb122.js"),[]).then(({data:e})=>e),"v-5b193c62":()=>s(()=>import("./20230227-Daeraxa-Survey1.html.cce17cec.js"),[]).then(({data:e})=>e),"v-29fd1295":()=>s(()=>import("./20230301-Daeraxa-MarUpdate.html.0a811ee1.js"),[]).then(({data:e})=>e),"v-6fcbebb6":()=>s(()=>import("./20230315-Daeraxa-v1.103.0.html.9a66cb6e.js"),[]).then(({data:e})=>e),"v-7f7a1a1a":()=>s(()=>import("./20230319-confused-Techie-HowLicenseNoneDeletedPackages.html.64b9bafc.js"),[]).then(({data:e})=>e),"v-91e3038e":()=>s(()=>import("./20230326-Daeraxa-Survey1-Results.html.e8bb3de9.js"),[]).then(({data:e})=>e),"v-e1c35862":()=>s(()=>import("./20230401-Daeraxa-AprUpdate.html.ef4f2fd8.js"),[]).then(({data:e})=>e),"v-0af04e6e":()=>s(()=>import("./20230401-confused-Techie-PON.html.f6d970d6.js"),[]).then(({data:e})=>e),"v-3a723159":()=>s(()=>import("./20230418-Daeraxa-v1.104.0.html.498d1fde.js"),[]).then(({data:e})=>e),"v-1d4a9cb2":()=>s(()=>import("./20230430-Daeraxa-Survey2.html.570aacd8.js"),[]).then(({data:e})=>e),"v-638b3e6c":()=>s(()=>import("./20230501-Daeraxa-MayUpdate.html.40844741.js"),[]).then(({data:e})=>e),"v-dbbf0e92":()=>s(()=>import("./20230516-Daeraxa-v1.105.0.html.bf43148e.js"),[]).then(({data:e})=>e),"v-aebfc812":()=>s(()=>import("./20230525-Daeraxa-Survey2-Results.html.af46184a.js"),[]).then(({data:e})=>e),"v-54f2f18e":()=>s(()=>import("./20230601-Daeraxa-JuneUpdate.html.5770013f.js"),[]).then(({data:e})=>e),"v-25d26d73":()=>s(()=>import("./20230610-Daeraxa-2kStars.html.f63a03a7.js"),[]).then(({data:e})=>e),"v-7e31f297":()=>s(()=>import("./20230616-Daeraxa-v1.106.0.html.22d3e3b7.js"),[]).then(({data:e})=>e),"v-4b1ee0b9":()=>s(()=>import("./20230701-Daeraxa-JulyUpdate.html.acbe26e1.js"),[]).then(({data:e})=>e),"v-5e247e06":()=>s(()=>import("./20230715-DeeDeeG-v1.107.0.html.38608ae1.js"),[]).then(({data:e})=>e),"v-6bf84516":()=>s(()=>import("./20230716-Daeraxa-v1.107.1.html.d06cfc56.js"),[]).then(({data:e})=>e),"v-3cb6722f":()=>s(()=>import("./20230801-Daeraxa-AugustUpdate.html.a30019be.js"),[]).then(({data:e})=>e),"v-0730d41e":()=>s(()=>import("./20230816-DeeDeeG-v1.108.0.html.11a20d1e.js"),[]).then(({data:e})=>e),"v-7b64cdf7":()=>s(()=>import("./20230825-Daeraxa-ChocolateyUpdate.html.53eb3298.js"),[]).then(({data:e})=>e),"v-356c6c42":()=>s(()=>import("./20230903-confused-Techie-pulsars-ci.html.f7812f3a.js"),[]).then(({data:e})=>e),"v-4dac8919":()=>s(()=>import("./20230904-Daeraxa-SeptemberUpdate.html.77d3175b.js"),[]).then(({data:e})=>e),"v-42666037":()=>s(()=>import("./20230916-Daeraxa-v1.109.0.html.fcf83ec5.js"),[]).then(({data:e})=>e),"v-64adae58":()=>s(()=>import("./20230917-Daeraxa-LemmyCommunity.html.95f4ca86.js"),[]).then(({data:e})=>e),"v-25bd77f6":()=>s(()=>import("./20230925-savetheclocktower-modern-tree-sitter-part-1.html.3d8130c9.js"),[]).then(({data:e})=>e),"v-5bad2e3c":()=>s(()=>import("./20230927-savetheclocktower-modern-tree-sitter-part-2.html.c9b9a8bd.js"),[]).then(({data:e})=>e),"v-6cc4d458":()=>s(()=>import("./20231004-Daeraxa-OctoberUpdate.html.3b1907f4.js"),[]).then(({data:e})=>e),"v-8b6c6124":()=>s(()=>import("./20231013-savetheclocktower-modern-tree-sitter-part-3.html.479e8a22.js"),[]).then(({data:e})=>e),"v-e8324d12":()=>s(()=>import("./20231016-Daeraxa-v1.110.0.html.1a72c098.js"),[]).then(({data:e})=>e),"v-5fc19751":()=>s(()=>import("./20231031-savetheclocktower-modern-tree-sitter-part-4.html.ac9522c5.js"),[]).then(({data:e})=>e),"v-879bed80":()=>s(()=>import("./20231109-Daeraxa-NovemberUpdate.html.e36ef3bb.js"),[]).then(({data:e})=>e),"v-168f28ae":()=>s(()=>import("./20231110-savetheclocktower-modern-tree-sitter-part-5.html.bb998452.js"),[]).then(({data:e})=>e),"v-77f85357":()=>s(()=>import("./20231116-Daeraxa-v1.111.0.html.d47bf460.js"),[]).then(({data:e})=>e),"v-04bbc674":()=>s(()=>import("./20231212-Daeraxa-DecemberUpdate.html.0098fa41.js"),[]).then(({data:e})=>e),"v-6409cd37":()=>s(()=>import("./20231216-Daeraxa-v1.112.0.html.cc8771c3.js"),[]).then(({data:e})=>e),"v-15a14140":()=>s(()=>import("./20231219-DeeDeeG-v1.112.1.html.0ec91d1c.js"),[]).then(({data:e})=>e),"v-4f70c30a":()=>s(()=>import("./20240112-Daeraxa-JanuaryUpdate.html.727ac307.js"),[]).then(({data:e})=>e),"v-3500d176":()=>s(()=>import("./20240115-Daeraxa-v1.113.0.html.1359019c.js"),[]).then(({data:e})=>e),"v-67f98f2c":()=>s(()=>import("./20240122-savetheclocktower-modern-tree-sitter-part-6.html.bc065797.js"),[]).then(({data:e})=>e),"v-1181f0fe":()=>s(()=>import("./20240124-mauricioszabo-the-quest-for-electron-lts.html.a8c82ab1.js"),[]).then(({data:e})=>e),"v-703ed5aa":()=>s(()=>import("./20240201-Daeraxa-FebruaryUpdate.html.5a1ade21.js"),[]).then(({data:e})=>e),"v-21124b56":()=>s(()=>import("./20240215-Daeraxa-v1.114.0.html.51cb2c96.js"),[]).then(({data:e})=>e),"v-88d8cd3c":()=>s(()=>import("./20240323-savetheclocktower-v1.115.0.html.f7639cd5.js"),[]).then(({data:e})=>e),"v-3ac80fd4":()=>s(()=>import("./20240417-confused-Techie-v1.116.0.html.7f7778b4.js"),[]).then(({data:e})=>e),"v-83b0b1e4":()=>s(()=>import("./20240520-confused-Techie-v1.117.0.html.022d8530.js"),[]).then(({data:e})=>e),"v-cdb5fcd6":()=>s(()=>import("./20240616-confused-Techie-v1.118.0.html.eef47ab0.js"),[]).then(({data:e})=>e),"v-d8729a94":()=>s(()=>import("./20240717-confused-Techie-v1.119.0.html.002ab8fa.js"),[]).then(({data:e})=>e),"v-147825fb":()=>s(()=>import("./index.html.3e6c1463.js"),[]).then(({data:e})=>e),"v-ed2312e4":()=>s(()=>import("./index.html.e54b2982.js"),[]).then(({data:e})=>e),"v-033b47ca":()=>s(()=>import("./index.html.09d5e6bd.js"),[]).then(({data:e})=>e),"v-0166a572":()=>s(()=>import("./index.html.bb4bd5d9.js"),[]).then(({data:e})=>e),"v-cf57a236":()=>s(()=>import("./index.html.6f6341d2.js"),[]).then(({data:e})=>e),"v-ad7b6e3a":()=>s(()=>import("./index.html.e66450b6.js"),[]).then(({data:e})=>e),"v-4368211c":()=>s(()=>import("./index.html.d51edc97.js"),[]).then(({data:e})=>e),"v-3973978e":()=>s(()=>import("./index.html.47212b5b.js"),[]).then(({data:e})=>e),"v-ad775132":()=>s(()=>import("./index.html.ebeb7c37.js"),[]).then(({data:e})=>e),"v-634d92c5":()=>s(()=>import("./index.html.22737491.js"),[]).then(({data:e})=>e),"v-6c7e3cf8":()=>s(()=>import("./index.html.47c4aeae.js"),[]).then(({data:e})=>e),"v-6e0db890":()=>s(()=>import("./index.html.8df00eba.js"),[]).then(({data:e})=>e),"v-a6cdf728":()=>s(()=>import("./index.html.c211f99a.js"),[]).then(({data:e})=>e),"v-36dcd4ed":()=>s(()=>import("./index.html.58a10652.js"),[]).then(({data:e})=>e),"v-3835d9b2":()=>s(()=>import("./index.html.74bcfb5f.js"),[]).then(({data:e})=>e),"v-6566cb89":()=>s(()=>import("./index.html.f2ffe19c.js"),[]).then(({data:e})=>e),"v-625b0d60":()=>s(()=>import("./index.html.f2a9140b.js"),[]).then(({data:e})=>e),"v-734cbc8c":()=>s(()=>import("./index.html.bfae9c6a.js"),[]).then(({data:e})=>e),"v-a73e70e8":()=>s(()=>import("./index.html.db4b9cf5.js"),[]).then(({data:e})=>e),"v-49ddb9a0":()=>s(()=>import("./index.html.39d9060f.js"),[]).then(({data:e})=>e),"v-923287ec":()=>s(()=>import("./index.html.f7363831.js"),[]).then(({data:e})=>e),"v-7080a64e":()=>s(()=>import("./index.html.b3509800.js"),[]).then(({data:e})=>e),"v-7c51deeb":()=>s(()=>import("./atom-package-server-api.html.0b048bfe.js"),[]).then(({data:e})=>e),"v-1c7613fc":()=>s(()=>import("./atom-update-server-api.html.874968a8.js"),[]).then(({data:e})=>e),"v-6ff500c4":()=>s(()=>import("./configuration-api.html.1ed2295a.js"),[]).then(({data:e})=>e),"v-15f047dd":()=>s(()=>import("./developing-node-modules.html.e77f2326.js"),[]).then(({data:e})=>e),"v-778fbb37":()=>s(()=>import("./how-atom-uses-chromium-snapshots.html.1d190b2c.js"),[]).then(({data:e})=>e),"v-012c1bd2":()=>s(()=>import("./interacting-with-other-packages-via-services.html.2f7e9afe.js"),[]).then(({data:e})=>e),"v-b6a94a42":()=>s(()=>import("./keymaps-in-depth.html.253e4d41.js"),[]).then(({data:e})=>e),"v-ee9016a0":()=>s(()=>import("./maintaining-your-packages.html.66a1baf7.js"),[]).then(({data:e})=>e),"v-4bb0fcfd":()=>s(()=>import("./scoped-settings-scopes-and-scope-descriptors.html.c8332e42.js"),[]).then(({data:e})=>e),"v-5c815036":()=>s(()=>import("./serialization-in-atom.html.9a8a1ff4.js"),[]).then(({data:e})=>e),"v-293dceca":()=>s(()=>import("./summary.html.c86acfc7.js"),[]).then(({data:e})=>e),"v-78782b86":()=>s(()=>import("./atom-in-the-cloud.html.be1543ce.js"),[]).then(({data:e})=>e),"v-bc28a444":()=>s(()=>import("./how-can-i-contribute-to-atom.html.49db6f72.js"),[]).then(({data:e})=>e),"v-71e09326":()=>s(()=>import("./how-can-i-tell-if-subpixel-antialiasing-is-working.html.f1514fae.js"),[]).then(({data:e})=>e),"v-83ab2196":()=>s(()=>import("./how-do-i-accept-input-from-my-program-or-script-when-using-the-script-package.html.eea6eb04.js"),[]).then(({data:e})=>e),"v-58dbd8e4":()=>s(()=>import("./how-do-i-build-or-execute-code-i-ve-written-in-atom.html.7afea0b7.js"),[]).then(({data:e})=>e),"v-69d7b7aa":()=>s(()=>import("./how-do-i-make-atom-recognize-a-file-with-extension-x-as-language-y.html.45ca3345.js"),[]).then(({data:e})=>e),"v-6d023415":()=>s(()=>import("./how-do-i-make-the-welcome-screen-stop-showing-up.html.2f9c8a6b.js"),[]).then(({data:e})=>e),"v-58d2c851":()=>s(()=>import("./how-do-i-preview-web-page-changes-automatically.html.7240862c.js"),[]).then(({data:e})=>e),"v-446213fa":()=>s(()=>import("./how-do-i-turn-on-line-wrap.html.743574d9.js"),[]).then(({data:e})=>e),"v-671ef772":()=>s(()=>import("./how-do-i-uninstall-atom-on-macos.html.8c93daae.js"),[]).then(({data:e})=>e),"v-6e77009f":()=>s(()=>import("./how-do-i-use-a-newline-in-the-result-of-find-and-replace.html.faceab9d.js"),[]).then(({data:e})=>e),"v-50328086":()=>s(()=>import("./i-am-unable-to-update-to-the-latest-version-of-atom-on-macos-how-do-i-fix-this.html.7f10693d.js"),[]).then(({data:e})=>e),"v-52c5c70a":()=>s(()=>import("./i-have-a-question-about-a-specific-atom-community-package-where-is-the-best-place-to-ask-it.html.2fe045ab.js"),[]).then(({data:e})=>e),"v-779d7601":()=>s(()=>import("./i-m-getting-an-error-about-a-self-signed-certificate-what-do-i-do.html.18a6988d.js"),[]).then(({data:e})=>e),"v-6bf81d3a":()=>s(()=>import("./i-m-having-a-problem-with-julia-what-do-i-do.html.acdafc71.js"),[]).then(({data:e})=>e),"v-103f9060":()=>s(()=>import("./i-m-having-a-problem-with-platformio-what-do-i-do.html.4bea6f02.js"),[]).then(({data:e})=>e),"v-f400a296":()=>s(()=>import("./i-m-trying-to-change-my-syntax-colors-from-styles-less-but-it-isn-t-working.html.9615ee41.js"),[]).then(({data:e})=>e),"v-69a4dea8":()=>s(()=>import("./i-m-using-an-international-keyboard-and-keys-that-use-altgr-or-ctrl-alt-aren-t-working.html.d12cc459.js"),[]).then(({data:e})=>e),"v-4ea46d24":()=>s(()=>import("./is-atom-open-source.html.3e2a7899.js"),[]).then(({data:e})=>e),"v-3e42c98a":()=>s(()=>import("./macos-mojave-font-rendering-change.html.d33ba74d.js"),[]).then(({data:e})=>e),"v-5bf72dd3":()=>s(()=>import("./the-menu-bar-disappeared-how-do-i-get-it-back.html.5285b7d6.js"),[]).then(({data:e})=>e),"v-25bdd335":()=>s(()=>import("./what-does-atom-cost.html.f490edb9.js"),[]).then(({data:e})=>e),"v-7e76b4c2":()=>s(()=>import("./what-does-safe-mode-do.html.c28e8226.js"),[]).then(({data:e})=>e),"v-8a4db88a":()=>s(()=>import("./what-is-this-line-on-the-right-in-the-editor-view.html.92c5016f.js"),[]).then(({data:e})=>e),"v-39dbf018":()=>s(()=>import("./what-platforms-does-atom-run-on.html.e67bfbea.js"),[]).then(({data:e})=>e),"v-09b88655":()=>s(()=>import("./what-s-the-difference-between-an-ide-and-an-editor.html.7377822b.js"),[]).then(({data:e})=>e),"v-616d7939":()=>s(()=>import("./why-does-atom-collect-usage-data.html.fd1638d4.js"),[]).then(({data:e})=>e),"v-43d2cead":()=>s(()=>import("./why-does-macos-say-that-atom-wants-to-access-my-calendar-contacts-photos-etc.html.dfe60df2.js"),[]).then(({data:e})=>e),"v-65c8d593":()=>s(()=>import("./why-is-atom-deleting-trailing-whitespace-why-is-there-a-newline-at-the-end-of-the-file.html.009bf895.js"),[]).then(({data:e})=>e),"v-258423d8":()=>s(()=>import("./atom-basics.html.fc2f4a06.js"),[]).then(({data:e})=>e),"v-d48e3644":()=>s(()=>import("./installing-atom.html.4dd4d36f.js"),[]).then(({data:e})=>e),"v-3162898f":()=>s(()=>import("./summary.html.fe9be6bd.js"),[]).then(({data:e})=>e),"v-60602147":()=>s(()=>import("./why-atom.html.4fb90fd5.js"),[]).then(({data:e})=>e),"v-00ff85c6":()=>s(()=>import("./contributing-to-official-atom-packages.html.961e0ab9.js"),[]).then(({data:e})=>e),"v-38bce125":()=>s(()=>import("./converting-from-textmate.html.3a2f1029.js"),[]).then(({data:e})=>e),"v-752e2619":()=>s(()=>import("./creating-a-fork-of-a-core-package-in-atom-atom.html.367b286d.js"),[]).then(({data:e})=>e),"v-7741ac96":()=>s(()=>import("./creating-a-grammar.html.207ba73b.js"),[]).then(({data:e})=>e),"v-41063e28":()=>s(()=>import("./creating-a-legacy-textmate-grammar.html.ed46d264.js"),[]).then(({data:e})=>e),"v-049d4d93":()=>s(()=>import("./creating-a-theme.html.a5fedbd5.js"),[]).then(({data:e})=>e),"v-0762ec4e":()=>s(()=>import("./cross-platform-compatibility.html.12555539.js"),[]).then(({data:e})=>e),"v-28cbb2a8":()=>s(()=>import("./debugging.html.85a630ac.js"),[]).then(({data:e})=>e),"v-bb85f9fa":()=>s(()=>import("./hacking-on-atom-core.html.db966159.js"),[]).then(({data:e})=>e),"v-06c8b972":()=>s(()=>import("./handling-uris.html.ac6b4f1b.js"),[]).then(({data:e})=>e),"v-5746d296":()=>s(()=>import("./iconography.html.cb0903f5.js"),[]).then(({data:e})=>e),"v-be0ab716":()=>s(()=>import("./maintaining-a-fork-of-a-core-package-in-atom-atom.html.b59d275a.js"),[]).then(({data:e})=>e),"v-5d85798e":()=>s(()=>import("./package-active-editor-info.html.6ba548b2.js"),[]).then(({data:e})=>e),"v-4284f843":()=>s(()=>import("./package-modifying-text.html.a1bdfaa6.js"),[]).then(({data:e})=>e),"v-36ba24e9":()=>s(()=>import("./package-word-count.html.49781908.js"),[]).then(({data:e})=>e),"v-5d0a102e":()=>s(()=>import("./publishing.html.76babe74.js"),[]).then(({data:e})=>e),"v-7d723830":()=>s(()=>import("./summary.html.a6f48395.js"),[]).then(({data:e})=>e),"v-7061e28e":()=>s(()=>import("./the-init-file.html.87635cd5.js"),[]).then(({data:e})=>e),"v-5aa3b8b8":()=>s(()=>import("./tools-of-the-trade.html.af54b9d7.js"),[]).then(({data:e})=>e),"v-f2fe7262":()=>s(()=>import("./writing-specs.html.de9c57a1.js"),[]).then(({data:e})=>e),"v-53169d12":()=>s(()=>import("./glossary.html.3aca2517.js"),[]).then(({data:e})=>e),"v-5a059e16":()=>s(()=>import("./removing-shadow-dom-styles.html.f6df46a8.js"),[]).then(({data:e})=>e),"v-8f696676":()=>s(()=>import("./upgrading-your-package.html.901ab9e5.js"),[]).then(({data:e})=>e),"v-416277a8":()=>s(()=>import("./upgrading-your-syntax-theme.html.7650d04c.js"),[]).then(({data:e})=>e),"v-3bf32d2b":()=>s(()=>import("./upgrading-your-ui-theme-or-package-selectors.html.1c26203d.js"),[]).then(({data:e})=>e),"v-2cf2a68a":()=>s(()=>import("./atom-packages.html.52a02db4.js"),[]).then(({data:e})=>e),"v-0fe6afd0":()=>s(()=>import("./atom-selections.html.6ccd154b.js"),[]).then(({data:e})=>e),"v-2c165692":()=>s(()=>import("./autocomplete.html.ec6b60fc.js"),[]).then(({data:e})=>e),"v-61e2f142":()=>s(()=>import("./basic-customization.html.ea54ccd8.js"),[]).then(({data:e})=>e),"v-34299b28":()=>s(()=>import("./editing-and-deleting-text.html.77f55668.js"),[]).then(({data:e})=>e),"v-0311d835":()=>s(()=>import("./find-and-replace.html.4a488eeb.js"),[]).then(({data:e})=>e),"v-6252cab2":()=>s(()=>import("./folding.html.e4e0e277.js"),[]).then(({data:e})=>e),"v-faca997a":()=>s(()=>import("./github-package.html.9d0daecf.js"),[]).then(({data:e})=>e),"v-3ae8d9e8":()=>s(()=>import("./grammar.html.63ce206c.js"),[]).then(({data:e})=>e),"v-4d358836":()=>s(()=>import("./moving-in-atom.html.386980d6.js"),[]).then(({data:e})=>e),"v-0d49e5a8":()=>s(()=>import("./panes.html.8ce248a9.js"),[]).then(({data:e})=>e),"v-52fd2aae":()=>s(()=>import("./pending-pane-items.html.0021df08.js"),[]).then(({data:e})=>e),"v-3aec9d69":()=>s(()=>import("./snippets.html.2349e19b.js"),[]).then(({data:e})=>e),"v-21ac7a6d":()=>s(()=>import("./summary.html.b8077cd9.js"),[]).then(({data:e})=>e),"v-fd5de582":()=>s(()=>import("./version-control-in-atom.html.8e9ff87a.js"),[]).then(({data:e})=>e),"v-783f784b":()=>s(()=>import("./writing-in-atom.html.7240ac7e.js"),[]).then(({data:e})=>e),"v-0aa2ec94":()=>s(()=>import("./index.html.b8230621.js"),[]).then(({data:e})=>e),"v-278e8fc5":()=>s(()=>import("./index.html.d872f358.js"),[]).then(({data:e})=>e),"v-3e3eb0e0":()=>s(()=>import("./index.html.96af6444.js"),[]).then(({data:e})=>e),"v-35eded6e":()=>s(()=>import("./index.html.59090f4c.js"),[]).then(({data:e})=>e),"v-44f94232":()=>s(()=>import("./index.html.c48759b6.js"),[]).then(({data:e})=>e),"v-7412c3f9":()=>s(()=>import("./index.html.076d2cf9.js"),[]).then(({data:e})=>e),"v-4119e722":()=>s(()=>import("./list.html.c39f324c.js"),[]).then(({data:e})=>e),"v-4da9456e":()=>s(()=>import("./release-process.html.e8f6cce9.js"),[]).then(({data:e})=>e),"v-4ebc0363":()=>s(()=>import("./autocomplete-providers.html.39836bcd.js"),[]).then(({data:e})=>e),"v-6df8120e":()=>s(()=>import("./index.html.f452234d.js"),[]).then(({data:e})=>e),"v-5d8b44fc":()=>s(()=>import("./provider-api.html.dd1fb570.js"),[]).then(({data:e})=>e),"v-0c09001f":()=>s(()=>import("./symbolprovider-config-api.html.04edf651.js"),[]).then(({data:e})=>e),"v-04523903":()=>s(()=>import("./index.html.f1050665.js"),[]).then(({data:e})=>e),"v-6a77de6d":()=>s(()=>import("./june-2017.html.c21d6acc.js"),[]).then(({data:e})=>e),"v-3e52d25b":()=>s(()=>import("./incomplete-classpath-warning.html.cee3657e.js"),[]).then(({data:e})=>e),"v-49f587fe":()=>s(()=>import("./index.html.06c71ebc.js"),[]).then(({data:e})=>e),"v-c8081340":()=>s(()=>import("./blog-guide.html.8cda1ca4.js"),[]).then(({data:e})=>e),"v-fa6566c6":()=>s(()=>import("./building.html.d07905be.js"),[]).then(({data:e})=>e),"v-c2b13dfe":()=>s(()=>import("./configuration-files.html.cb664208.js"),[]).then(({data:e})=>e),"v-71e2ac9c":()=>s(()=>import("./document-style.html.c693adb9.js"),[]).then(({data:e})=>e),"v-f38bdb06":()=>s(()=>import("./file-organization.html.96259859.js"),[]).then(({data:e})=>e),"v-4861ba81":()=>s(()=>import("./configuration-api.html.d4ff21e0.js"),[]).then(({data:e})=>e),"v-1a40e700":()=>s(()=>import("./developing-node-modules.html.f010be5c.js"),[]).then(({data:e})=>e),"v-0a930154":()=>s(()=>import("./interacting-with-other-packages-via-services.html.ee16b4f1.js"),[]).then(({data:e})=>e),"v-563ab79c":()=>s(()=>import("./keymaps-in-depth.html.acd7fcb2.js"),[]).then(({data:e})=>e),"v-322ec6da":()=>s(()=>import("./maintaining-your-packages.html.4eb25dae.js"),[]).then(({data:e})=>e),"v-56da0c3a":()=>s(()=>import("./scoped-settings-scopes-and-scope-descriptors.html.3914ff78.js"),[]).then(({data:e})=>e),"v-33739e9c":()=>s(()=>import("./serialization-in-pulsar.html.58da7fb2.js"),[]).then(({data:e})=>e),"v-1b3c033e":()=>s(()=>import("./summary.html.8a21214b.js"),[]).then(({data:e})=>e),"v-e880b502":()=>s(()=>import("./building-pulsar.html.b0e5a0ea.js"),[]).then(({data:e})=>e),"v-13ba24f3":()=>s(()=>import("./contributing-to-official-pulsar-packages.html.26ef0088.js"),[]).then(({data:e})=>e),"v-f3d252b4":()=>s(()=>import("./converting-from-textmate.html.3ede2cec.js"),[]).then(({data:e})=>e),"v-6ef9d234":()=>s(()=>import("./creating-a-fork-of-a-core-package.html.1312dc3f.js"),[]).then(({data:e})=>e),"v-43bb93f6":()=>s(()=>import("./creating-a-grammar.html.d2553095.js"),[]).then(({data:e})=>e),"v-1032ada6":()=>s(()=>import("./creating-a-legacy-textmate-grammar.html.b6783a7b.js"),[]).then(({data:e})=>e),"v-f63d57d8":()=>s(()=>import("./creating-a-theme.html.e0e52d2e.js"),[]).then(({data:e})=>e),"v-78bfd6da":()=>s(()=>import("./cross-platform-compatibility.html.1dd2761e.js"),[]).then(({data:e})=>e),"v-c040e972":()=>s(()=>import("./debugging.html.3bdb6a8e.js"),[]).then(({data:e})=>e),"v-143cd798":()=>s(()=>import("./hacking-on-the-core.html.5c30ae63.js"),[]).then(({data:e})=>e),"v-c277d734":()=>s(()=>import("./handling-uris.html.a6645854.js"),[]).then(({data:e})=>e),"v-544678d8":()=>s(()=>import("./iconography.html.f6f37c01.js"),[]).then(({data:e})=>e),"v-05ecf928":()=>s(()=>import("./maintaining-a-fork-of-a-core-package.html.d089d3ba.js"),[]).then(({data:e})=>e),"v-878fda62":()=>s(()=>import("./package-active-editor-info.html.f8b84ee7.js"),[]).then(({data:e})=>e),"v-e59f4bf8":()=>s(()=>import("./package-modifying-text.html.8104c421.js"),[]).then(({data:e})=>e),"v-93d2e1ac":()=>s(()=>import("./package-word-count.html.7f5691c3.js"),[]).then(({data:e})=>e),"v-3ce2332a":()=>s(()=>import("./publishing.html.68438e12.js"),[]).then(({data:e})=>e),"v-82dca6e2":()=>s(()=>import("./summary.html.7e60db5d.js"),[]).then(({data:e})=>e),"v-69f77fd8":()=>s(()=>import("./the-init-file.html.106c20da.js"),[]).then(({data:e})=>e),"v-4bffba0e":()=>s(()=>import("./tools-of-the-trade.html.9e252ffc.js"),[]).then(({data:e})=>e),"v-fbe0eb5e":()=>s(()=>import("./using-ppm.html.4f5504d2.js"),[]).then(({data:e})=>e),"v-28a937ee":()=>s(()=>import("./writing-specs.html.3849103b.js"),[]).then(({data:e})=>e),"v-76c0c524":()=>s(()=>import("./common-issues.html.dfde4f04.js"),[]).then(({data:e})=>e),"v-495d25f0":()=>s(()=>import("./get-help.html.a5704781.js"),[]).then(({data:e})=>e),"v-8c05c62e":()=>s(()=>import("./installing-pulsar.html.60eab37a.js"),[]).then(({data:e})=>e),"v-6660d212":()=>s(()=>import("./pulsar-basics.html.c9db0718.js"),[]).then(({data:e})=>e),"v-14a6a286":()=>s(()=>import("./summary.html.2480fd44.js"),[]).then(({data:e})=>e),"v-1ca4faf8":()=>s(()=>import("./why-pulsar.html.62b37d90.js"),[]).then(({data:e})=>e),"v-9ad007fc":()=>s(()=>import("./autocomplete.html.a3130bec.js"),[]).then(({data:e})=>e),"v-55d9a5b4":()=>s(()=>import("./basic-customization.html.f10a503f.js"),[]).then(({data:e})=>e),"v-73d97d01":()=>s(()=>import("./editing-and-deleting-text.html.31c0ca86.js"),[]).then(({data:e})=>e),"v-282b3c00":()=>s(()=>import("./find-and-replace.html.f30e892a.js"),[]).then(({data:e})=>e),"v-ea7dfef2":()=>s(()=>import("./folding.html.3ffe9e96.js"),[]).then(({data:e})=>e),"v-a1dd9864":()=>s(()=>import("./github-package.html.90ba8ca2.js"),[]).then(({data:e})=>e),"v-0af9c8e1":()=>s(()=>import("./grammar.html.bc810e1f.js"),[]).then(({data:e})=>e),"v-15cc7078":()=>s(()=>import("./moving-in-pulsar.html.325aabf8.js"),[]).then(({data:e})=>e),"v-76058cbd":()=>s(()=>import("./panes.html.7c7f5559.js"),[]).then(({data:e})=>e),"v-67da6db9":()=>s(()=>import("./pending-pane-items.html.4cd88c45.js"),[]).then(({data:e})=>e),"v-814e3bea":()=>s(()=>import("./pulsar-packages.html.37ad4142.js"),[]).then(({data:e})=>e),"v-bca7e1de":()=>s(()=>import("./pulsar-selections.html.ecb5890c.js"),[]).then(({data:e})=>e),"v-20452234":()=>s(()=>import("./snippets.html.fd11bb00.js"),[]).then(({data:e})=>e),"v-4a1ab042":()=>s(()=>import("./summary.html.6c334e6e.js"),[]).then(({data:e})=>e),"v-13272930":()=>s(()=>import("./version-control-in-pulsar.html.74f8278b.js"),[]).then(({data:e})=>e),"v-e092af18":()=>s(()=>import("./writing-in-pulsar.html.e1c1e98e.js"),[]).then(({data:e})=>e),"v-3706649a":()=>s(()=>import("./404.html.83136f9a.js"),[]).then(({data:e})=>e),"v-5bc93818":()=>s(()=>import("./index.html.1725108a.js"),[]).then(({data:e})=>e),"v-744d024e":()=>s(()=>import("./index.html.e66a9e6f.js"),[]).then(({data:e})=>e),"v-145ac574":()=>s(()=>import("./index.html.a9c04ba9.js"),[]).then(({data:e})=>e),"v-75ed4ea4":()=>s(()=>import("./index.html.3d557021.js"),[]).then(({data:e})=>e),"v-d804e652":()=>s(()=>import("./index.html.4a993420.js"),[]).then(({data:e})=>e),"v-154dc4c4":()=>s(()=>import("./index.html.10df3af1.js"),[]).then(({data:e})=>e),"v-01560935":()=>s(()=>import("./index.html.dcdc22a2.js"),[]).then(({data:e})=>e),"v-65ee6ad2":()=>s(()=>import("./index.html.5925a712.js"),[]).then(({data:e})=>e),"v-0b77a069":()=>s(()=>import("./index.html.7ceb711f.js"),[]).then(({data:e})=>e),"v-65f23183":()=>s(()=>import("./index.html.b03183ad.js"),[]).then(({data:e})=>e),"v-f5401b6a":()=>s(()=>import("./index.html.16e14736.js"),[]).then(({data:e})=>e),"v-586be6a4":()=>s(()=>import("./index.html.4393ebb6.js"),[]).then(({data:e})=>e),"v-007c0ae2":()=>s(()=>import("./index.html.234bb87b.js"),[]).then(({data:e})=>e),"v-109540fd":()=>s(()=>import("./index.html.10d7d674.js"),[]).then(({data:e})=>e),"v-33e16b10":()=>s(()=>import("./index.html.2f30f8af.js"),[]).then(({data:e})=>e),"v-5b38f390":()=>s(()=>import("./index.html.19e04ff4.js"),[]).then(({data:e})=>e),"v-f6458faa":()=>s(()=>import("./index.html.644bc9d9.js"),[]).then(({data:e})=>e),"v-6c28ad56":()=>s(()=>import("./index.html.d36c0486.js"),[]).then(({data:e})=>e),"v-18ed05d5":()=>s(()=>import("./index.html.5f67efaf.js"),[]).then(({data:e})=>e),"v-7d8d32d8":()=>s(()=>import("./index.html.4ae4defc.js"),[]).then(({data:e})=>e),"v-6486a861":()=>s(()=>import("./index.html.fb13f8c6.js"),[]).then(({data:e})=>e),"v-9ffc7398":()=>s(()=>import("./index.html.450e1e45.js"),[]).then(({data:e})=>e),"v-54365ad2":()=>s(()=>import("./index.html.01c707f9.js"),[]).then(({data:e})=>e),"v-28a80d22":()=>s(()=>import("./index.html.130de9b2.js"),[]).then(({data:e})=>e),"v-9c858a88":()=>s(()=>import("./index.html.d8f16f60.js"),[]).then(({data:e})=>e),"v-132a6ac4":()=>s(()=>import("./index.html.79d61236.js"),[]).then(({data:e})=>e),"v-08115088":()=>s(()=>import("./index.html.5eca0895.js"),[]).then(({data:e})=>e),"v-4a89825a":()=>s(()=>import("./index.html.100503fe.js"),[]).then(({data:e})=>e),"v-7ab1f304":()=>s(()=>import("./index.html.0f592a40.js"),[]).then(({data:e})=>e),"v-31f54a17":()=>s(()=>import("./index.html.a8a660db.js"),[]).then(({data:e})=>e),"v-0da0b37b":()=>s(()=>import("./index.html.3aa61e11.js"),[]).then(({data:e})=>e),"v-181c8802":()=>s(()=>import("./index.html.d9369373.js"),[]).then(({data:e})=>e)};function Wn(e,t){const i=Object.create(null),a=e.split(",");for(let n=0;n!!i[n.toLowerCase()]:n=>!!i[n]}function Gn(e){if(ae(e)){const t={};for(let i=0;i{if(i){const a=i.split(Pu);a.length>1&&(t[a[0].trim()]=a[1].trim())}}),t}function Fa(e){let t="";if(ye(e))t=e;else if(ae(e))for(let i=0;iye(e)?e:e==null?"":ae(e)||Ae(e)&&(e.toString===hr||!ce(e.toString))?JSON.stringify(e,cr,2):String(e),cr=(e,t)=>t&&t.__v_isRef?cr(e,t.value):ci(t)?{[`Map(${t.size})`]:[...t.entries()].reduce((i,[a,n])=>(i[`${a} =>`]=n,i),{})}:ur(t)?{[`Set(${t.size})`]:[...t.values()]}:Ae(t)&&!ae(t)&&!pr(t)?String(t):t,Ie={},si=[],ht=()=>{},Ou=()=>!1,Ru=/^on[^a-z]/,Ki=e=>Ru.test(e),Yn=e=>e.startsWith("onUpdate:"),ze=Object.assign,Kn=(e,t)=>{const i=e.indexOf(t);i>-1&&e.splice(i,1)},Cu=Object.prototype.hasOwnProperty,ge=(e,t)=>Cu.call(e,t),ae=Array.isArray,ci=e=>za(e)==="[object Map]",ur=e=>za(e)==="[object Set]",ce=e=>typeof e=="function",ye=e=>typeof e=="string",Zn=e=>typeof e=="symbol",Ae=e=>e!==null&&typeof e=="object",dr=e=>Ae(e)&&ce(e.then)&&ce(e.catch),hr=Object.prototype.toString,za=e=>hr.call(e),Su=e=>za(e).slice(8,-1),pr=e=>za(e)==="[object Object]",Jn=e=>ye(e)&&e!=="NaN"&&e[0]!=="-"&&""+parseInt(e,10)===e,Oi=Wn(",key,ref,ref_for,ref_key,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted"),Ha=e=>{const t=Object.create(null);return i=>t[i]||(t[i]=e(i))},Vu=/-(\w)/g,rt=Ha(e=>e.replace(Vu,(t,i)=>i?i.toUpperCase():"")),Mu=/\B([A-Z])/g,ki=Ha(e=>e.replace(Mu,"-$1").toLowerCase()),Zi=Ha(e=>e.charAt(0).toUpperCase()+e.slice(1)),en=Ha(e=>e?`on${Zi(e)}`:""),Hi=(e,t)=>!Object.is(e,t),tn=(e,t)=>{for(let i=0;i{Object.defineProperty(e,t,{configurable:!0,enumerable:!1,value:i})},Qn=e=>{const t=parseFloat(e);return isNaN(t)?e:t};let Bl;const Fu=()=>Bl||(Bl=typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:typeof global<"u"?global:{});let Ye;class zu{constructor(t=!1){this.detached=t,this.active=!0,this.effects=[],this.cleanups=[],this.parent=Ye,!t&&Ye&&(this.index=(Ye.scopes||(Ye.scopes=[])).push(this)-1)}run(t){if(this.active){const i=Ye;try{return Ye=this,t()}finally{Ye=i}}}on(){Ye=this}off(){Ye=this.parent}stop(t){if(this.active){let i,a;for(i=0,a=this.effects.length;i{const t=new Set(e);return t.w=0,t.n=0,t},mr=e=>(e.w&Nt)>0,gr=e=>(e.n&Nt)>0,qu=({deps:e})=>{if(e.length)for(let t=0;t{const{deps:t}=e;if(t.length){let i=0;for(let a=0;a{(h==="length"||h>=c)&&r.push(d)})}else switch(i!==void 0&&r.push(o.get(i)),t){case"add":ae(e)?Jn(i)&&r.push(o.get("length")):(r.push(o.get(Xt)),ci(e)&&r.push(o.get(yn)));break;case"delete":ae(e)||(r.push(o.get(Xt)),ci(e)&&r.push(o.get(yn)));break;case"set":ci(e)&&r.push(o.get(Xt));break}if(r.length===1)r[0]&&wn(r[0]);else{const c=[];for(const d of r)d&&c.push(...d);wn(Xn(c))}}function wn(e,t){const i=ae(e)?e:[...e];for(const a of i)a.computed&&jl(a);for(const a of i)a.computed||jl(a)}function jl(e,t){(e!==ut||e.allowRecurse)&&(e.scheduler?e.scheduler():e.run())}const Uu=Wn("__proto__,__v_isRef,__isVue"),_r=new Set(Object.getOwnPropertyNames(Symbol).filter(e=>e!=="arguments"&&e!=="caller").map(e=>Symbol[e]).filter(Zn)),ju=tl(),Wu=tl(!1,!0),Gu=tl(!0),Wl=Yu();function Yu(){const e={};return["includes","indexOf","lastIndexOf"].forEach(t=>{e[t]=function(...i){const a=be(this);for(let l=0,o=this.length;l{e[t]=function(...i){yi();const a=be(this)[t].apply(this,i);return wi(),a}}),e}function tl(e=!1,t=!1){return function(a,n,l){if(n==="__v_isReactive")return!e;if(n==="__v_isReadonly")return e;if(n==="__v_isShallow")return t;if(n==="__v_raw"&&l===(e?t?ud:xr:t?wr:yr).get(a))return a;const o=ae(a);if(!e&&o&&ge(Wl,n))return Reflect.get(Wl,n,l);const r=Reflect.get(a,n,l);return(Zn(n)?_r.has(n):Uu(n))||(e||Ze(a,"get",n),t)?r:He(r)?o&&Jn(n)?r:r.value:Ae(r)?e?nl(r):Ut(r):r}}const Ku=br(),Zu=br(!0);function br(e=!1){return function(i,a,n,l){let o=i[a];if(pi(o)&&He(o)&&!He(n))return!1;if(!e&&(!Pa(n)&&!pi(n)&&(o=be(o),n=be(n)),!ae(i)&&He(o)&&!He(n)))return o.value=n,!0;const r=ae(i)&&Jn(a)?Number(a)e,$a=e=>Reflect.getPrototypeOf(e);function pa(e,t,i=!1,a=!1){e=e.__v_raw;const n=be(e),l=be(t);i||(t!==l&&Ze(n,"get",t),Ze(n,"get",l));const{has:o}=$a(n),r=a?il:i?ol:$i;if(o.call(n,t))return r(e.get(t));if(o.call(n,l))return r(e.get(l));e!==n&&e.get(t)}function ma(e,t=!1){const i=this.__v_raw,a=be(i),n=be(e);return t||(e!==n&&Ze(a,"has",e),Ze(a,"has",n)),e===n?i.has(e):i.has(e)||i.has(n)}function ga(e,t=!1){return e=e.__v_raw,!t&&Ze(be(e),"iterate",Xt),Reflect.get(e,"size",e)}function Gl(e){e=be(e);const t=be(this);return $a(t).has.call(t,e)||(t.add(e),Lt(t,"add",e,e)),this}function Yl(e,t){t=be(t);const i=be(this),{has:a,get:n}=$a(i);let l=a.call(i,e);l||(e=be(e),l=a.call(i,e));const o=n.call(i,e);return i.set(e,t),l?Hi(t,o)&&Lt(i,"set",e,t):Lt(i,"add",e,t),this}function Kl(e){const t=be(this),{has:i,get:a}=$a(t);let n=i.call(t,e);n||(e=be(e),n=i.call(t,e)),a&&a.call(t,e);const l=t.delete(e);return n&&Lt(t,"delete",e,void 0),l}function Zl(){const e=be(this),t=e.size!==0,i=e.clear();return t&&Lt(e,"clear",void 0,void 0),i}function va(e,t){return function(a,n){const l=this,o=l.__v_raw,r=be(o),c=t?il:e?ol:$i;return!e&&Ze(r,"iterate",Xt),o.forEach((d,h)=>a.call(n,c(d),c(h),l))}}function fa(e,t,i){return function(...a){const n=this.__v_raw,l=be(n),o=ci(l),r=e==="entries"||e===Symbol.iterator&&o,c=e==="keys"&&o,d=n[e](...a),h=i?il:t?ol:$i;return!t&&Ze(l,"iterate",c?yn:Xt),{next(){const{value:p,done:m}=d.next();return m?{value:p,done:m}:{value:r?[h(p[0]),h(p[1])]:h(p),done:m}},[Symbol.iterator](){return this}}}}function Ot(e){return function(...t){return e==="delete"?!1:this}}function id(){const e={get(l){return pa(this,l)},get size(){return ga(this)},has:ma,add:Gl,set:Yl,delete:Kl,clear:Zl,forEach:va(!1,!1)},t={get(l){return pa(this,l,!1,!0)},get size(){return ga(this)},has:ma,add:Gl,set:Yl,delete:Kl,clear:Zl,forEach:va(!1,!0)},i={get(l){return pa(this,l,!0)},get size(){return ga(this,!0)},has(l){return ma.call(this,l,!0)},add:Ot("add"),set:Ot("set"),delete:Ot("delete"),clear:Ot("clear"),forEach:va(!0,!1)},a={get(l){return pa(this,l,!0,!0)},get size(){return ga(this,!0)},has(l){return ma.call(this,l,!0)},add:Ot("add"),set:Ot("set"),delete:Ot("delete"),clear:Ot("clear"),forEach:va(!0,!0)};return["keys","values","entries",Symbol.iterator].forEach(l=>{e[l]=fa(l,!1,!1),i[l]=fa(l,!0,!1),t[l]=fa(l,!1,!0),a[l]=fa(l,!0,!0)}),[e,i,t,a]}const[ad,nd,ld,od]=id();function al(e,t){const i=t?e?od:ld:e?nd:ad;return(a,n,l)=>n==="__v_isReactive"?!e:n==="__v_isReadonly"?e:n==="__v_raw"?a:Reflect.get(ge(i,n)&&n in a?i:a,n,l)}const rd={get:al(!1,!1)},sd={get:al(!1,!0)},cd={get:al(!0,!1)},yr=new WeakMap,wr=new WeakMap,xr=new WeakMap,ud=new WeakMap;function dd(e){switch(e){case"Object":case"Array":return 1;case"Map":case"Set":case"WeakMap":case"WeakSet":return 2;default:return 0}}function hd(e){return e.__v_skip||!Object.isExtensible(e)?0:dd(Su(e))}function Ut(e){return pi(e)?e:ll(e,!1,kr,rd,yr)}function pd(e){return ll(e,!1,td,sd,wr)}function nl(e){return ll(e,!0,ed,cd,xr)}function ll(e,t,i,a,n){if(!Ae(e)||e.__v_raw&&!(t&&e.__v_isReactive))return e;const l=n.get(e);if(l)return l;const o=hd(e);if(o===0)return e;const r=new Proxy(e,o===2?a:i);return n.set(e,r),r}function ui(e){return pi(e)?ui(e.__v_raw):!!(e&&e.__v_isReactive)}function pi(e){return!!(e&&e.__v_isReadonly)}function Pa(e){return!!(e&&e.__v_isShallow)}function Tr(e){return ui(e)||pi(e)}function be(e){const t=e&&e.__v_raw;return t?be(t):e}function Er(e){return Ea(e,"__v_skip",!0),e}const $i=e=>Ae(e)?Ut(e):e,ol=e=>Ae(e)?nl(e):e;function Pr(e){Ht&&ut&&(e=be(e),fr(e.dep||(e.dep=Xn())))}function Lr(e,t){e=be(e),e.dep&&wn(e.dep)}function He(e){return!!(e&&e.__v_isRef===!0)}function ne(e){return Ar(e,!1)}function Dr(e){return Ar(e,!0)}function Ar(e,t){return He(e)?e:new md(e,t)}class md{constructor(t,i){this.__v_isShallow=i,this.dep=void 0,this.__v_isRef=!0,this._rawValue=i?t:be(t),this._value=i?t:$i(t)}get value(){return Pr(this),this._value}set value(t){const i=this.__v_isShallow||Pa(t)||pi(t);t=i?t:be(t),Hi(t,this._rawValue)&&(this._rawValue=t,this._value=i?t:$i(t),Lr(this))}}function at(e){return He(e)?e.value:e}const gd={get:(e,t,i)=>at(Reflect.get(e,t,i)),set:(e,t,i,a)=>{const n=e[t];return He(n)&&!He(i)?(n.value=i,!0):Reflect.set(e,t,i,a)}};function Ir(e){return ui(e)?e:new Proxy(e,gd)}function vd(e){const t=ae(e)?new Array(e.length):{};for(const i in e)t[i]=Ji(e,i);return t}class fd{constructor(t,i,a){this._object=t,this._key=i,this._defaultValue=a,this.__v_isRef=!0}get value(){const t=this._object[this._key];return t===void 0?this._defaultValue:t}set value(t){this._object[this._key]=t}}function Ji(e,t,i){const a=e[t];return He(a)?a:new fd(e,t,i)}var Or;class _d{constructor(t,i,a,n){this._setter=i,this.dep=void 0,this.__v_isRef=!0,this[Or]=!1,this._dirty=!0,this.effect=new el(t,()=>{this._dirty||(this._dirty=!0,Lr(this))}),this.effect.computed=this,this.effect.active=this._cacheable=!n,this.__v_isReadonly=a}get value(){const t=be(this);return Pr(t),(t._dirty||!t._cacheable)&&(t._dirty=!1,t._value=t.effect.run()),t._value}set value(t){this._setter(t)}}Or="__v_isReadonly";function bd(e,t,i=!1){let a,n;const l=ce(e);return l?(a=e,n=ht):(a=e.get,n=e.set),new _d(a,n,l||!n,i)}function $t(e,t,i,a){let n;try{n=a?e(...a):e()}catch(l){Qi(l,t,i)}return n}function nt(e,t,i,a){if(ce(e)){const l=$t(e,t,i,a);return l&&dr(l)&&l.catch(o=>{Qi(o,t,i)}),l}const n=[];for(let l=0;l>>1;qi(Ne[a])_t&&Ne.splice(t,1)}function xd(e){ae(e)?di.push(...e):(!Et||!Et.includes(e,e.allowRecurse?Kt+1:Kt))&&di.push(e),Cr()}function Jl(e,t=Ni?_t+1:0){for(;tqi(i)-qi(a)),Kt=0;Kte.id==null?1/0:e.id,Td=(e,t)=>{const i=qi(e)-qi(t);if(i===0){if(e.pre&&!t.pre)return-1;if(t.pre&&!e.pre)return 1}return i};function Sr(e){xn=!1,Ni=!0,Ne.sort(Td);const t=ht;try{for(_t=0;_tye(f)?f.trim():f)),p&&(n=i.map(Qn))}let r,c=a[r=en(t)]||a[r=en(rt(t))];!c&&l&&(c=a[r=en(ki(t))]),c&&nt(c,e,6,n);const d=a[r+"Once"];if(d){if(!e.emitted)e.emitted={};else if(e.emitted[r])return;e.emitted[r]=!0,nt(d,e,6,n)}}function Vr(e,t,i=!1){const a=t.emitsCache,n=a.get(e);if(n!==void 0)return n;const l=e.emits;let o={},r=!1;if(!ce(e)){const c=d=>{const h=Vr(d,t,!0);h&&(r=!0,ze(o,h))};!i&&t.mixins.length&&t.mixins.forEach(c),e.extends&&c(e.extends),e.mixins&&e.mixins.forEach(c)}return!l&&!r?(Ae(e)&&a.set(e,null),null):(ae(l)?l.forEach(c=>o[c]=null):ze(o,l),Ae(e)&&a.set(e,o),o)}function qa(e,t){return!e||!Ki(t)?!1:(t=t.slice(2).replace(/Once$/,""),ge(e,t[0].toLowerCase()+t.slice(1))||ge(e,ki(t))||ge(e,t))}let it=null,Mr=null;function Da(e){const t=it;return it=e,Mr=e&&e.type.__scopeId||null,t}function Pd(e,t=it,i){if(!t||e._n)return e;const a=(...n)=>{a._d&&ro(-1);const l=Da(t);let o;try{o=e(...n)}finally{Da(l),a._d&&ro(1)}return o};return a._n=!0,a._c=!0,a._d=!0,a}function an(e){const{type:t,vnode:i,proxy:a,withProxy:n,props:l,propsOptions:[o],slots:r,attrs:c,emit:d,render:h,renderCache:p,data:m,setupState:f,ctx:y,inheritAttrs:_}=e;let x,b;const w=Da(e);try{if(i.shapeFlag&4){const C=n||a;x=ct(h.call(C,C,p,l,f,m,y)),b=c}else{const C=t;x=ct(C.length>1?C(l,{attrs:c,slots:r,emit:d}):C(l,null)),b=t.props?c:Ld(c)}}catch(C){Vi.length=0,Qi(C,e,1),x=Oe(lt)}let E=x;if(b&&_!==!1){const C=Object.keys(b),{shapeFlag:F}=E;C.length&&F&7&&(o&&C.some(Yn)&&(b=Dd(b,o)),E=qt(E,b))}return i.dirs&&(E=qt(E),E.dirs=E.dirs?E.dirs.concat(i.dirs):i.dirs),i.transition&&(E.transition=i.transition),x=E,Da(w),x}const Ld=e=>{let t;for(const i in e)(i==="class"||i==="style"||Ki(i))&&((t||(t={}))[i]=e[i]);return t},Dd=(e,t)=>{const i={};for(const a in e)(!Yn(a)||!(a.slice(9)in t))&&(i[a]=e[a]);return i};function Ad(e,t,i){const{props:a,children:n,component:l}=e,{props:o,children:r,patchFlag:c}=t,d=l.emitsOptions;if(t.dirs||t.transition)return!0;if(i&&c>=0){if(c&1024)return!0;if(c&16)return a?Ql(a,o,d):!!o;if(c&8){const h=t.dynamicProps;for(let p=0;pe.__isSuspense;function Fr(e,t){t&&t.pendingBranch?ae(e)?t.effects.push(...e):t.effects.push(e):xd(e)}function Ke(e,t){if(Fe){let i=Fe.provides;const a=Fe.parent&&Fe.parent.provides;a===i&&(i=Fe.provides=Object.create(a)),i[e]=t}}function we(e,t,i=!1){const a=Fe||it;if(a){const n=a.parent==null?a.vnode.appContext&&a.vnode.appContext.provides:a.parent.provides;if(n&&e in n)return n[e];if(arguments.length>1)return i&&ce(t)?t.call(a.proxy):t}}function Rd(e,t){return cl(e,null,t)}const _a={};function De(e,t,i){return cl(e,t,i)}function cl(e,t,{immediate:i,deep:a,flush:n,onTrack:l,onTrigger:o}=Ie){const r=Fe;let c,d=!1,h=!1;if(He(e)?(c=()=>e.value,d=Pa(e)):ui(e)?(c=()=>e,a=!0):ae(e)?(h=!0,d=e.some(E=>ui(E)||Pa(E)),c=()=>e.map(E=>{if(He(E))return E.value;if(ui(E))return oi(E);if(ce(E))return $t(E,r,2)})):ce(e)?t?c=()=>$t(e,r,2):c=()=>{if(!(r&&r.isUnmounted))return p&&p(),nt(e,r,3,[m])}:c=ht,t&&a){const E=c;c=()=>oi(E())}let p,m=E=>{p=b.onStop=()=>{$t(E,r,4)}},f;if(vi)if(m=ht,t?i&&nt(t,r,3,[c(),h?[]:void 0,m]):c(),n==="sync"){const E=Ph();f=E.__watcherHandles||(E.__watcherHandles=[])}else return ht;let y=h?new Array(e.length).fill(_a):_a;const _=()=>{if(!!b.active)if(t){const E=b.run();(a||d||(h?E.some((C,F)=>Hi(C,y[F])):Hi(E,y)))&&(p&&p(),nt(t,r,3,[E,y===_a?void 0:h&&y[0]===_a?[]:y,m]),y=E)}else b.run()};_.allowRecurse=!!t;let x;n==="sync"?x=_:n==="post"?x=()=>Ue(_,r&&r.suspense):(_.pre=!0,r&&(_.id=r.uid),x=()=>Na(_));const b=new el(c,x);t?i?_():y=b.run():n==="post"?Ue(b.run.bind(b),r&&r.suspense):b.run();const w=()=>{b.stop(),r&&r.scope&&Kn(r.scope.effects,b)};return f&&f.push(w),w}function Cd(e,t,i){const a=this.proxy,n=ye(e)?e.includes(".")?zr(a,e):()=>a[e]:e.bind(a,a);let l;ce(t)?l=t:(l=t.handler,i=t);const o=Fe;gi(this);const r=cl(n,l.bind(a),i);return o?gi(o):ei(),r}function zr(e,t){const i=t.split(".");return()=>{let a=e;for(let n=0;n{oi(i,t)});else if(pr(e))for(const i in e)oi(e[i],t);return e}function Hr(){const e={isMounted:!1,isLeaving:!1,isUnmounting:!1,leavingVNodes:new Map};return Se(()=>{e.isMounted=!0}),xi(()=>{e.isUnmounting=!0}),e}const et=[Function,Array],Sd={name:"BaseTransition",props:{mode:String,appear:Boolean,persisted:Boolean,onBeforeEnter:et,onEnter:et,onAfterEnter:et,onEnterCancelled:et,onBeforeLeave:et,onLeave:et,onAfterLeave:et,onLeaveCancelled:et,onBeforeAppear:et,onAppear:et,onAfterAppear:et,onAppearCancelled:et},setup(e,{slots:t}){const i=ea(),a=Hr();let n;return()=>{const l=t.default&&ul(t.default(),!0);if(!l||!l.length)return;let o=l[0];if(l.length>1){for(const _ of l)if(_.type!==lt){o=_;break}}const r=be(e),{mode:c}=r;if(a.isLeaving)return nn(o);const d=Xl(o);if(!d)return nn(o);const h=Bi(d,r,a,i);Ui(d,h);const p=i.subTree,m=p&&Xl(p);let f=!1;const{getTransitionKey:y}=d.type;if(y){const _=y();n===void 0?n=_:_!==n&&(n=_,f=!0)}if(m&&m.type!==lt&&(!Zt(d,m)||f)){const _=Bi(m,r,a,i);if(Ui(m,_),c==="out-in")return a.isLeaving=!0,_.afterLeave=()=>{a.isLeaving=!1,i.update.active!==!1&&i.update()},nn(o);c==="in-out"&&d.type!==lt&&(_.delayLeave=(x,b,w)=>{const E=Nr(a,m);E[String(m.key)]=m,x._leaveCb=()=>{b(),x._leaveCb=void 0,delete h.delayedLeave},h.delayedLeave=w})}return o}}},$r=Sd;function Nr(e,t){const{leavingVNodes:i}=e;let a=i.get(t.type);return a||(a=Object.create(null),i.set(t.type,a)),a}function Bi(e,t,i,a){const{appear:n,mode:l,persisted:o=!1,onBeforeEnter:r,onEnter:c,onAfterEnter:d,onEnterCancelled:h,onBeforeLeave:p,onLeave:m,onAfterLeave:f,onLeaveCancelled:y,onBeforeAppear:_,onAppear:x,onAfterAppear:b,onAppearCancelled:w}=t,E=String(e.key),C=Nr(i,e),F=(A,N)=>{A&&nt(A,a,9,N)},S=(A,N)=>{const z=N[1];F(A,N),ae(A)?A.every(le=>le.length<=1)&&z():A.length<=1&&z()},T={mode:l,persisted:o,beforeEnter(A){let N=r;if(!i.isMounted)if(n)N=_||r;else return;A._leaveCb&&A._leaveCb(!0);const z=C[E];z&&Zt(e,z)&&z.el._leaveCb&&z.el._leaveCb(),F(N,[A])},enter(A){let N=c,z=d,le=h;if(!i.isMounted)if(n)N=x||c,z=b||d,le=w||h;else return;let Z=!1;const B=A._enterCb=D=>{Z||(Z=!0,D?F(le,[A]):F(z,[A]),T.delayedLeave&&T.delayedLeave(),A._enterCb=void 0)};N?S(N,[A,B]):B()},leave(A,N){const z=String(e.key);if(A._enterCb&&A._enterCb(!0),i.isUnmounting)return N();F(p,[A]);let le=!1;const Z=A._leaveCb=B=>{le||(le=!0,N(),B?F(y,[A]):F(f,[A]),A._leaveCb=void 0,C[z]===e&&delete C[z])};C[z]=e,m?S(m,[A,Z]):Z()},clone(A){return Bi(A,t,i,a)}};return T}function nn(e){if(Xi(e))return e=qt(e),e.children=null,e}function Xl(e){return Xi(e)?e.children?e.children[0]:void 0:e}function Ui(e,t){e.shapeFlag&6&&e.component?Ui(e.component.subTree,t):e.shapeFlag&128?(e.ssContent.transition=t.clone(e.ssContent),e.ssFallback.transition=t.clone(e.ssFallback)):e.transition=t}function ul(e,t=!1,i){let a=[],n=0;for(let l=0;l1)for(let l=0;l!!e.type.__asyncLoader;function g(e){ce(e)&&(e={loader:e});const{loader:t,loadingComponent:i,errorComponent:a,delay:n=200,timeout:l,suspensible:o=!0,onError:r}=e;let c=null,d,h=0;const p=()=>(h++,c=null,m()),m=()=>{let f;return c||(f=c=t().catch(y=>{if(y=y instanceof Error?y:new Error(String(y)),r)return new Promise((_,x)=>{r(y,()=>_(p()),()=>x(y),h+1)});throw y}).then(y=>f!==c&&c?c:(y&&(y.__esModule||y[Symbol.toStringTag]==="Module")&&(y=y.default),d=y,y)))};return K({name:"AsyncComponentWrapper",__asyncLoader:m,get __asyncResolved(){return d},setup(){const f=Fe;if(d)return()=>ln(d,f);const y=w=>{c=null,Qi(w,f,13,!a)};if(o&&f.suspense||vi)return m().then(w=>()=>ln(w,f)).catch(w=>(y(w),()=>a?Oe(a,{error:w}):null));const _=ne(!1),x=ne(),b=ne(!!n);return n&&setTimeout(()=>{b.value=!1},n),l!=null&&setTimeout(()=>{if(!_.value&&!x.value){const w=new Error(`Async component timed out after ${l}ms.`);y(w),x.value=w}},l),m().then(()=>{_.value=!0,f.parent&&Xi(f.parent.vnode)&&Na(f.parent.update)}).catch(w=>{y(w),x.value=w}),()=>{if(_.value&&d)return ln(d,f);if(x.value&&a)return Oe(a,{error:x.value});if(i&&!b.value)return Oe(i)}}})}function ln(e,t){const{ref:i,props:a,children:n,ce:l}=t.vnode,o=Oe(e,a,n);return o.ref=i,o.ce=l,delete t.vnode.ce,o}const Xi=e=>e.type.__isKeepAlive;function Vd(e,t){qr(e,"a",t)}function Md(e,t){qr(e,"da",t)}function qr(e,t,i=Fe){const a=e.__wdc||(e.__wdc=()=>{let n=i;for(;n;){if(n.isDeactivated)return;n=n.parent}return e()});if(Ba(t,a,i),i){let n=i.parent;for(;n&&n.parent;)Xi(n.parent.vnode)&&Fd(a,t,i,n),n=n.parent}}function Fd(e,t,i,a){const n=Ba(t,e,a,!0);dl(()=>{Kn(a[t],n)},i)}function Ba(e,t,i=Fe,a=!1){if(i){const n=i[e]||(i[e]=[]),l=t.__weh||(t.__weh=(...o)=>{if(i.isUnmounted)return;yi(),gi(i);const r=nt(t,i,e,o);return ei(),wi(),r});return a?n.unshift(l):n.push(l),l}}const At=e=>(t,i=Fe)=>(!vi||e==="sp")&&Ba(e,(...a)=>t(...a),i),zd=At("bm"),Se=At("m"),Hd=At("bu"),Br=At("u"),xi=At("bum"),dl=At("um"),$d=At("sp"),Nd=At("rtg"),qd=At("rtc");function Bd(e,t=Fe){Ba("ec",e,t)}function ft(e,t,i,a){const n=e.dirs,l=t&&t.dirs;for(let o=0;ot(o,r,void 0,l&&l[r]));else{const o=Object.keys(e);n=new Array(o.length);for(let r=0,c=o.length;re?is(e)?gl(e)||e.proxy:Tn(e.parent):null,Ci=ze(Object.create(null),{$:e=>e,$el:e=>e.vnode.el,$data:e=>e.data,$props:e=>e.props,$attrs:e=>e.attrs,$slots:e=>e.slots,$refs:e=>e.refs,$parent:e=>Tn(e.parent),$root:e=>Tn(e.root),$emit:e=>e.emit,$options:e=>hl(e),$forceUpdate:e=>e.f||(e.f=()=>Na(e.update)),$nextTick:e=>e.n||(e.n=sl.bind(e.proxy)),$watch:e=>Cd.bind(e)}),on=(e,t)=>e!==Ie&&!e.__isScriptSetup&&ge(e,t),Gd={get({_:e},t){const{ctx:i,setupState:a,data:n,props:l,accessCache:o,type:r,appContext:c}=e;let d;if(t[0]!=="$"){const f=o[t];if(f!==void 0)switch(f){case 1:return a[t];case 2:return n[t];case 4:return i[t];case 3:return l[t]}else{if(on(a,t))return o[t]=1,a[t];if(n!==Ie&&ge(n,t))return o[t]=2,n[t];if((d=e.propsOptions[0])&&ge(d,t))return o[t]=3,l[t];if(i!==Ie&&ge(i,t))return o[t]=4,i[t];En&&(o[t]=0)}}const h=Ci[t];let p,m;if(h)return t==="$attrs"&&Ze(e,"get",t),h(e);if((p=r.__cssModules)&&(p=p[t]))return p;if(i!==Ie&&ge(i,t))return o[t]=4,i[t];if(m=c.config.globalProperties,ge(m,t))return m[t]},set({_:e},t,i){const{data:a,setupState:n,ctx:l}=e;return on(n,t)?(n[t]=i,!0):a!==Ie&&ge(a,t)?(a[t]=i,!0):ge(e.props,t)||t[0]==="$"&&t.slice(1)in e?!1:(l[t]=i,!0)},has({_:{data:e,setupState:t,accessCache:i,ctx:a,appContext:n,propsOptions:l}},o){let r;return!!i[o]||e!==Ie&&ge(e,o)||on(t,o)||(r=l[0])&&ge(r,o)||ge(a,o)||ge(Ci,o)||ge(n.config.globalProperties,o)},defineProperty(e,t,i){return i.get!=null?e._.accessCache[t]=0:ge(i,"value")&&this.set(e,t,i.value,null),Reflect.defineProperty(e,t,i)}};let En=!0;function Yd(e){const t=hl(e),i=e.proxy,a=e.ctx;En=!1,t.beforeCreate&&to(t.beforeCreate,e,"bc");const{data:n,computed:l,methods:o,watch:r,provide:c,inject:d,created:h,beforeMount:p,mounted:m,beforeUpdate:f,updated:y,activated:_,deactivated:x,beforeDestroy:b,beforeUnmount:w,destroyed:E,unmounted:C,render:F,renderTracked:S,renderTriggered:T,errorCaptured:A,serverPrefetch:N,expose:z,inheritAttrs:le,components:Z,directives:B,filters:D}=t;if(d&&Kd(d,a,null,e.appContext.config.unwrapInjectedRef),o)for(const M in o){const J=o[M];ce(J)&&(a[M]=J.bind(i))}if(n){const M=n.call(i,i);Ae(M)&&(e.data=Ut(M))}if(En=!0,l)for(const M in l){const J=l[M],ee=ce(J)?J.bind(i,i):ce(J.get)?J.get.bind(i,i):ht,de=!ce(J)&&ce(J.set)?J.set.bind(i):ht,fe=P({get:ee,set:de});Object.defineProperty(a,M,{enumerable:!0,configurable:!0,get:()=>fe.value,set:ue=>fe.value=ue})}if(r)for(const M in r)jr(r[M],a,i,M);if(c){const M=ce(c)?c.call(i):c;Reflect.ownKeys(M).forEach(J=>{Ke(J,M[J])})}h&&to(h,e,"c");function W(M,J){ae(J)?J.forEach(ee=>M(ee.bind(i))):J&&M(J.bind(i))}if(W(zd,p),W(Se,m),W(Hd,f),W(Br,y),W(Vd,_),W(Md,x),W(Bd,A),W(qd,S),W(Nd,T),W(xi,w),W(dl,C),W($d,N),ae(z))if(z.length){const M=e.exposed||(e.exposed={});z.forEach(J=>{Object.defineProperty(M,J,{get:()=>i[J],set:ee=>i[J]=ee})})}else e.exposed||(e.exposed={});F&&e.render===ht&&(e.render=F),le!=null&&(e.inheritAttrs=le),Z&&(e.components=Z),B&&(e.directives=B)}function Kd(e,t,i=ht,a=!1){ae(e)&&(e=Pn(e));for(const n in e){const l=e[n];let o;Ae(l)?"default"in l?o=we(l.from||n,l.default,!0):o=we(l.from||n):o=we(l),He(o)&&a?Object.defineProperty(t,n,{enumerable:!0,configurable:!0,get:()=>o.value,set:r=>o.value=r}):t[n]=o}}function to(e,t,i){nt(ae(e)?e.map(a=>a.bind(t.proxy)):e.bind(t.proxy),t,i)}function jr(e,t,i,a){const n=a.includes(".")?zr(i,a):()=>i[a];if(ye(e)){const l=t[e];ce(l)&&De(n,l)}else if(ce(e))De(n,e.bind(i));else if(Ae(e))if(ae(e))e.forEach(l=>jr(l,t,i,a));else{const l=ce(e.handler)?e.handler.bind(i):t[e.handler];ce(l)&&De(n,l,e)}}function hl(e){const t=e.type,{mixins:i,extends:a}=t,{mixins:n,optionsCache:l,config:{optionMergeStrategies:o}}=e.appContext,r=l.get(t);let c;return r?c=r:!n.length&&!i&&!a?c=t:(c={},n.length&&n.forEach(d=>Aa(c,d,o,!0)),Aa(c,t,o)),Ae(t)&&l.set(t,c),c}function Aa(e,t,i,a=!1){const{mixins:n,extends:l}=t;l&&Aa(e,l,i,!0),n&&n.forEach(o=>Aa(e,o,i,!0));for(const o in t)if(!(a&&o==="expose")){const r=Zd[o]||i&&i[o];e[o]=r?r(e[o],t[o]):t[o]}return e}const Zd={data:io,props:Gt,emits:Gt,methods:Gt,computed:Gt,beforeCreate:qe,created:qe,beforeMount:qe,mounted:qe,beforeUpdate:qe,updated:qe,beforeDestroy:qe,beforeUnmount:qe,destroyed:qe,unmounted:qe,activated:qe,deactivated:qe,errorCaptured:qe,serverPrefetch:qe,components:Gt,directives:Gt,watch:Qd,provide:io,inject:Jd};function io(e,t){return t?e?function(){return ze(ce(e)?e.call(this,this):e,ce(t)?t.call(this,this):t)}:t:e}function Jd(e,t){return Gt(Pn(e),Pn(t))}function Pn(e){if(ae(e)){const t={};for(let i=0;i0)&&!(o&16)){if(o&8){const h=e.vnode.dynamicProps;for(let p=0;p{c=!0;const[m,f]=Gr(p,t,!0);ze(o,m),f&&r.push(...f)};!i&&t.mixins.length&&t.mixins.forEach(h),e.extends&&h(e.extends),e.mixins&&e.mixins.forEach(h)}if(!l&&!c)return Ae(e)&&a.set(e,si),si;if(ae(l))for(let h=0;h-1,f[1]=_<0||y<_,(y>-1||ge(f,"default"))&&r.push(p)}}}const d=[o,r];return Ae(e)&&a.set(e,d),d}function ao(e){return e[0]!=="$"}function no(e){const t=e&&e.toString().match(/^\s*function (\w+)/);return t?t[1]:e===null?"null":""}function lo(e,t){return no(e)===no(t)}function oo(e,t){return ae(t)?t.findIndex(i=>lo(i,e)):ce(t)&&lo(t,e)?0:-1}const Yr=e=>e[0]==="_"||e==="$stable",pl=e=>ae(e)?e.map(ct):[ct(e)],th=(e,t,i)=>{if(t._n)return t;const a=Pd((...n)=>pl(t(...n)),i);return a._c=!1,a},Kr=(e,t,i)=>{const a=e._ctx;for(const n in e){if(Yr(n))continue;const l=e[n];if(ce(l))t[n]=th(n,l,a);else if(l!=null){const o=pl(l);t[n]=()=>o}}},Zr=(e,t)=>{const i=pl(t);e.slots.default=()=>i},ih=(e,t)=>{if(e.vnode.shapeFlag&32){const i=t._;i?(e.slots=be(t),Ea(t,"_",i)):Kr(t,e.slots={})}else e.slots={},t&&Zr(e,t);Ea(e.slots,Ua,1)},ah=(e,t,i)=>{const{vnode:a,slots:n}=e;let l=!0,o=Ie;if(a.shapeFlag&32){const r=t._;r?i&&r===1?l=!1:(ze(n,t),!i&&r===1&&delete n._):(l=!t.$stable,Kr(t,n)),o=t}else t&&(Zr(e,t),o={default:1});if(l)for(const r in n)!Yr(r)&&!(r in o)&&delete n[r]};function Jr(){return{app:null,config:{isNativeTag:Ou,performance:!1,globalProperties:{},optionMergeStrategies:{},errorHandler:void 0,warnHandler:void 0,compilerOptions:{}},mixins:[],components:{},directives:{},provides:Object.create(null),optionsCache:new WeakMap,propsCache:new WeakMap,emitsCache:new WeakMap}}let nh=0;function lh(e,t){return function(a,n=null){ce(a)||(a=Object.assign({},a)),n!=null&&!Ae(n)&&(n=null);const l=Jr(),o=new Set;let r=!1;const c=l.app={_uid:nh++,_component:a,_props:n,_container:null,_context:l,_instance:null,version:Lh,get config(){return l.config},set config(d){},use(d,...h){return o.has(d)||(d&&ce(d.install)?(o.add(d),d.install(c,...h)):ce(d)&&(o.add(d),d(c,...h))),c},mixin(d){return l.mixins.includes(d)||l.mixins.push(d),c},component(d,h){return h?(l.components[d]=h,c):l.components[d]},directive(d,h){return h?(l.directives[d]=h,c):l.directives[d]},mount(d,h,p){if(!r){const m=Oe(a,n);return m.appContext=l,h&&t?t(m,d):e(m,d,p),r=!0,c._container=d,d.__vue_app__=c,gl(m.component)||m.component.proxy}},unmount(){r&&(e(null,c._container),delete c._container.__vue_app__)},provide(d,h){return l.provides[d]=h,c}};return c}}function Ia(e,t,i,a,n=!1){if(ae(e)){e.forEach((m,f)=>Ia(m,t&&(ae(t)?t[f]:t),i,a,n));return}if(Ri(a)&&!n)return;const l=a.shapeFlag&4?gl(a.component)||a.component.proxy:a.el,o=n?null:l,{i:r,r:c}=e,d=t&&t.r,h=r.refs===Ie?r.refs={}:r.refs,p=r.setupState;if(d!=null&&d!==c&&(ye(d)?(h[d]=null,ge(p,d)&&(p[d]=null)):He(d)&&(d.value=null)),ce(c))$t(c,r,12,[o,h]);else{const m=ye(c),f=He(c);if(m||f){const y=()=>{if(e.f){const _=m?ge(p,c)?p[c]:h[c]:c.value;n?ae(_)&&Kn(_,l):ae(_)?_.includes(l)||_.push(l):m?(h[c]=[l],ge(p,c)&&(p[c]=h[c])):(c.value=[l],e.k&&(h[e.k]=c.value))}else m?(h[c]=o,ge(p,c)&&(p[c]=o)):f&&(c.value=o,e.k&&(h[e.k]=o))};o?(y.id=-1,Ue(y,i)):y()}}}let Rt=!1;const ba=e=>/svg/.test(e.namespaceURI)&&e.tagName!=="foreignObject",ka=e=>e.nodeType===8;function oh(e){const{mt:t,p:i,o:{patchProp:a,createText:n,nextSibling:l,parentNode:o,remove:r,insert:c,createComment:d}}=e,h=(b,w)=>{if(!w.hasChildNodes()){i(null,b,w),La(),w._vnode=b;return}Rt=!1,p(w.firstChild,b,null,null,null),La(),w._vnode=b,Rt&&console.error("Hydration completed but contains mismatches.")},p=(b,w,E,C,F,S=!1)=>{const T=ka(b)&&b.data==="[",A=()=>_(b,w,E,C,F,T),{type:N,ref:z,shapeFlag:le,patchFlag:Z}=w;let B=b.nodeType;w.el=b,Z===-2&&(S=!1,w.dynamicChildren=null);let D=null;switch(N){case mi:B!==3?w.children===""?(c(w.el=n(""),o(b),b),D=b):D=A():(b.data!==w.children&&(Rt=!0,b.data=w.children),D=l(b));break;case lt:B!==8||T?D=A():D=l(b);break;case Si:if(T&&(b=l(b),B=b.nodeType),B===1||B===3){D=b;const H=!w.children.length;for(let W=0;W{S=S||!!w.dynamicChildren;const{type:T,props:A,patchFlag:N,shapeFlag:z,dirs:le}=w,Z=T==="input"&&le||T==="option";if(Z||N!==-1){if(le&&ft(w,null,E,"created"),A)if(Z||!S||N&48)for(const D in A)(Z&&D.endsWith("value")||Ki(D)&&!Oi(D))&&a(b,D,null,A[D],!1,void 0,E);else A.onClick&&a(b,"onClick",null,A.onClick,!1,void 0,E);let B;if((B=A&&A.onVnodeBeforeMount)&&tt(B,E,w),le&&ft(w,null,E,"beforeMount"),((B=A&&A.onVnodeMounted)||le)&&Fr(()=>{B&&tt(B,E,w),le&&ft(w,null,E,"mounted")},C),z&16&&!(A&&(A.innerHTML||A.textContent))){let D=f(b.firstChild,w,b,E,C,F,S);for(;D;){Rt=!0;const H=D;D=D.nextSibling,r(H)}}else z&8&&b.textContent!==w.children&&(Rt=!0,b.textContent=w.children)}return b.nextSibling},f=(b,w,E,C,F,S,T)=>{T=T||!!w.dynamicChildren;const A=w.children,N=A.length;for(let z=0;z{const{slotScopeIds:T}=w;T&&(F=F?F.concat(T):T);const A=o(b),N=f(l(b),w,A,E,C,F,S);return N&&ka(N)&&N.data==="]"?l(w.anchor=N):(Rt=!0,c(w.anchor=d("]"),A,N),N)},_=(b,w,E,C,F,S)=>{if(Rt=!0,w.el=null,S){const N=x(b);for(;;){const z=l(b);if(z&&z!==N)r(z);else break}}const T=l(b),A=o(b);return r(b),i(null,w,A,T,E,C,ba(A),F),T},x=b=>{let w=0;for(;b;)if(b=l(b),b&&ka(b)&&(b.data==="["&&w++,b.data==="]")){if(w===0)return l(b);w--}return b};return[h,p]}const Ue=Fr;function rh(e){return sh(e,oh)}function sh(e,t){const i=Fu();i.__VUE__=!0;const{insert:a,remove:n,patchProp:l,createElement:o,createText:r,createComment:c,setText:d,setElementText:h,parentNode:p,nextSibling:m,setScopeId:f=ht,insertStaticContent:y}=e,_=(v,k,L,O=null,R=null,q=null,Y=!1,$=null,j=!!k.dynamicChildren)=>{if(v===k)return;v&&!Zt(v,k)&&(O=U(v),ue(v,R,q,!0),v=null),k.patchFlag===-2&&(j=!1,k.dynamicChildren=null);const{type:V,ref:te,shapeFlag:X}=k;switch(V){case mi:x(v,k,L,O);break;case lt:b(v,k,L,O);break;case Si:v==null&&w(k,L,O,Y);break;case Be:Z(v,k,L,O,R,q,Y,$,j);break;default:X&1?F(v,k,L,O,R,q,Y,$,j):X&6?B(v,k,L,O,R,q,Y,$,j):(X&64||X&128)&&V.process(v,k,L,O,R,q,Y,$,j,oe)}te!=null&&R&&Ia(te,v&&v.ref,q,k||v,!k)},x=(v,k,L,O)=>{if(v==null)a(k.el=r(k.children),L,O);else{const R=k.el=v.el;k.children!==v.children&&d(R,k.children)}},b=(v,k,L,O)=>{v==null?a(k.el=c(k.children||""),L,O):k.el=v.el},w=(v,k,L,O)=>{[v.el,v.anchor]=y(v.children,k,L,O,v.el,v.anchor)},E=({el:v,anchor:k},L,O)=>{let R;for(;v&&v!==k;)R=m(v),a(v,L,O),v=R;a(k,L,O)},C=({el:v,anchor:k})=>{let L;for(;v&&v!==k;)L=m(v),n(v),v=L;n(k)},F=(v,k,L,O,R,q,Y,$,j)=>{Y=Y||k.type==="svg",v==null?S(k,L,O,R,q,Y,$,j):N(v,k,R,q,Y,$,j)},S=(v,k,L,O,R,q,Y,$)=>{let j,V;const{type:te,props:X,shapeFlag:ie,transition:re,dirs:he}=v;if(j=v.el=o(v.type,q,X&&X.is,X),ie&8?h(j,v.children):ie&16&&A(v.children,j,null,O,R,q&&te!=="foreignObject",Y,$),he&&ft(v,null,O,"created"),X){for(const Te in X)Te!=="value"&&!Oi(Te)&&l(j,Te,null,X[Te],q,v.children,O,R,G);"value"in X&&l(j,"value",null,X.value),(V=X.onVnodeBeforeMount)&&tt(V,O,v)}T(j,v,v.scopeId,Y,O),he&&ft(v,null,O,"beforeMount");const Ee=(!R||R&&!R.pendingBranch)&&re&&!re.persisted;Ee&&re.beforeEnter(j),a(j,k,L),((V=X&&X.onVnodeMounted)||Ee||he)&&Ue(()=>{V&&tt(V,O,v),Ee&&re.enter(j),he&&ft(v,null,O,"mounted")},R)},T=(v,k,L,O,R)=>{if(L&&f(v,L),O)for(let q=0;q{for(let V=j;V{const $=k.el=v.el;let{patchFlag:j,dynamicChildren:V,dirs:te}=k;j|=v.patchFlag&16;const X=v.props||Ie,ie=k.props||Ie;let re;L&&jt(L,!1),(re=ie.onVnodeBeforeUpdate)&&tt(re,L,k,v),te&&ft(k,v,L,"beforeUpdate"),L&&jt(L,!0);const he=R&&k.type!=="foreignObject";if(V?z(v.dynamicChildren,V,$,L,O,he,q):Y||J(v,k,$,null,L,O,he,q,!1),j>0){if(j&16)le($,k,X,ie,L,O,R);else if(j&2&&X.class!==ie.class&&l($,"class",null,ie.class,R),j&4&&l($,"style",X.style,ie.style,R),j&8){const Ee=k.dynamicProps;for(let Te=0;Te{re&&tt(re,L,k,v),te&&ft(k,v,L,"updated")},O)},z=(v,k,L,O,R,q,Y)=>{for(let $=0;${if(L!==O){if(L!==Ie)for(const $ in L)!Oi($)&&!($ in O)&&l(v,$,L[$],null,Y,k.children,R,q,G);for(const $ in O){if(Oi($))continue;const j=O[$],V=L[$];j!==V&&$!=="value"&&l(v,$,V,j,Y,k.children,R,q,G)}"value"in O&&l(v,"value",L.value,O.value)}},Z=(v,k,L,O,R,q,Y,$,j)=>{const V=k.el=v?v.el:r(""),te=k.anchor=v?v.anchor:r("");let{patchFlag:X,dynamicChildren:ie,slotScopeIds:re}=k;re&&($=$?$.concat(re):re),v==null?(a(V,L,O),a(te,L,O),A(k.children,L,te,R,q,Y,$,j)):X>0&&X&64&&ie&&v.dynamicChildren?(z(v.dynamicChildren,ie,L,R,q,Y,$),(k.key!=null||R&&k===R.subTree)&&Qr(v,k,!0)):J(v,k,L,te,R,q,Y,$,j)},B=(v,k,L,O,R,q,Y,$,j)=>{k.slotScopeIds=$,v==null?k.shapeFlag&512?R.ctx.activate(k,L,O,Y,j):D(k,L,O,R,q,Y,j):H(v,k,j)},D=(v,k,L,O,R,q,Y)=>{const $=v.component=_h(v,O,R);if(Xi(v)&&($.ctx.renderer=oe),bh($),$.asyncDep){if(R&&R.registerDep($,W),!v.el){const j=$.subTree=Oe(lt);b(null,j,k,L)}return}W($,v,k,L,R,q,Y)},H=(v,k,L)=>{const O=k.component=v.component;if(Ad(v,k,L))if(O.asyncDep&&!O.asyncResolved){M(O,k,L);return}else O.next=k,wd(O.update),O.update();else k.el=v.el,O.vnode=k},W=(v,k,L,O,R,q,Y)=>{const $=()=>{if(v.isMounted){let{next:te,bu:X,u:ie,parent:re,vnode:he}=v,Ee=te,Te;jt(v,!1),te?(te.el=he.el,M(v,te,Y)):te=he,X&&tn(X),(Te=te.props&&te.props.onVnodeBeforeUpdate)&&tt(Te,re,te,he),jt(v,!0);const Ve=an(v),st=v.subTree;v.subTree=Ve,_(st,Ve,p(st.el),U(st),v,R,q),te.el=Ve.el,Ee===null&&Id(v,Ve.el),ie&&Ue(ie,R),(Te=te.props&&te.props.onVnodeUpdated)&&Ue(()=>tt(Te,re,te,he),R)}else{let te;const{el:X,props:ie}=k,{bm:re,m:he,parent:Ee}=v,Te=Ri(k);if(jt(v,!1),re&&tn(re),!Te&&(te=ie&&ie.onVnodeBeforeMount)&&tt(te,Ee,k),jt(v,!0),X&&se){const Ve=()=>{v.subTree=an(v),se(X,v.subTree,v,R,null)};Te?k.type.__asyncLoader().then(()=>!v.isUnmounted&&Ve()):Ve()}else{const Ve=v.subTree=an(v);_(null,Ve,L,O,v,R,q),k.el=Ve.el}if(he&&Ue(he,R),!Te&&(te=ie&&ie.onVnodeMounted)){const Ve=k;Ue(()=>tt(te,Ee,Ve),R)}(k.shapeFlag&256||Ee&&Ri(Ee.vnode)&&Ee.vnode.shapeFlag&256)&&v.a&&Ue(v.a,R),v.isMounted=!0,k=L=O=null}},j=v.effect=new el($,()=>Na(V),v.scope),V=v.update=()=>j.run();V.id=v.uid,jt(v,!0),V()},M=(v,k,L)=>{k.component=v;const O=v.vnode.props;v.vnode=k,v.next=null,eh(v,k.props,O,L),ah(v,k.children,L),yi(),Jl(),wi()},J=(v,k,L,O,R,q,Y,$,j=!1)=>{const V=v&&v.children,te=v?v.shapeFlag:0,X=k.children,{patchFlag:ie,shapeFlag:re}=k;if(ie>0){if(ie&128){de(V,X,L,O,R,q,Y,$,j);return}else if(ie&256){ee(V,X,L,O,R,q,Y,$,j);return}}re&8?(te&16&&G(V,R,q),X!==V&&h(L,X)):te&16?re&16?de(V,X,L,O,R,q,Y,$,j):G(V,R,q,!0):(te&8&&h(L,""),re&16&&A(X,L,O,R,q,Y,$,j))},ee=(v,k,L,O,R,q,Y,$,j)=>{v=v||si,k=k||si;const V=v.length,te=k.length,X=Math.min(V,te);let ie;for(ie=0;iete?G(v,R,q,!0,!1,X):A(k,L,O,R,q,Y,$,j,X)},de=(v,k,L,O,R,q,Y,$,j)=>{let V=0;const te=k.length;let X=v.length-1,ie=te-1;for(;V<=X&&V<=ie;){const re=v[V],he=k[V]=j?Mt(k[V]):ct(k[V]);if(Zt(re,he))_(re,he,L,null,R,q,Y,$,j);else break;V++}for(;V<=X&&V<=ie;){const re=v[X],he=k[ie]=j?Mt(k[ie]):ct(k[ie]);if(Zt(re,he))_(re,he,L,null,R,q,Y,$,j);else break;X--,ie--}if(V>X){if(V<=ie){const re=ie+1,he=reie)for(;V<=X;)ue(v[V],R,q,!0),V++;else{const re=V,he=V,Ee=new Map;for(V=he;V<=ie;V++){const Ge=k[V]=j?Mt(k[V]):ct(k[V]);Ge.key!=null&&Ee.set(Ge.key,V)}let Te,Ve=0;const st=ie-he+1;let ii=!1,zl=0;const Ei=new Array(st);for(V=0;V=st){ue(Ge,R,q,!0);continue}let vt;if(Ge.key!=null)vt=Ee.get(Ge.key);else for(Te=he;Te<=ie;Te++)if(Ei[Te-he]===0&&Zt(Ge,k[Te])){vt=Te;break}vt===void 0?ue(Ge,R,q,!0):(Ei[vt-he]=V+1,vt>=zl?zl=vt:ii=!0,_(Ge,k[vt],L,null,R,q,Y,$,j),Ve++)}const Hl=ii?ch(Ei):si;for(Te=Hl.length-1,V=st-1;V>=0;V--){const Ge=he+V,vt=k[Ge],$l=Ge+1{const{el:q,type:Y,transition:$,children:j,shapeFlag:V}=v;if(V&6){fe(v.component.subTree,k,L,O);return}if(V&128){v.suspense.move(k,L,O);return}if(V&64){Y.move(v,k,L,oe);return}if(Y===Be){a(q,k,L);for(let X=0;X$.enter(q),R);else{const{leave:X,delayLeave:ie,afterLeave:re}=$,he=()=>a(q,k,L),Ee=()=>{X(q,()=>{he(),re&&re()})};ie?ie(q,he,Ee):Ee()}else a(q,k,L)},ue=(v,k,L,O=!1,R=!1)=>{const{type:q,props:Y,ref:$,children:j,dynamicChildren:V,shapeFlag:te,patchFlag:X,dirs:ie}=v;if($!=null&&Ia($,null,L,v,!0),te&256){k.ctx.deactivate(v);return}const re=te&1&&ie,he=!Ri(v);let Ee;if(he&&(Ee=Y&&Y.onVnodeBeforeUnmount)&&tt(Ee,k,v),te&6)I(v.component,L,O);else{if(te&128){v.suspense.unmount(L,O);return}re&&ft(v,null,k,"beforeUnmount"),te&64?v.type.remove(v,k,L,R,oe,O):V&&(q!==Be||X>0&&X&64)?G(V,k,L,!1,!0):(q===Be&&X&384||!R&&te&16)&&G(j,k,L),O&&Re(v)}(he&&(Ee=Y&&Y.onVnodeUnmounted)||re)&&Ue(()=>{Ee&&tt(Ee,k,v),re&&ft(v,null,k,"unmounted")},L)},Re=v=>{const{type:k,el:L,anchor:O,transition:R}=v;if(k===Be){We(L,O);return}if(k===Si){C(v);return}const q=()=>{n(L),R&&!R.persisted&&R.afterLeave&&R.afterLeave()};if(v.shapeFlag&1&&R&&!R.persisted){const{leave:Y,delayLeave:$}=R,j=()=>Y(L,q);$?$(v.el,q,j):j()}else q()},We=(v,k)=>{let L;for(;v!==k;)L=m(v),n(v),v=L;n(k)},I=(v,k,L)=>{const{bum:O,scope:R,update:q,subTree:Y,um:$}=v;O&&tn(O),R.stop(),q&&(q.active=!1,ue(Y,v,k,L)),$&&Ue($,k),Ue(()=>{v.isUnmounted=!0},k),k&&k.pendingBranch&&!k.isUnmounted&&v.asyncDep&&!v.asyncResolved&&v.suspenseId===k.pendingId&&(k.deps--,k.deps===0&&k.resolve())},G=(v,k,L,O=!1,R=!1,q=0)=>{for(let Y=q;Yv.shapeFlag&6?U(v.component.subTree):v.shapeFlag&128?v.suspense.next():m(v.anchor||v.el),Q=(v,k,L)=>{v==null?k._vnode&&ue(k._vnode,null,null,!0):_(k._vnode||null,v,k,null,null,null,L),Jl(),La(),k._vnode=v},oe={p:_,um:ue,m:fe,r:Re,mt:D,mc:A,pc:J,pbc:z,n:U,o:e};let _e,se;return t&&([_e,se]=t(oe)),{render:Q,hydrate:_e,createApp:lh(Q,_e)}}function jt({effect:e,update:t},i){e.allowRecurse=t.allowRecurse=i}function Qr(e,t,i=!1){const a=e.children,n=t.children;if(ae(a)&&ae(n))for(let l=0;l>1,e[i[r]]0&&(t[a]=i[l-1]),i[l]=a)}}for(l=i.length,o=i[l-1];l-- >0;)i[l]=o,o=t[o];return i}const uh=e=>e.__isTeleport,Be=Symbol(void 0),mi=Symbol(void 0),lt=Symbol(void 0),Si=Symbol(void 0),Vi=[];let dt=null;function Mi(e=!1){Vi.push(dt=e?null:[])}function dh(){Vi.pop(),dt=Vi[Vi.length-1]||null}let ji=1;function ro(e){ji+=e}function Xr(e){return e.dynamicChildren=ji>0?dt||si:null,dh(),ji>0&&dt&&dt.push(e),e}function wa(e,t,i,a,n,l){return Xr($e(e,t,i,a,n,l,!0))}function hh(e,t,i,a,n){return Xr(Oe(e,t,i,a,n,!0))}function Dn(e){return e?e.__v_isVNode===!0:!1}function Zt(e,t){return e.type===t.type&&e.key===t.key}const Ua="__vInternal",es=({key:e})=>e!=null?e:null,xa=({ref:e,ref_key:t,ref_for:i})=>e!=null?ye(e)||He(e)||ce(e)?{i:it,r:e,k:t,f:!!i}:e:null;function $e(e,t=null,i=null,a=0,n=null,l=e===Be?0:1,o=!1,r=!1){const c={__v_isVNode:!0,__v_skip:!0,type:e,props:t,key:t&&es(t),ref:t&&xa(t),scopeId:Mr,slotScopeIds:null,children:i,component:null,suspense:null,ssContent:null,ssFallback:null,dirs:null,transition:null,el:null,anchor:null,target:null,targetAnchor:null,staticCount:0,shapeFlag:l,patchFlag:a,dynamicProps:n,dynamicChildren:null,appContext:null,ctx:it};return r?(ml(c,i),l&128&&e.normalize(c)):i&&(c.shapeFlag|=ye(i)?8:16),ji>0&&!o&&dt&&(c.patchFlag>0||l&6)&&c.patchFlag!==32&&dt.push(c),c}const Oe=ph;function ph(e,t=null,i=null,a=0,n=null,l=!1){if((!e||e===Ud)&&(e=lt),Dn(e)){const r=qt(e,t,!0);return i&&ml(r,i),ji>0&&!l&&dt&&(r.shapeFlag&6?dt[dt.indexOf(e)]=r:dt.push(r)),r.patchFlag|=-2,r}if(Th(e)&&(e=e.__vccOpts),t){t=mh(t);let{class:r,style:c}=t;r&&!ye(r)&&(t.class=Fa(r)),Ae(c)&&(Tr(c)&&!ae(c)&&(c=ze({},c)),t.style=Gn(c))}const o=ye(e)?1:Od(e)?128:uh(e)?64:Ae(e)?4:ce(e)?2:0;return $e(e,t,i,a,n,o,l,!0)}function mh(e){return e?Tr(e)||Ua in e?ze({},e):e:null}function qt(e,t,i=!1){const{props:a,ref:n,patchFlag:l,children:o}=e,r=t?gh(a||{},t):a;return{__v_isVNode:!0,__v_skip:!0,type:e.type,props:r,key:r&&es(r),ref:t&&t.ref?i&&n?ae(n)?n.concat(xa(t)):[n,xa(t)]:xa(t):n,scopeId:e.scopeId,slotScopeIds:e.slotScopeIds,children:o,target:e.target,targetAnchor:e.targetAnchor,staticCount:e.staticCount,shapeFlag:e.shapeFlag,patchFlag:t&&e.type!==Be?l===-1?16:l|16:l,dynamicProps:e.dynamicProps,dynamicChildren:e.dynamicChildren,appContext:e.appContext,dirs:e.dirs,transition:e.transition,component:e.component,suspense:e.suspense,ssContent:e.ssContent&&qt(e.ssContent),ssFallback:e.ssFallback&&qt(e.ssFallback),el:e.el,anchor:e.anchor,ctx:e.ctx}}function ts(e=" ",t=0){return Oe(mi,null,e,t)}function c1(e,t){const i=Oe(Si,null,e);return i.staticCount=t,i}function u1(e="",t=!1){return t?(Mi(),hh(lt,null,e)):Oe(lt,null,e)}function ct(e){return e==null||typeof e=="boolean"?Oe(lt):ae(e)?Oe(Be,null,e.slice()):typeof e=="object"?Mt(e):Oe(mi,null,String(e))}function Mt(e){return e.el===null&&e.patchFlag!==-1||e.memo?e:qt(e)}function ml(e,t){let i=0;const{shapeFlag:a}=e;if(t==null)t=null;else if(ae(t))i=16;else if(typeof t=="object")if(a&65){const n=t.default;n&&(n._c&&(n._d=!1),ml(e,n()),n._c&&(n._d=!0));return}else{i=32;const n=t._;!n&&!(Ua in t)?t._ctx=it:n===3&&it&&(it.slots._===1?t._=1:(t._=2,e.patchFlag|=1024))}else ce(t)?(t={default:t,_ctx:it},i=32):(t=String(t),a&64?(i=16,t=[ts(t)]):i=8);e.children=t,e.shapeFlag|=i}function gh(...e){const t={};for(let i=0;iFe||it,gi=e=>{Fe=e,e.scope.on()},ei=()=>{Fe&&Fe.scope.off(),Fe=null};function is(e){return e.vnode.shapeFlag&4}let vi=!1;function bh(e,t=!1){vi=t;const{props:i,children:a}=e.vnode,n=is(e);Xd(e,i,n,t),ih(e,a);const l=n?kh(e,t):void 0;return vi=!1,l}function kh(e,t){const i=e.type;e.accessCache=Object.create(null),e.proxy=Er(new Proxy(e.ctx,Gd));const{setup:a}=i;if(a){const n=e.setupContext=a.length>1?wh(e):null;gi(e),yi();const l=$t(a,e,0,[e.props,n]);if(wi(),ei(),dr(l)){if(l.then(ei,ei),t)return l.then(o=>{so(e,o,t)}).catch(o=>{Qi(o,e,0)});e.asyncDep=l}else so(e,l,t)}else as(e,t)}function so(e,t,i){ce(t)?e.type.__ssrInlineRender?e.ssrRender=t:e.render=t:Ae(t)&&(e.setupState=Ir(t)),as(e,i)}let co;function as(e,t,i){const a=e.type;if(!e.render){if(!t&&co&&!a.render){const n=a.template||hl(e).template;if(n){const{isCustomElement:l,compilerOptions:o}=e.appContext.config,{delimiters:r,compilerOptions:c}=a,d=ze(ze({isCustomElement:l,delimiters:r},o),c);a.render=co(n,d)}}e.render=a.render||ht}gi(e),yi(),Yd(e),wi(),ei()}function yh(e){return new Proxy(e.attrs,{get(t,i){return Ze(e,"get","$attrs"),t[i]}})}function wh(e){const t=a=>{e.exposed=a||{}};let i;return{get attrs(){return i||(i=yh(e))},slots:e.slots,emit:e.emit,expose:t}}function gl(e){if(e.exposed)return e.exposeProxy||(e.exposeProxy=new Proxy(Ir(Er(e.exposed)),{get(t,i){if(i in t)return t[i];if(i in Ci)return Ci[i](e)},has(t,i){return i in t||i in Ci}}))}function xh(e,t=!0){return ce(e)?e.displayName||e.name:e.name||t&&e.__name}function Th(e){return ce(e)&&"__vccOpts"in e}const P=(e,t)=>bd(e,t,vi);function u(e,t,i){const a=arguments.length;return a===2?Ae(t)&&!ae(t)?Dn(t)?Oe(e,null,[t]):Oe(e,t):Oe(e,null,t):(a>3?i=Array.prototype.slice.call(arguments,2):a===3&&Dn(i)&&(i=[i]),Oe(e,t,i))}const Eh=Symbol(""),Ph=()=>we(Eh),Lh="3.2.45",Dh="http://www.w3.org/2000/svg",Jt=typeof document<"u"?document:null,uo=Jt&&Jt.createElement("template"),Ah={insert:(e,t,i)=>{t.insertBefore(e,i||null)},remove:e=>{const t=e.parentNode;t&&t.removeChild(e)},createElement:(e,t,i,a)=>{const n=t?Jt.createElementNS(Dh,e):Jt.createElement(e,i?{is:i}:void 0);return e==="select"&&a&&a.multiple!=null&&n.setAttribute("multiple",a.multiple),n},createText:e=>Jt.createTextNode(e),createComment:e=>Jt.createComment(e),setText:(e,t)=>{e.nodeValue=t},setElementText:(e,t)=>{e.textContent=t},parentNode:e=>e.parentNode,nextSibling:e=>e.nextSibling,querySelector:e=>Jt.querySelector(e),setScopeId(e,t){e.setAttribute(t,"")},insertStaticContent(e,t,i,a,n,l){const o=i?i.previousSibling:t.lastChild;if(n&&(n===l||n.nextSibling))for(;t.insertBefore(n.cloneNode(!0),i),!(n===l||!(n=n.nextSibling)););else{uo.innerHTML=a?`${e}`:e;const r=uo.content;if(a){const c=r.firstChild;for(;c.firstChild;)r.appendChild(c.firstChild);r.removeChild(c)}t.insertBefore(r,i)}return[o?o.nextSibling:t.firstChild,i?i.previousSibling:t.lastChild]}};function Ih(e,t,i){const a=e._vtc;a&&(t=(t?[t,...a]:[...a]).join(" ")),t==null?e.removeAttribute("class"):i?e.setAttribute("class",t):e.className=t}function Oh(e,t,i){const a=e.style,n=ye(i);if(i&&!n){for(const l in i)An(a,l,i[l]);if(t&&!ye(t))for(const l in t)i[l]==null&&An(a,l,"")}else{const l=a.display;n?t!==i&&(a.cssText=i):t&&e.removeAttribute("style"),"_vod"in e&&(a.display=l)}}const ho=/\s*!important$/;function An(e,t,i){if(ae(i))i.forEach(a=>An(e,t,a));else if(i==null&&(i=""),t.startsWith("--"))e.setProperty(t,i);else{const a=Rh(e,t);ho.test(i)?e.setProperty(ki(a),i.replace(ho,""),"important"):e[a]=i}}const po=["Webkit","Moz","ms"],rn={};function Rh(e,t){const i=rn[t];if(i)return i;let a=rt(t);if(a!=="filter"&&a in e)return rn[t]=a;a=Zi(a);for(let n=0;nsn||(Hh.then(()=>sn=0),sn=Date.now());function Nh(e,t){const i=a=>{if(!a._vts)a._vts=Date.now();else if(a._vts<=i.attached)return;nt(qh(a,i.value),t,5,[a])};return i.value=e,i.attached=$h(),i}function qh(e,t){if(ae(t)){const i=e.stopImmediatePropagation;return e.stopImmediatePropagation=()=>{i.call(e),e._stopped=!0},t.map(a=>n=>!n._stopped&&a&&a(n))}else return t}const vo=/^on[a-z]/,Bh=(e,t,i,a,n=!1,l,o,r,c)=>{t==="class"?Ih(e,a,n):t==="style"?Oh(e,i,a):Ki(t)?Yn(t)||Fh(e,t,i,a,o):(t[0]==="."?(t=t.slice(1),!0):t[0]==="^"?(t=t.slice(1),!1):Uh(e,t,a,n))?Sh(e,t,a,l,o,r,c):(t==="true-value"?e._trueValue=a:t==="false-value"&&(e._falseValue=a),Ch(e,t,a,n))};function Uh(e,t,i,a){return a?!!(t==="innerHTML"||t==="textContent"||t in e&&vo.test(t)&&ce(i)):t==="spellcheck"||t==="draggable"||t==="translate"||t==="form"||t==="list"&&e.tagName==="INPUT"||t==="type"&&e.tagName==="TEXTAREA"||vo.test(t)&&ye(i)?!1:t in e}const Ct="transition",Pi="animation",Dt=(e,{slots:t})=>u($r,ls(e),t);Dt.displayName="Transition";const ns={name:String,type:String,css:{type:Boolean,default:!0},duration:[String,Number,Object],enterFromClass:String,enterActiveClass:String,enterToClass:String,appearFromClass:String,appearActiveClass:String,appearToClass:String,leaveFromClass:String,leaveActiveClass:String,leaveToClass:String},jh=Dt.props=ze({},$r.props,ns),Wt=(e,t=[])=>{ae(e)?e.forEach(i=>i(...t)):e&&e(...t)},fo=e=>e?ae(e)?e.some(t=>t.length>1):e.length>1:!1;function ls(e){const t={};for(const Z in e)Z in ns||(t[Z]=e[Z]);if(e.css===!1)return t;const{name:i="v",type:a,duration:n,enterFromClass:l=`${i}-enter-from`,enterActiveClass:o=`${i}-enter-active`,enterToClass:r=`${i}-enter-to`,appearFromClass:c=l,appearActiveClass:d=o,appearToClass:h=r,leaveFromClass:p=`${i}-leave-from`,leaveActiveClass:m=`${i}-leave-active`,leaveToClass:f=`${i}-leave-to`}=e,y=Wh(n),_=y&&y[0],x=y&&y[1],{onBeforeEnter:b,onEnter:w,onEnterCancelled:E,onLeave:C,onLeaveCancelled:F,onBeforeAppear:S=b,onAppear:T=w,onAppearCancelled:A=E}=t,N=(Z,B,D)=>{Vt(Z,B?h:r),Vt(Z,B?d:o),D&&D()},z=(Z,B)=>{Z._isLeaving=!1,Vt(Z,p),Vt(Z,f),Vt(Z,m),B&&B()},le=Z=>(B,D)=>{const H=Z?T:w,W=()=>N(B,Z,D);Wt(H,[B,W]),_o(()=>{Vt(B,Z?c:l),xt(B,Z?h:r),fo(H)||bo(B,a,_,W)})};return ze(t,{onBeforeEnter(Z){Wt(b,[Z]),xt(Z,l),xt(Z,o)},onBeforeAppear(Z){Wt(S,[Z]),xt(Z,c),xt(Z,d)},onEnter:le(!1),onAppear:le(!0),onLeave(Z,B){Z._isLeaving=!0;const D=()=>z(Z,B);xt(Z,p),rs(),xt(Z,m),_o(()=>{!Z._isLeaving||(Vt(Z,p),xt(Z,f),fo(C)||bo(Z,a,x,D))}),Wt(C,[Z,D])},onEnterCancelled(Z){N(Z,!1),Wt(E,[Z])},onAppearCancelled(Z){N(Z,!0),Wt(A,[Z])},onLeaveCancelled(Z){z(Z),Wt(F,[Z])}})}function Wh(e){if(e==null)return null;if(Ae(e))return[cn(e.enter),cn(e.leave)];{const t=cn(e);return[t,t]}}function cn(e){return Qn(e)}function xt(e,t){t.split(/\s+/).forEach(i=>i&&e.classList.add(i)),(e._vtc||(e._vtc=new Set)).add(t)}function Vt(e,t){t.split(/\s+/).forEach(a=>a&&e.classList.remove(a));const{_vtc:i}=e;i&&(i.delete(t),i.size||(e._vtc=void 0))}function _o(e){requestAnimationFrame(()=>{requestAnimationFrame(e)})}let Gh=0;function bo(e,t,i,a){const n=e._endId=++Gh,l=()=>{n===e._endId&&a()};if(i)return setTimeout(l,i);const{type:o,timeout:r,propCount:c}=os(e,t);if(!o)return a();const d=o+"end";let h=0;const p=()=>{e.removeEventListener(d,m),l()},m=f=>{f.target===e&&++h>=c&&p()};setTimeout(()=>{h(i[y]||"").split(", "),n=a(`${Ct}Delay`),l=a(`${Ct}Duration`),o=ko(n,l),r=a(`${Pi}Delay`),c=a(`${Pi}Duration`),d=ko(r,c);let h=null,p=0,m=0;t===Ct?o>0&&(h=Ct,p=o,m=l.length):t===Pi?d>0&&(h=Pi,p=d,m=c.length):(p=Math.max(o,d),h=p>0?o>d?Ct:Pi:null,m=h?h===Ct?l.length:c.length:0);const f=h===Ct&&/\b(transform|all)(,|$)/.test(a(`${Ct}Property`).toString());return{type:h,timeout:p,propCount:m,hasTransform:f}}function ko(e,t){for(;e.lengthyo(i)+yo(e[a])))}function yo(e){return Number(e.slice(0,-1).replace(",","."))*1e3}function rs(){return document.body.offsetHeight}const ss=new WeakMap,cs=new WeakMap,Yh={name:"TransitionGroup",props:ze({},jh,{tag:String,moveClass:String}),setup(e,{slots:t}){const i=ea(),a=Hr();let n,l;return Br(()=>{if(!n.length)return;const o=e.moveClass||`${e.name||"v"}-move`;if(!Qh(n[0].el,i.vnode.el,o))return;n.forEach(Kh),n.forEach(Zh);const r=n.filter(Jh);rs(),r.forEach(c=>{const d=c.el,h=d.style;xt(d,o),h.transform=h.webkitTransform=h.transitionDuration="";const p=d._moveCb=m=>{m&&m.target!==d||(!m||/transform$/.test(m.propertyName))&&(d.removeEventListener("transitionend",p),d._moveCb=null,Vt(d,o))};d.addEventListener("transitionend",p)})}),()=>{const o=be(e),r=ls(o);let c=o.tag||Be;n=l,l=t.default?ul(t.default()):[];for(let d=0;d{o.split(/\s+/).forEach(r=>r&&a.classList.remove(r))}),i.split(/\s+/).forEach(o=>o&&a.classList.add(o)),a.style.display="none";const n=t.nodeType===1?t:t.parentNode;n.appendChild(a);const{hasTransform:l}=os(a);return n.removeChild(a),l}const Xh=ze({patchProp:Bh},Ah);let un,xo=!1;function ep(){return un=xo?un:rh(Xh),xo=!0,un}const tp=(...e)=>{const t=ep().createApp(...e),{mount:i}=t;return t.mount=a=>{const n=ip(a);if(n)return i(n,!0,n instanceof SVGElement)},t};function ip(e){return ye(e)?document.querySelector(e):e}const ap=JSON.parse('{"base":"/","lang":"en-US","title":"","description":"A Community-led Hyper-Hackable Text Editor","head":[["script",{"src":"/download-preselect.js"}]],"locales":{}}');var np=([e,t,i])=>e==="meta"&&t.name?`${e}.${t.name}`:["title","base"].includes(e)?e:e==="template"&&t.id?`${e}.${t.id}`:JSON.stringify([e,t,i]),lp=e=>{const t=new Set,i=[];return e.forEach(a=>{const n=np(a);t.has(n)||(t.add(n),i.push(a))}),i},op=e=>/(\.html|\/)$/.test(e)?e:e+"/",rp=e=>e.startsWith("ftp://"),ti=e=>/^(https?:)?\/\//.test(e),sp=/.md((\?|#).*)?$/,Oa=(e,t="/")=>!!(ti(e)||rp(e)||e.startsWith("/")&&!e.startsWith(t)&&!sp.test(e)),cp=e=>/^mailto:/.test(e),up=e=>/^tel:/.test(e),vl=e=>Object.prototype.toString.call(e)==="[object Object]",fl=e=>e.replace(/\/$/,""),us=e=>e.replace(/^\//,""),dp=(e,t)=>{const i=Object.keys(e).sort((a,n)=>{const l=n.split("/").length-a.split("/").length;return l!==0?l:n.length-a.length});for(const a of i)if(t.startsWith(a))return a;return"/"};const ds={"v-22a39d25":g(()=>s(()=>import("./about.html.b43feee1.js"),[])),"v-67f865c9":g(()=>s(()=>import("./community.html.4ddab81c.js"),[])),"v-0100cb31":g(()=>s(()=>import("./donate.html.b9be285d.js"),[])),"v-5395f6f8":g(()=>s(()=>import("./download.html.45b3ffc4.js"),[])),"v-8daa1a0e":g(()=>s(()=>import("./index.html.4b5dd6bc.js"),[])),"v-41185df1":g(()=>s(()=>import("./repos.html.47dd31c0.js"),[])),"v-f7f18754":g(()=>s(()=>import("./20221112-Daeraxa-ExamplePost.html.006b41d6.js"),[])),"v-0075970e":g(()=>s(()=>import("./20221127-confused-Techie-SunsetMisadventureBackend.html.181ef131.js"),[])),"v-cfc0df38":g(()=>s(()=>import("./20221208-Daeraxa-DistroTubeVideo.html.7c68eb7c.js"),[])),"v-4769b7e2":g(()=>s(()=>import("./20221215-confused-Techie-v1.100.1-beta.html.465d9003.js"),[])),"v-59933042":g(()=>s(()=>import("./20230114-confused-Techie-v1.101.0-beta.html.ba25e8b5.js"),[])),"v-3a050031":g(()=>s(()=>import("./20230201-Daeraxa-FebUpdate.html.3f2403f8.js"),[])),"v-5fdcc4a1":g(()=>s(()=>import("./20230209-mauricioszabo-tree-sitter-part-1.html.80ed8667.js"),[])),"v-f88b1c54":g(()=>s(()=>import("./20230215-Daeraxa-v1.102.0.html.5122191c.js"),[])),"v-5999d3d4":g(()=>s(()=>import("./20230216-Daeraxa-ReleaseStrategyUpdate.html.2cea6acb.js"),[])),"v-5b193c62":g(()=>s(()=>import("./20230227-Daeraxa-Survey1.html.fb8ea37d.js"),[])),"v-29fd1295":g(()=>s(()=>import("./20230301-Daeraxa-MarUpdate.html.f5a92e03.js"),[])),"v-6fcbebb6":g(()=>s(()=>import("./20230315-Daeraxa-v1.103.0.html.aceca816.js"),[])),"v-7f7a1a1a":g(()=>s(()=>import("./20230319-confused-Techie-HowLicenseNoneDeletedPackages.html.ea402cf4.js"),[])),"v-91e3038e":g(()=>s(()=>import("./20230326-Daeraxa-Survey1-Results.html.c3f15d90.js"),[])),"v-e1c35862":g(()=>s(()=>import("./20230401-Daeraxa-AprUpdate.html.cdce7d87.js"),["assets/20230401-Daeraxa-AprUpdate.html.cdce7d87.js","assets/tree-sitter.6a39e323.js","assets/chocolatey.6923f762.js"])),"v-0af04e6e":g(()=>s(()=>import("./20230401-confused-Techie-PON.html.937b0317.js"),[])),"v-3a723159":g(()=>s(()=>import("./20230418-Daeraxa-v1.104.0.html.85facd51.js"),[])),"v-1d4a9cb2":g(()=>s(()=>import("./20230430-Daeraxa-Survey2.html.0177dabb.js"),[])),"v-638b3e6c":g(()=>s(()=>import("./20230501-Daeraxa-MayUpdate.html.f0636831.js"),[])),"v-dbbf0e92":g(()=>s(()=>import("./20230516-Daeraxa-v1.105.0.html.a8dc50d7.js"),[])),"v-aebfc812":g(()=>s(()=>import("./20230525-Daeraxa-Survey2-Results.html.628207e7.js"),[])),"v-54f2f18e":g(()=>s(()=>import("./20230601-Daeraxa-JuneUpdate.html.e4da287c.js"),["assets/20230601-Daeraxa-JuneUpdate.html.e4da287c.js","assets/detective.e7e89ed0.js","assets/package.4e7449ae.js","assets/tree-sitter.6a39e323.js"])),"v-25d26d73":g(()=>s(()=>import("./20230610-Daeraxa-2kStars.html.b0855c71.js"),[])),"v-7e31f297":g(()=>s(()=>import("./20230616-Daeraxa-v1.106.0.html.98178db0.js"),[])),"v-4b1ee0b9":g(()=>s(()=>import("./20230701-Daeraxa-JulyUpdate.html.1c15beb2.js"),[])),"v-5e247e06":g(()=>s(()=>import("./20230715-DeeDeeG-v1.107.0.html.9b90e470.js"),[])),"v-6bf84516":g(()=>s(()=>import("./20230716-Daeraxa-v1.107.1.html.3624d889.js"),[])),"v-3cb6722f":g(()=>s(()=>import("./20230801-Daeraxa-AugustUpdate.html.8f13ad09.js"),["assets/20230801-Daeraxa-AugustUpdate.html.8f13ad09.js","assets/detective.e7e89ed0.js","assets/package.4e7449ae.js","assets/spotlight.aba19e76.js"])),"v-0730d41e":g(()=>s(()=>import("./20230816-DeeDeeG-v1.108.0.html.49ef70c8.js"),[])),"v-7b64cdf7":g(()=>s(()=>import("./20230825-Daeraxa-ChocolateyUpdate.html.914c06fc.js"),["assets/20230825-Daeraxa-ChocolateyUpdate.html.914c06fc.js","assets/chocolatey.6923f762.js"])),"v-356c6c42":g(()=>s(()=>import("./20230903-confused-Techie-pulsars-ci.html.c905c8d2.js"),[])),"v-4dac8919":g(()=>s(()=>import("./20230904-Daeraxa-SeptemberUpdate.html.055d9ca9.js"),["assets/20230904-Daeraxa-SeptemberUpdate.html.055d9ca9.js","assets/chocolatey.6923f762.js","assets/package.4e7449ae.js","assets/spotlight.aba19e76.js"])),"v-42666037":g(()=>s(()=>import("./20230916-Daeraxa-v1.109.0.html.6de34209.js"),[])),"v-64adae58":g(()=>s(()=>import("./20230917-Daeraxa-LemmyCommunity.html.b23f8cd4.js"),[])),"v-25bd77f6":g(()=>s(()=>import("./20230925-savetheclocktower-modern-tree-sitter-part-1.html.a328c2a8.js"),[])),"v-5bad2e3c":g(()=>s(()=>import("./20230927-savetheclocktower-modern-tree-sitter-part-2.html.1843c64c.js"),["assets/20230927-savetheclocktower-modern-tree-sitter-part-2.html.1843c64c.js","assets/tree-sitter-string-scopes-diagram.2db37c5c.js"])),"v-6cc4d458":g(()=>s(()=>import("./20231004-Daeraxa-OctoberUpdate.html.dbc8c526.js"),["assets/20231004-Daeraxa-OctoberUpdate.html.dbc8c526.js","assets/tree-sitter.6a39e323.js","assets/spotlight.aba19e76.js"])),"v-8b6c6124":g(()=>s(()=>import("./20231013-savetheclocktower-modern-tree-sitter-part-3.html.dff0e326.js"),["assets/20231013-savetheclocktower-modern-tree-sitter-part-3.html.dff0e326.js","assets/tree-sitter-string-scopes-diagram.2db37c5c.js"])),"v-e8324d12":g(()=>s(()=>import("./20231016-Daeraxa-v1.110.0.html.5b3b1354.js"),[])),"v-5fc19751":g(()=>s(()=>import("./20231031-savetheclocktower-modern-tree-sitter-part-4.html.c1f7638e.js"),[])),"v-879bed80":g(()=>s(()=>import("./20231109-Daeraxa-NovemberUpdate.html.38101774.js"),["assets/20231109-Daeraxa-NovemberUpdate.html.38101774.js","assets/tools.52491106.js","assets/detective.e7e89ed0.js","assets/tree-sitter.6a39e323.js","assets/spotlight.aba19e76.js"])),"v-168f28ae":g(()=>s(()=>import("./20231110-savetheclocktower-modern-tree-sitter-part-5.html.c7b10bdf.js"),[])),"v-77f85357":g(()=>s(()=>import("./20231116-Daeraxa-v1.111.0.html.a8f9e5b3.js"),[])),"v-04bbc674":g(()=>s(()=>import("./20231212-Daeraxa-DecemberUpdate.html.6af4fa5b.js"),["assets/20231212-Daeraxa-DecemberUpdate.html.6af4fa5b.js","assets/electron.b5867f08.js","assets/tools.52491106.js","assets/spotlight.aba19e76.js"])),"v-6409cd37":g(()=>s(()=>import("./20231216-Daeraxa-v1.112.0.html.12b661cc.js"),[])),"v-15a14140":g(()=>s(()=>import("./20231219-DeeDeeG-v1.112.1.html.f5412ece.js"),[])),"v-4f70c30a":g(()=>s(()=>import("./20240112-Daeraxa-JanuaryUpdate.html.4c5055b5.js"),["assets/20240112-Daeraxa-JanuaryUpdate.html.4c5055b5.js","assets/tree-sitter.6a39e323.js","assets/package.4e7449ae.js","assets/spotlight.aba19e76.js"])),"v-3500d176":g(()=>s(()=>import("./20240115-Daeraxa-v1.113.0.html.c5e2a7ca.js"),[])),"v-67f98f2c":g(()=>s(()=>import("./20240122-savetheclocktower-modern-tree-sitter-part-6.html.da288708.js"),[])),"v-1181f0fe":g(()=>s(()=>import("./20240124-mauricioszabo-the-quest-for-electron-lts.html.e3a101c2.js"),[])),"v-703ed5aa":g(()=>s(()=>import("./20240201-Daeraxa-FebruaryUpdate.html.f8bbd93f.js"),["assets/20240201-Daeraxa-FebruaryUpdate.html.f8bbd93f.js","assets/tree-sitter.6a39e323.js","assets/electron.b5867f08.js","assets/package.4e7449ae.js","assets/spotlight.aba19e76.js"])),"v-21124b56":g(()=>s(()=>import("./20240215-Daeraxa-v1.114.0.html.52b3c07b.js"),[])),"v-88d8cd3c":g(()=>s(()=>import("./20240323-savetheclocktower-v1.115.0.html.80b3b399.js"),[])),"v-3ac80fd4":g(()=>s(()=>import("./20240417-confused-Techie-v1.116.0.html.5ffb1535.js"),[])),"v-83b0b1e4":g(()=>s(()=>import("./20240520-confused-Techie-v1.117.0.html.22f66452.js"),[])),"v-cdb5fcd6":g(()=>s(()=>import("./20240616-confused-Techie-v1.118.0.html.8cfe0363.js"),[])),"v-d8729a94":g(()=>s(()=>import("./20240717-confused-Techie-v1.119.0.html.170ba893.js"),[])),"v-147825fb":g(()=>s(()=>import("./index.html.3d7eaf24.js"),[])),"v-ed2312e4":g(()=>s(()=>import("./index.html.3b03ddf1.js"),[])),"v-033b47ca":g(()=>s(()=>import("./index.html.8656dbb7.js"),[])),"v-0166a572":g(()=>s(()=>import("./index.html.bed1e49d.js"),[])),"v-cf57a236":g(()=>s(()=>import("./index.html.c5e5eb68.js"),[])),"v-ad7b6e3a":g(()=>s(()=>import("./index.html.b1e370f8.js"),[])),"v-4368211c":g(()=>s(()=>import("./index.html.782819d8.js"),[])),"v-3973978e":g(()=>s(()=>import("./index.html.fb3f4ca6.js"),["assets/index.html.fb3f4ca6.js","assets/keybinding.964a4e0d.js","assets/markup.30f112ce.js"])),"v-ad775132":g(()=>s(()=>import("./index.html.7b64fcbd.js"),["assets/index.html.7b64fcbd.js","assets/zoom.50f0dc7b.js","assets/whitespace-settings.13b26c2a.js","assets/package-issue-link.51bb6d85.js","assets/welcome-screen-checkbox.604a29ce.js","assets/update-atom-macos.07b32cce.js","assets/find-and-replace-newline.08e81a06.js","assets/wrap-guide-line.159c54bb.js"])),"v-634d92c5":g(()=>s(()=>import("./index.html.817cae06.js"),["assets/index.html.817cae06.js","assets/windows-downloads.e9b59e10.js","assets/windows-system-settings.141561e2.js","assets/finder.04f876a5.js","assets/platform-selector.697b4dd8.js"])),"v-6c7e3cf8":g(()=>s(()=>import("./index.html.50f23593.js"),["assets/index.html.50f23593.js","assets/spec-suite.8f5e854d.js","assets/dev-tools.1b7b2813.js","assets/theme-side-by-side.33cf8d5d.js","assets/iconography.bf3cea92.js","assets/cpu-profile-done.c24435bc.js"])),"v-6e0db890":g(()=>s(()=>import("./index.html.07668541.js"),[])),"v-a6cdf728":g(()=>s(()=>import("./index.html.ad5774dc.js"),[])),"v-36dcd4ed":g(()=>s(()=>import("./index.html.4798fcfd.js"),["assets/index.html.4798fcfd.js","assets/spec-deps.b3f6a1b6.js","assets/dep-cop.6353fb49.js"])),"v-3835d9b2":g(()=>s(()=>import("./index.html.f7de1c88.js"),["assets/index.html.f7de1c88.js","assets/unity-theme.516a5ae9.js","assets/symbol.b88bf5a9.js","assets/encodings.f93acf64.js","assets/find-replace-project.ada882a7.js","assets/snippet-scope.1f381b52.js","assets/autocomplete.b0bae406.js","assets/folding.6d09f8df.js","assets/panes.a7cec271.js","assets/allow-pending-pane-items.0fe3dd4a.js","assets/grammar.c16b8cd6.js","assets/open-on-github.6fa9b166.js","assets/github-review-reply.6d405e4b.js","assets/preview.2eaf146a.js","assets/menubar.df752f94.js","assets/portable-mode-folder.90669a9a.js"])),"v-6566cb89":g(()=>s(()=>import("./index.html.9c7c1197.js"),[])),"v-625b0d60":g(()=>s(()=>import("./index.html.35187d4a.js"),[])),"v-734cbc8c":g(()=>s(()=>import("./index.html.576a4614.js"),[])),"v-a73e70e8":g(()=>s(()=>import("./index.html.371049af.js"),[])),"v-49ddb9a0":g(()=>s(()=>import("./index.html.fa010113.js"),[])),"v-923287ec":g(()=>s(()=>import("./index.html.5de84039.js"),[])),"v-7080a64e":g(()=>s(()=>import("./index.html.b41a041c.js"),[])),"v-7c51deeb":g(()=>s(()=>import("./atom-package-server-api.html.a54744b0.js"),[])),"v-1c7613fc":g(()=>s(()=>import("./atom-update-server-api.html.bfd69d08.js"),[])),"v-6ff500c4":g(()=>s(()=>import("./configuration-api.html.e432c45d.js"),[])),"v-15f047dd":g(()=>s(()=>import("./developing-node-modules.html.270ef95c.js"),[])),"v-778fbb37":g(()=>s(()=>import("./how-atom-uses-chromium-snapshots.html.80b272b6.js"),[])),"v-012c1bd2":g(()=>s(()=>import("./interacting-with-other-packages-via-services.html.4dc580d2.js"),[])),"v-b6a94a42":g(()=>s(()=>import("./keymaps-in-depth.html.70df9d29.js"),["assets/keymaps-in-depth.html.70df9d29.js","assets/keybinding.964a4e0d.js"])),"v-ee9016a0":g(()=>s(()=>import("./maintaining-your-packages.html.641bcc0a.js"),[])),"v-4bb0fcfd":g(()=>s(()=>import("./scoped-settings-scopes-and-scope-descriptors.html.9871f523.js"),["assets/scoped-settings-scopes-and-scope-descriptors.html.9871f523.js","assets/markup.30f112ce.js"])),"v-5c815036":g(()=>s(()=>import("./serialization-in-atom.html.06063206.js"),[])),"v-293dceca":g(()=>s(()=>import("./summary.html.e2da166f.js"),[])),"v-78782b86":g(()=>s(()=>import("./atom-in-the-cloud.html.d130be16.js"),[])),"v-bc28a444":g(()=>s(()=>import("./how-can-i-contribute-to-atom.html.6405f755.js"),[])),"v-71e09326":g(()=>s(()=>import("./how-can-i-tell-if-subpixel-antialiasing-is-working.html.ee25faf8.js"),["assets/how-can-i-tell-if-subpixel-antialiasing-is-working.html.ee25faf8.js","assets/zoom.50f0dc7b.js"])),"v-83ab2196":g(()=>s(()=>import("./how-do-i-accept-input-from-my-program-or-script-when-using-the-script-package.html.cc97b768.js"),[])),"v-58dbd8e4":g(()=>s(()=>import("./how-do-i-build-or-execute-code-i-ve-written-in-atom.html.7293d923.js"),[])),"v-69d7b7aa":g(()=>s(()=>import("./how-do-i-make-atom-recognize-a-file-with-extension-x-as-language-y.html.cf5f66f2.js"),[])),"v-6d023415":g(()=>s(()=>import("./how-do-i-make-the-welcome-screen-stop-showing-up.html.ff75e4bd.js"),["assets/how-do-i-make-the-welcome-screen-stop-showing-up.html.ff75e4bd.js","assets/welcome-screen-checkbox.604a29ce.js"])),"v-58d2c851":g(()=>s(()=>import("./how-do-i-preview-web-page-changes-automatically.html.05d87b35.js"),[])),"v-446213fa":g(()=>s(()=>import("./how-do-i-turn-on-line-wrap.html.0f348516.js"),[])),"v-671ef772":g(()=>s(()=>import("./how-do-i-uninstall-atom-on-macos.html.2069a8eb.js"),[])),"v-6e77009f":g(()=>s(()=>import("./how-do-i-use-a-newline-in-the-result-of-find-and-replace.html.0763323b.js"),["assets/how-do-i-use-a-newline-in-the-result-of-find-and-replace.html.0763323b.js","assets/find-and-replace-newline.08e81a06.js"])),"v-50328086":g(()=>s(()=>import("./i-am-unable-to-update-to-the-latest-version-of-atom-on-macos-how-do-i-fix-this.html.2508a414.js"),["assets/i-am-unable-to-update-to-the-latest-version-of-atom-on-macos-how-do-i-fix-this.html.2508a414.js","assets/update-atom-macos.07b32cce.js"])),"v-52c5c70a":g(()=>s(()=>import("./i-have-a-question-about-a-specific-atom-community-package-where-is-the-best-place-to-ask-it.html.9271651f.js"),["assets/i-have-a-question-about-a-specific-atom-community-package-where-is-the-best-place-to-ask-it.html.9271651f.js","assets/package-issue-link.51bb6d85.js"])),"v-779d7601":g(()=>s(()=>import("./i-m-getting-an-error-about-a-self-signed-certificate-what-do-i-do.html.689f5508.js"),[])),"v-6bf81d3a":g(()=>s(()=>import("./i-m-having-a-problem-with-julia-what-do-i-do.html.e48effb5.js"),[])),"v-103f9060":g(()=>s(()=>import("./i-m-having-a-problem-with-platformio-what-do-i-do.html.6d67c3ad.js"),[])),"v-f400a296":g(()=>s(()=>import("./i-m-trying-to-change-my-syntax-colors-from-styles-less-but-it-isn-t-working.html.bc280728.js"),[])),"v-69a4dea8":g(()=>s(()=>import("./i-m-using-an-international-keyboard-and-keys-that-use-altgr-or-ctrl-alt-aren-t-working.html.610e3ec4.js"),[])),"v-4ea46d24":g(()=>s(()=>import("./is-atom-open-source.html.9b17a52a.js"),[])),"v-3e42c98a":g(()=>s(()=>import("./macos-mojave-font-rendering-change.html.47686ca6.js"),[])),"v-5bf72dd3":g(()=>s(()=>import("./the-menu-bar-disappeared-how-do-i-get-it-back.html.29f26548.js"),[])),"v-25bdd335":g(()=>s(()=>import("./what-does-atom-cost.html.79addfd4.js"),[])),"v-7e76b4c2":g(()=>s(()=>import("./what-does-safe-mode-do.html.d38fba60.js"),[])),"v-8a4db88a":g(()=>s(()=>import("./what-is-this-line-on-the-right-in-the-editor-view.html.e82e70ee.js"),["assets/what-is-this-line-on-the-right-in-the-editor-view.html.e82e70ee.js","assets/wrap-guide-line.159c54bb.js"])),"v-39dbf018":g(()=>s(()=>import("./what-platforms-does-atom-run-on.html.88de8aff.js"),[])),"v-09b88655":g(()=>s(()=>import("./what-s-the-difference-between-an-ide-and-an-editor.html.1f47c819.js"),[])),"v-616d7939":g(()=>s(()=>import("./why-does-atom-collect-usage-data.html.56dfada2.js"),[])),"v-43d2cead":g(()=>s(()=>import("./why-does-macos-say-that-atom-wants-to-access-my-calendar-contacts-photos-etc.html.d4b59bf7.js"),[])),"v-65c8d593":g(()=>s(()=>import("./why-is-atom-deleting-trailing-whitespace-why-is-there-a-newline-at-the-end-of-the-file.html.4baf2edb.js"),["assets/why-is-atom-deleting-trailing-whitespace-why-is-there-a-newline-at-the-end-of-the-file.html.4baf2edb.js","assets/whitespace-settings.13b26c2a.js"])),"v-258423d8":g(()=>s(()=>import("./atom-basics.html.54e153e2.js"),["assets/atom-basics.html.54e153e2.js","assets/finder.04f876a5.js","assets/platform-selector.697b4dd8.js"])),"v-d48e3644":g(()=>s(()=>import("./installing-atom.html.501202c9.js"),["assets/installing-atom.html.501202c9.js","assets/windows-downloads.e9b59e10.js","assets/windows-system-settings.141561e2.js"])),"v-3162898f":g(()=>s(()=>import("./summary.html.a00ca4a9.js"),[])),"v-60602147":g(()=>s(()=>import("./why-atom.html.68b5b6aa.js"),[])),"v-00ff85c6":g(()=>s(()=>import("./contributing-to-official-atom-packages.html.d63ab9ab.js"),[])),"v-38bce125":g(()=>s(()=>import("./converting-from-textmate.html.25b3df05.js"),[])),"v-752e2619":g(()=>s(()=>import("./creating-a-fork-of-a-core-package-in-atom-atom.html.8f3be404.js"),[])),"v-7741ac96":g(()=>s(()=>import("./creating-a-grammar.html.5cf23f52.js"),[])),"v-41063e28":g(()=>s(()=>import("./creating-a-legacy-textmate-grammar.html.87a1089f.js"),[])),"v-049d4d93":g(()=>s(()=>import("./creating-a-theme.html.9a1c6a33.js"),["assets/creating-a-theme.html.9a1c6a33.js","assets/theme-side-by-side.33cf8d5d.js","assets/dev-tools.1b7b2813.js"])),"v-0762ec4e":g(()=>s(()=>import("./cross-platform-compatibility.html.ce60d4e3.js"),[])),"v-28cbb2a8":g(()=>s(()=>import("./debugging.html.361d347b.js"),["assets/debugging.html.361d347b.js","assets/cpu-profile-done.c24435bc.js"])),"v-bb85f9fa":g(()=>s(()=>import("./hacking-on-atom-core.html.65b1ff88.js"),[])),"v-06c8b972":g(()=>s(()=>import("./handling-uris.html.595a8f9e.js"),[])),"v-5746d296":g(()=>s(()=>import("./iconography.html.d80c4c08.js"),["assets/iconography.html.d80c4c08.js","assets/iconography.bf3cea92.js"])),"v-be0ab716":g(()=>s(()=>import("./maintaining-a-fork-of-a-core-package-in-atom-atom.html.81c74f67.js"),[])),"v-5d85798e":g(()=>s(()=>import("./package-active-editor-info.html.765e6517.js"),[])),"v-4284f843":g(()=>s(()=>import("./package-modifying-text.html.02ee0996.js"),[])),"v-36ba24e9":g(()=>s(()=>import("./package-word-count.html.0ef1249a.js"),["assets/package-word-count.html.0ef1249a.js","assets/spec-suite.8f5e854d.js","assets/dev-tools.1b7b2813.js"])),"v-5d0a102e":g(()=>s(()=>import("./publishing.html.6359d45b.js"),[])),"v-7d723830":g(()=>s(()=>import("./summary.html.b8867dc0.js"),[])),"v-7061e28e":g(()=>s(()=>import("./the-init-file.html.3d79e2ce.js"),[])),"v-5aa3b8b8":g(()=>s(()=>import("./tools-of-the-trade.html.9e3c72a0.js"),[])),"v-f2fe7262":g(()=>s(()=>import("./writing-specs.html.ccd20a54.js"),[])),"v-53169d12":g(()=>s(()=>import("./glossary.html.4f5f8a73.js"),[])),"v-5a059e16":g(()=>s(()=>import("./removing-shadow-dom-styles.html.58174f11.js"),[])),"v-8f696676":g(()=>s(()=>import("./upgrading-your-package.html.6a49dd16.js"),["assets/upgrading-your-package.html.6a49dd16.js","assets/spec-deps.b3f6a1b6.js","assets/dep-cop.6353fb49.js"])),"v-416277a8":g(()=>s(()=>import("./upgrading-your-syntax-theme.html.01458e4b.js"),[])),"v-3bf32d2b":g(()=>s(()=>import("./upgrading-your-ui-theme-or-package-selectors.html.32b0695c.js"),["assets/upgrading-your-ui-theme-or-package-selectors.html.32b0695c.js","assets/dep-cop.6353fb49.js"])),"v-2cf2a68a":g(()=>s(()=>import("./atom-packages.html.4f74879c.js"),["assets/atom-packages.html.4f74879c.js","assets/unity-theme.516a5ae9.js"])),"v-0fe6afd0":g(()=>s(()=>import("./atom-selections.html.308f14c6.js"),[])),"v-2c165692":g(()=>s(()=>import("./autocomplete.html.b253649f.js"),["assets/autocomplete.html.b253649f.js","assets/autocomplete.b0bae406.js"])),"v-61e2f142":g(()=>s(()=>import("./basic-customization.html.d526d0ac.js"),["assets/basic-customization.html.d526d0ac.js","assets/menubar.df752f94.js","assets/portable-mode-folder.90669a9a.js"])),"v-34299b28":g(()=>s(()=>import("./editing-and-deleting-text.html.1d230db3.js"),["assets/editing-and-deleting-text.html.1d230db3.js","assets/encodings.f93acf64.js"])),"v-0311d835":g(()=>s(()=>import("./find-and-replace.html.a6a6d2f5.js"),["assets/find-and-replace.html.a6a6d2f5.js","assets/find-replace-project.ada882a7.js"])),"v-6252cab2":g(()=>s(()=>import("./folding.html.6d44f0d2.js"),["assets/folding.html.6d44f0d2.js","assets/folding.6d09f8df.js"])),"v-faca997a":g(()=>s(()=>import("./github-package.html.4c719713.js"),["assets/github-package.html.4c719713.js","assets/github-review-reply.6d405e4b.js"])),"v-3ae8d9e8":g(()=>s(()=>import("./grammar.html.b1bb0d35.js"),["assets/grammar.html.b1bb0d35.js","assets/grammar.c16b8cd6.js"])),"v-4d358836":g(()=>s(()=>import("./moving-in-atom.html.f0c85e72.js"),["assets/moving-in-atom.html.f0c85e72.js","assets/symbol.b88bf5a9.js"])),"v-0d49e5a8":g(()=>s(()=>import("./panes.html.4b274c2a.js"),["assets/panes.html.4b274c2a.js","assets/panes.a7cec271.js"])),"v-52fd2aae":g(()=>s(()=>import("./pending-pane-items.html.b95fbf84.js"),["assets/pending-pane-items.html.b95fbf84.js","assets/allow-pending-pane-items.0fe3dd4a.js"])),"v-3aec9d69":g(()=>s(()=>import("./snippets.html.19dc3661.js"),["assets/snippets.html.19dc3661.js","assets/snippet-scope.1f381b52.js"])),"v-21ac7a6d":g(()=>s(()=>import("./summary.html.27ef905f.js"),[])),"v-fd5de582":g(()=>s(()=>import("./version-control-in-atom.html.6159267b.js"),["assets/version-control-in-atom.html.6159267b.js","assets/open-on-github.6fa9b166.js"])),"v-783f784b":g(()=>s(()=>import("./writing-in-atom.html.1d45f779.js"),["assets/writing-in-atom.html.1d45f779.js","assets/preview.2eaf146a.js"])),"v-0aa2ec94":g(()=>s(()=>import("./index.html.05af89eb.js"),["assets/index.html.05af89eb.js","assets/keybinding.964a4e0d.js","assets/markup.30f112ce.js"])),"v-278e8fc5":g(()=>s(()=>import("./index.html.08b5d061.js"),["assets/index.html.08b5d061.js","assets/spec-suite.8f5e854d.js","assets/dev-tools.1b7b2813.js","assets/theme-side-by-side.33cf8d5d.js","assets/iconography.bf3cea92.js","assets/cpu-profile-done.c24435bc.js"])),"v-3e3eb0e0":g(()=>s(()=>import("./index.html.a5b4ee80.js"),[])),"v-35eded6e":g(()=>s(()=>import("./index.html.11b1f262.js"),["assets/index.html.11b1f262.js","assets/windows-system-settings.141561e2.js","assets/finder.04f876a5.js"])),"v-44f94232":g(()=>s(()=>import("./index.html.be37943f.js"),["assets/index.html.be37943f.js","assets/unity-theme.516a5ae9.js","assets/symbol.b88bf5a9.js","assets/bookmarks.736c4e14.js","assets/encodings.f93acf64.js","assets/find-replace-project.ada882a7.js","assets/snippet-scope.1f381b52.js","assets/autocomplete.b0bae406.js","assets/folding.6d09f8df.js","assets/panes.a7cec271.js","assets/allow-pending-pane-items.0fe3dd4a.js","assets/grammar.c16b8cd6.js","assets/open-on-github.6fa9b166.js","assets/github-review-reply.6d405e4b.js","assets/preview.2eaf146a.js","assets/portable-mode-folder.90669a9a.js"])),"v-7412c3f9":g(()=>s(()=>import("./index.html.5cd06018.js"),[])),"v-4119e722":g(()=>s(()=>import("./list.html.fb443d07.js"),[])),"v-4da9456e":g(()=>s(()=>import("./release-process.html.16f6a0ce.js"),[])),"v-4ebc0363":g(()=>s(()=>import("./autocomplete-providers.html.2ebd5ee8.js"),[])),"v-6df8120e":g(()=>s(()=>import("./index.html.e11796f0.js"),[])),"v-5d8b44fc":g(()=>s(()=>import("./provider-api.html.c71bfab3.js"),[])),"v-0c09001f":g(()=>s(()=>import("./symbolprovider-config-api.html.63c64aa2.js"),[])),"v-04523903":g(()=>s(()=>import("./index.html.4bb12063.js"),[])),"v-6a77de6d":g(()=>s(()=>import("./june-2017.html.924b5d43.js"),[])),"v-3e52d25b":g(()=>s(()=>import("./incomplete-classpath-warning.html.efd2128c.js"),[])),"v-49f587fe":g(()=>s(()=>import("./index.html.0618f05b.js"),[])),"v-c8081340":g(()=>s(()=>import("./blog-guide.html.01a5daf0.js"),[])),"v-fa6566c6":g(()=>s(()=>import("./building.html.84c6f242.js"),[])),"v-c2b13dfe":g(()=>s(()=>import("./configuration-files.html.d70a7efb.js"),[])),"v-71e2ac9c":g(()=>s(()=>import("./document-style.html.83e9f03f.js"),[])),"v-f38bdb06":g(()=>s(()=>import("./file-organization.html.750afb1d.js"),[])),"v-4861ba81":g(()=>s(()=>import("./configuration-api.html.688150f0.js"),[])),"v-1a40e700":g(()=>s(()=>import("./developing-node-modules.html.7558848f.js"),[])),"v-0a930154":g(()=>s(()=>import("./interacting-with-other-packages-via-services.html.137a2a04.js"),[])),"v-563ab79c":g(()=>s(()=>import("./keymaps-in-depth.html.596e6691.js"),["assets/keymaps-in-depth.html.596e6691.js","assets/keybinding.964a4e0d.js"])),"v-322ec6da":g(()=>s(()=>import("./maintaining-your-packages.html.f9de6122.js"),[])),"v-56da0c3a":g(()=>s(()=>import("./scoped-settings-scopes-and-scope-descriptors.html.564ad54d.js"),["assets/scoped-settings-scopes-and-scope-descriptors.html.564ad54d.js","assets/markup.30f112ce.js"])),"v-33739e9c":g(()=>s(()=>import("./serialization-in-pulsar.html.6abe4c1d.js"),[])),"v-1b3c033e":g(()=>s(()=>import("./summary.html.17457156.js"),[])),"v-e880b502":g(()=>s(()=>import("./building-pulsar.html.806a23cc.js"),[])),"v-13ba24f3":g(()=>s(()=>import("./contributing-to-official-pulsar-packages.html.be326d7d.js"),[])),"v-f3d252b4":g(()=>s(()=>import("./converting-from-textmate.html.5e63d586.js"),[])),"v-6ef9d234":g(()=>s(()=>import("./creating-a-fork-of-a-core-package.html.e80cef32.js"),[])),"v-43bb93f6":g(()=>s(()=>import("./creating-a-grammar.html.741fc286.js"),[])),"v-1032ada6":g(()=>s(()=>import("./creating-a-legacy-textmate-grammar.html.895bfcba.js"),[])),"v-f63d57d8":g(()=>s(()=>import("./creating-a-theme.html.22be0ae0.js"),["assets/creating-a-theme.html.22be0ae0.js","assets/theme-side-by-side.33cf8d5d.js","assets/dev-tools.1b7b2813.js"])),"v-78bfd6da":g(()=>s(()=>import("./cross-platform-compatibility.html.ffa3c2d2.js"),[])),"v-c040e972":g(()=>s(()=>import("./debugging.html.13d250a3.js"),["assets/debugging.html.13d250a3.js","assets/cpu-profile-done.c24435bc.js"])),"v-143cd798":g(()=>s(()=>import("./hacking-on-the-core.html.e53f325e.js"),[])),"v-c277d734":g(()=>s(()=>import("./handling-uris.html.5686dc7c.js"),[])),"v-544678d8":g(()=>s(()=>import("./iconography.html.41f9df89.js"),["assets/iconography.html.41f9df89.js","assets/iconography.bf3cea92.js"])),"v-05ecf928":g(()=>s(()=>import("./maintaining-a-fork-of-a-core-package.html.d4161206.js"),[])),"v-878fda62":g(()=>s(()=>import("./package-active-editor-info.html.6db919df.js"),[])),"v-e59f4bf8":g(()=>s(()=>import("./package-modifying-text.html.ca206c57.js"),[])),"v-93d2e1ac":g(()=>s(()=>import("./package-word-count.html.96694544.js"),["assets/package-word-count.html.96694544.js","assets/spec-suite.8f5e854d.js","assets/dev-tools.1b7b2813.js"])),"v-3ce2332a":g(()=>s(()=>import("./publishing.html.2e08f39b.js"),[])),"v-82dca6e2":g(()=>s(()=>import("./summary.html.139b70e8.js"),[])),"v-69f77fd8":g(()=>s(()=>import("./the-init-file.html.a2ffb654.js"),[])),"v-4bffba0e":g(()=>s(()=>import("./tools-of-the-trade.html.06204920.js"),[])),"v-fbe0eb5e":g(()=>s(()=>import("./using-ppm.html.ba74ad98.js"),[])),"v-28a937ee":g(()=>s(()=>import("./writing-specs.html.44b3bd70.js"),[])),"v-76c0c524":g(()=>s(()=>import("./common-issues.html.95d1715c.js"),[])),"v-495d25f0":g(()=>s(()=>import("./get-help.html.8b9cc126.js"),[])),"v-8c05c62e":g(()=>s(()=>import("./installing-pulsar.html.42e59c39.js"),["assets/installing-pulsar.html.42e59c39.js","assets/windows-system-settings.141561e2.js"])),"v-6660d212":g(()=>s(()=>import("./pulsar-basics.html.47a95a49.js"),["assets/pulsar-basics.html.47a95a49.js","assets/finder.04f876a5.js"])),"v-14a6a286":g(()=>s(()=>import("./summary.html.1b92ae24.js"),[])),"v-1ca4faf8":g(()=>s(()=>import("./why-pulsar.html.36fb91be.js"),[])),"v-9ad007fc":g(()=>s(()=>import("./autocomplete.html.f60be63b.js"),["assets/autocomplete.html.f60be63b.js","assets/autocomplete.b0bae406.js"])),"v-55d9a5b4":g(()=>s(()=>import("./basic-customization.html.d03c4748.js"),["assets/basic-customization.html.d03c4748.js","assets/portable-mode-folder.90669a9a.js"])),"v-73d97d01":g(()=>s(()=>import("./editing-and-deleting-text.html.cf992d5a.js"),["assets/editing-and-deleting-text.html.cf992d5a.js","assets/encodings.f93acf64.js"])),"v-282b3c00":g(()=>s(()=>import("./find-and-replace.html.82039939.js"),["assets/find-and-replace.html.82039939.js","assets/find-replace-project.ada882a7.js"])),"v-ea7dfef2":g(()=>s(()=>import("./folding.html.aad93fbd.js"),["assets/folding.html.aad93fbd.js","assets/folding.6d09f8df.js"])),"v-a1dd9864":g(()=>s(()=>import("./github-package.html.3c4fde2e.js"),["assets/github-package.html.3c4fde2e.js","assets/github-review-reply.6d405e4b.js"])),"v-0af9c8e1":g(()=>s(()=>import("./grammar.html.e25e2511.js"),["assets/grammar.html.e25e2511.js","assets/grammar.c16b8cd6.js"])),"v-15cc7078":g(()=>s(()=>import("./moving-in-pulsar.html.c8a1d28a.js"),["assets/moving-in-pulsar.html.c8a1d28a.js","assets/symbol.b88bf5a9.js","assets/bookmarks.736c4e14.js"])),"v-76058cbd":g(()=>s(()=>import("./panes.html.690697a2.js"),["assets/panes.html.690697a2.js","assets/panes.a7cec271.js"])),"v-67da6db9":g(()=>s(()=>import("./pending-pane-items.html.71de0d56.js"),["assets/pending-pane-items.html.71de0d56.js","assets/allow-pending-pane-items.0fe3dd4a.js"])),"v-814e3bea":g(()=>s(()=>import("./pulsar-packages.html.22e50009.js"),["assets/pulsar-packages.html.22e50009.js","assets/unity-theme.516a5ae9.js"])),"v-bca7e1de":g(()=>s(()=>import("./pulsar-selections.html.6ca88fd0.js"),[])),"v-20452234":g(()=>s(()=>import("./snippets.html.f09874a6.js"),["assets/snippets.html.f09874a6.js","assets/snippet-scope.1f381b52.js"])),"v-4a1ab042":g(()=>s(()=>import("./summary.html.89a8b88a.js"),[])),"v-13272930":g(()=>s(()=>import("./version-control-in-pulsar.html.af5df576.js"),["assets/version-control-in-pulsar.html.af5df576.js","assets/open-on-github.6fa9b166.js"])),"v-e092af18":g(()=>s(()=>import("./writing-in-pulsar.html.226cb384.js"),["assets/writing-in-pulsar.html.226cb384.js","assets/preview.2eaf146a.js"])),"v-3706649a":g(()=>s(()=>import("./404.html.7cbe2b72.js"),[])),"v-5bc93818":g(()=>s(()=>import("./index.html.151d8d55.js"),[])),"v-744d024e":g(()=>s(()=>import("./index.html.941947fb.js"),[])),"v-145ac574":g(()=>s(()=>import("./index.html.1b0cb753.js"),[])),"v-75ed4ea4":g(()=>s(()=>import("./index.html.fbff6f41.js"),[])),"v-d804e652":g(()=>s(()=>import("./index.html.30ea87d2.js"),[])),"v-154dc4c4":g(()=>s(()=>import("./index.html.8ea55446.js"),[])),"v-01560935":g(()=>s(()=>import("./index.html.85a58c87.js"),[])),"v-65ee6ad2":g(()=>s(()=>import("./index.html.39ac8650.js"),[])),"v-0b77a069":g(()=>s(()=>import("./index.html.715b0c77.js"),[])),"v-65f23183":g(()=>s(()=>import("./index.html.e9497bdd.js"),[])),"v-f5401b6a":g(()=>s(()=>import("./index.html.89530304.js"),[])),"v-586be6a4":g(()=>s(()=>import("./index.html.5e5c1bba.js"),[])),"v-007c0ae2":g(()=>s(()=>import("./index.html.bb5ab2a9.js"),[])),"v-109540fd":g(()=>s(()=>import("./index.html.1cce4ebf.js"),[])),"v-33e16b10":g(()=>s(()=>import("./index.html.10fdafc2.js"),[])),"v-5b38f390":g(()=>s(()=>import("./index.html.90c99035.js"),[])),"v-f6458faa":g(()=>s(()=>import("./index.html.61294029.js"),[])),"v-6c28ad56":g(()=>s(()=>import("./index.html.077e976b.js"),[])),"v-18ed05d5":g(()=>s(()=>import("./index.html.f8b7ceda.js"),[])),"v-7d8d32d8":g(()=>s(()=>import("./index.html.b35e00fe.js"),[])),"v-6486a861":g(()=>s(()=>import("./index.html.8a1f00a1.js"),[])),"v-9ffc7398":g(()=>s(()=>import("./index.html.46dbbb19.js"),[])),"v-54365ad2":g(()=>s(()=>import("./index.html.956b9123.js"),[])),"v-28a80d22":g(()=>s(()=>import("./index.html.758e28c3.js"),[])),"v-9c858a88":g(()=>s(()=>import("./index.html.70b20295.js"),[])),"v-132a6ac4":g(()=>s(()=>import("./index.html.de5ae0cd.js"),[])),"v-08115088":g(()=>s(()=>import("./index.html.6811c420.js"),[])),"v-4a89825a":g(()=>s(()=>import("./index.html.320bab91.js"),[])),"v-7ab1f304":g(()=>s(()=>import("./index.html.719d38d5.js"),[])),"v-31f54a17":g(()=>s(()=>import("./index.html.421853eb.js"),[])),"v-0da0b37b":g(()=>s(()=>import("./index.html.00cec056.js"),[])),"v-181c8802":g(()=>s(()=>import("./index.html.563e9153.js"),[]))};const hp={};var To;const ta=typeof window<"u",pp=e=>typeof e=="function",mp=e=>typeof e=="string",hs=()=>{},Eo=ta&&((To=window==null?void 0:window.navigator)==null?void 0:To.userAgent)&&/iP(ad|hone|od)/.test(window.navigator.userAgent);function bt(e){return typeof e=="function"?e():at(e)}function _l(e,t){function i(...a){e(()=>t.apply(this,a),{fn:t,thisArg:this,args:a})}return i}const ps=e=>e();function gp(e,t={}){let i,a;return l=>{const o=bt(e),r=bt(t.maxWait);if(i&&clearTimeout(i),o<=0||r!==void 0&&r<=0)return a&&(clearTimeout(a),a=null),l();r&&!a&&(a=setTimeout(()=>{i&&clearTimeout(i),a=null,l()},r)),i=setTimeout(()=>{a&&clearTimeout(a),a=null,l()},o)}}function vp(e,t=!0,i=!0){let a=0,n,l=!0;const o=()=>{n&&(clearTimeout(n),n=void 0)};return c=>{const d=bt(e),h=Date.now()-a;if(o(),d<=0)return a=Date.now(),c();h>d&&(i||!l)?(a=Date.now(),c()):t&&(n=setTimeout(()=>{a=Date.now(),l=!0,o(),c()},d-h)),!i&&!n&&(n=setTimeout(()=>l=!0,d)),l=!1}}function fp(e=ps){const t=ne(!0);function i(){t.value=!1}function a(){t.value=!0}return{isActive:t,pause:i,resume:a,eventFilter:(...l)=>{t.value&&e(...l)}}}function _p(e){return e}function ja(e){return $u()?(Nu(e),!0):!1}function bp(e,t=200,i={}){return _l(gp(t,i),e)}function kp(e,t=200,i=!1,a=!0){return _l(vp(t,i,a),e)}function ms(e){return typeof e=="function"?P(e):ne(e)}function gs(e,t=!0){ea()?Se(e):t?e():sl(e)}function yp(e){ea()&&dl(e)}var Po=Object.getOwnPropertySymbols,wp=Object.prototype.hasOwnProperty,xp=Object.prototype.propertyIsEnumerable,Tp=(e,t)=>{var i={};for(var a in e)wp.call(e,a)&&t.indexOf(a)<0&&(i[a]=e[a]);if(e!=null&&Po)for(var a of Po(e))t.indexOf(a)<0&&xp.call(e,a)&&(i[a]=e[a]);return i};function Ep(e,t,i={}){const a=i,{eventFilter:n=ps}=a,l=Tp(a,["eventFilter"]);return De(e,_l(n,t),l)}var Pp=Object.defineProperty,Lp=Object.defineProperties,Dp=Object.getOwnPropertyDescriptors,Ra=Object.getOwnPropertySymbols,vs=Object.prototype.hasOwnProperty,fs=Object.prototype.propertyIsEnumerable,Lo=(e,t,i)=>t in e?Pp(e,t,{enumerable:!0,configurable:!0,writable:!0,value:i}):e[t]=i,Ap=(e,t)=>{for(var i in t||(t={}))vs.call(t,i)&&Lo(e,i,t[i]);if(Ra)for(var i of Ra(t))fs.call(t,i)&&Lo(e,i,t[i]);return e},Ip=(e,t)=>Lp(e,Dp(t)),Op=(e,t)=>{var i={};for(var a in e)vs.call(e,a)&&t.indexOf(a)<0&&(i[a]=e[a]);if(e!=null&&Ra)for(var a of Ra(e))t.indexOf(a)<0&&fs.call(e,a)&&(i[a]=e[a]);return i};function Rp(e,t,i={}){const a=i,{eventFilter:n}=a,l=Op(a,["eventFilter"]),{eventFilter:o,pause:r,resume:c,isActive:d}=fp(n);return{stop:Ep(e,t,Ip(Ap({},l),{eventFilter:o})),pause:r,resume:c,isActive:d}}function _s(e){var t;const i=bt(e);return(t=i==null?void 0:i.$el)!=null?t:i}const Ca=ta?window:void 0,bs=ta?window.document:void 0;ta&&window.navigator;ta&&window.location;function Bt(...e){let t,i,a,n;if(mp(e[0])||Array.isArray(e[0])?([i,a,n]=e,t=Ca):[t,i,a,n]=e,!t)return hs;Array.isArray(i)||(i=[i]),Array.isArray(a)||(a=[a]);const l=[],o=()=>{l.forEach(h=>h()),l.length=0},r=(h,p,m)=>(h.addEventListener(p,m,n),()=>h.removeEventListener(p,m,n)),c=De(()=>_s(t),h=>{o(),h&&l.push(...i.flatMap(p=>a.map(m=>r(h,p,m))))},{immediate:!0,flush:"post"}),d=()=>{c(),o()};return ja(d),d}function ks(e,t=!1){const i=ne(),a=()=>i.value=Boolean(e());return a(),gs(a,t),i}function Cp(e,t={}){const{window:i=Ca}=t,a=ks(()=>i&&"matchMedia"in i&&typeof i.matchMedia=="function");let n;const l=ne(!1),o=()=>{!n||("removeEventListener"in n?n.removeEventListener("change",r):n.removeListener(r))},r=()=>{!a.value||(o(),n=i.matchMedia(ms(e).value),l.value=n.matches,"addEventListener"in n?n.addEventListener("change",r):n.addListener(r))};return Rd(r),ja(()=>o()),l}const In=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{},On="__vueuse_ssr_handlers__";In[On]=In[On]||{};const Sp=In[On];function Vp(e,t){return Sp[e]||t}function Mp(e){return e==null?"any":e instanceof Set?"set":e instanceof Map?"map":e instanceof Date?"date":typeof e=="boolean"?"boolean":typeof e=="string"?"string":typeof e=="object"?"object":Number.isNaN(e)?"any":"number"}var Fp=Object.defineProperty,Do=Object.getOwnPropertySymbols,zp=Object.prototype.hasOwnProperty,Hp=Object.prototype.propertyIsEnumerable,Ao=(e,t,i)=>t in e?Fp(e,t,{enumerable:!0,configurable:!0,writable:!0,value:i}):e[t]=i,Io=(e,t)=>{for(var i in t||(t={}))zp.call(t,i)&&Ao(e,i,t[i]);if(Do)for(var i of Do(t))Hp.call(t,i)&&Ao(e,i,t[i]);return e};const $p={boolean:{read:e=>e==="true",write:e=>String(e)},object:{read:e=>JSON.parse(e),write:e=>JSON.stringify(e)},number:{read:e=>Number.parseFloat(e),write:e=>String(e)},any:{read:e=>e,write:e=>String(e)},string:{read:e=>e,write:e=>String(e)},map:{read:e=>new Map(JSON.parse(e)),write:e=>JSON.stringify(Array.from(e.entries()))},set:{read:e=>new Set(JSON.parse(e)),write:e=>JSON.stringify(Array.from(e))},date:{read:e=>new Date(e),write:e=>e.toISOString()}};function ys(e,t,i,a={}){var n;const{flush:l="pre",deep:o=!0,listenToStorageChanges:r=!0,writeDefaults:c=!0,mergeDefaults:d=!1,shallow:h,window:p=Ca,eventFilter:m,onError:f=T=>{console.error(T)}}=a,y=(h?Dr:ne)(t);if(!i)try{i=Vp("getDefaultStorage",()=>{var T;return(T=Ca)==null?void 0:T.localStorage})()}catch(T){f(T)}if(!i)return y;const _=bt(t),x=Mp(_),b=(n=a.serializer)!=null?n:$p[x],{pause:w,resume:E}=Rp(y,()=>C(y.value),{flush:l,deep:o,eventFilter:m});return p&&r&&Bt(p,"storage",S),S(),y;function C(T){try{T==null?i.removeItem(e):i.setItem(e,b.write(T))}catch(A){f(A)}}function F(T){w();try{const A=T?T.newValue:i.getItem(e);if(A==null)return c&&_!==null&&i.setItem(e,b.write(_)),_;if(!T&&d){const N=b.read(A);return pp(d)?d(N,_):x==="object"&&!Array.isArray(N)?Io(Io({},_),N):N}else return typeof A!="string"?A:b.read(A)}catch(A){f(A)}finally{E()}}function S(T){if(!(T&&T.storageArea!==i)){if(T&&T.key===null){y.value=_;return}T&&T.key!==e||(y.value=F(T))}}}function Np(e){return Cp("(prefers-color-scheme: dark)",e)}const Oo=[["requestFullscreen","exitFullscreen","fullscreenElement","fullscreenEnabled","fullscreenchange","fullscreenerror"],["webkitRequestFullscreen","webkitExitFullscreen","webkitFullscreenElement","webkitFullscreenEnabled","webkitfullscreenchange","webkitfullscreenerror"],["webkitRequestFullScreen","webkitCancelFullScreen","webkitCurrentFullScreenElement","webkitCancelFullScreen","webkitfullscreenchange","webkitfullscreenerror"],["mozRequestFullScreen","mozCancelFullScreen","mozFullScreenElement","mozFullScreenEnabled","mozfullscreenchange","mozfullscreenerror"],["msRequestFullscreen","msExitFullscreen","msFullscreenElement","msFullscreenEnabled","MSFullscreenChange","MSFullscreenError"]];function bl(e,t={}){const{document:i=bs,autoExit:a=!1}=t,n=e||(i==null?void 0:i.querySelector("html")),l=ne(!1);let o=Oo[0];const r=ks(()=>{if(i){for(const _ of Oo)if(_[1]in i)return o=_,!0}else return!1;return!1}),[c,d,h,,p]=o;async function m(){!r.value||(i!=null&&i[h]&&await i[d](),l.value=!1)}async function f(){if(!r.value)return;await m();const _=_s(n);_&&(await _[c](),l.value=!0)}async function y(){l.value?await m():await f()}return i&&Bt(i,p,()=>{l.value=!!(i!=null&&i[h])},!1),a&&ja(m),{isSupported:r,isFullscreen:l,enter:f,exit:m,toggle:y}}var Ro;(function(e){e.UP="UP",e.RIGHT="RIGHT",e.DOWN="DOWN",e.LEFT="LEFT",e.NONE="NONE"})(Ro||(Ro={}));function qp(e,t=hs,i={}){const{immediate:a=!0,manual:n=!1,type:l="text/javascript",async:o=!0,crossOrigin:r,referrerPolicy:c,noModule:d,defer:h,document:p=bs,attrs:m={}}=i,f=ne(null);let y=null;const _=w=>new Promise((E,C)=>{const F=A=>(f.value=A,E(A),A);if(!p){E(!1);return}let S=!1,T=p.querySelector(`script[src="${bt(e)}"]`);T?T.hasAttribute("data-loaded")&&F(T):(T=p.createElement("script"),T.type=l,T.async=o,T.src=bt(e),h&&(T.defer=h),r&&(T.crossOrigin=r),d&&(T.noModule=d),c&&(T.referrerPolicy=c),Object.entries(m).forEach(([A,N])=>T==null?void 0:T.setAttribute(A,N)),S=!0),T.addEventListener("error",A=>C(A)),T.addEventListener("abort",A=>C(A)),T.addEventListener("load",()=>{T.setAttribute("data-loaded","true"),t(T),F(T)}),S&&(T=p.head.appendChild(T)),w||F(T)}),x=(w=!0)=>(y||(y=_(w)),y),b=()=>{if(!p)return;y=null,f.value&&(f.value=null);const w=p.querySelector(`script[src="${bt(e)}"]`);w&&p.head.removeChild(w)};return a&&!n&&gs(x),n||yp(b),{scriptTag:f,load:x,unload:b}}function Bp(e){const t=e||window.event;return t.touches.length>1?!0:(t.preventDefault&&t.preventDefault(),!1)}function Up(e,t=!1){const i=ne(t);let a=null,n;De(ms(e),r=>{if(r){const c=r;n=c.style.overflow,i.value&&(c.style.overflow="hidden")}},{immediate:!0});const l=()=>{const r=bt(e);!r||i.value||(Eo&&(a=Bt(r,"touchmove",Bp,{passive:!1})),r.style.overflow="hidden",i.value=!0)},o=()=>{const r=bt(e);!r||!i.value||(Eo&&(a==null||a()),r.style.overflow=n,i.value=!1)};return ja(o),P({get(){return i.value},set(r){r?l():o()}})}var jp=Object.defineProperty,Co=Object.getOwnPropertySymbols,Wp=Object.prototype.hasOwnProperty,Gp=Object.prototype.propertyIsEnumerable,So=(e,t,i)=>t in e?jp(e,t,{enumerable:!0,configurable:!0,writable:!0,value:i}):e[t]=i,Yp=(e,t)=>{for(var i in t||(t={}))Wp.call(t,i)&&So(e,i,t[i]);if(Co)for(var i of Co(t))Gp.call(t,i)&&So(e,i,t[i]);return e};const Kp={easeInSine:[.12,0,.39,0],easeOutSine:[.61,1,.88,1],easeInOutSine:[.37,0,.63,1],easeInQuad:[.11,0,.5,0],easeOutQuad:[.5,1,.89,1],easeInOutQuad:[.45,0,.55,1],easeInCubic:[.32,0,.67,0],easeOutCubic:[.33,1,.68,1],easeInOutCubic:[.65,0,.35,1],easeInQuart:[.5,0,.75,0],easeOutQuart:[.25,1,.5,1],easeInOutQuart:[.76,0,.24,1],easeInQuint:[.64,0,.78,0],easeOutQuint:[.22,1,.36,1],easeInOutQuint:[.83,0,.17,1],easeInExpo:[.7,0,.84,0],easeOutExpo:[.16,1,.3,1],easeInOutExpo:[.87,0,.13,1],easeInCirc:[.55,0,1,.45],easeOutCirc:[0,.55,.45,1],easeInOutCirc:[.85,0,.15,1],easeInBack:[.36,0,.66,-.56],easeOutBack:[.34,1.56,.64,1],easeInOutBack:[.68,-.6,.32,1.6]};Yp({linear:_p},Kp);var kt=(e={})=>e;const ws=({type:e="info",text:t="",vertical:i="top",color:a},{slots:n})=>{var l;return u("span",{class:["badge",e,{diy:a}],style:{verticalAlign:i,...a?{backgroundColor:a}:{}}},t||((l=n.default)==null?void 0:l.call(n)))};ws.displayName="Badge";const Rn=({icon:e="",color:t,size:i})=>e?u("span",{class:["icon",`fas fa-${e}`],...t||i?{style:{...t?{color:t}:{},...i?{"font-size":`${i}px`}:{}}}:{}}):null;Rn.displayName="FontIcon",Rn.props={icon:String,color:String,size:Number};const pe=({name:e="",color:t="currentColor"},{slots:i})=>{var a;return u("svg",{xmlns:"http://www.w3.org/2000/svg",class:["icon",`${e}-icon`],viewBox:"0 0 1024 1024",fill:t,"aria-label":`${e} icon`},(a=i.default)==null?void 0:a.call(i))};pe.displayName="IconBase";const xs=(e,{slots:t})=>{var i;return((i=t.default)==null?void 0:i.call(t))||null},Wi=e=>{const t=ea();return typeof(t==null?void 0:t.appContext.components)=="object"&&(e in t.appContext.components||rt(e)in t.appContext.components||Zi(rt(e))in t.appContext.components)},ia=e=>{const t=yt();return P(()=>e[t.value])},Wa=(e,t)=>{let i=1;for(let a=0;a>6;return i+=i<<3,i^=i>>11,i%t};class Ts{constructor(){this.messageElements={};const t="message-container",i=document.getElementById(t);i?this.containerElement=i:(this.containerElement=document.createElement("div"),this.containerElement.id=t,document.body.appendChild(this.containerElement))}pop(t,i=2e3){const a=document.createElement("div"),n=Date.now();return a.className="message move-in",a.innerHTML=t,this.containerElement.appendChild(a),this.messageElements[n]=a,i>0&&setTimeout(()=>{this.close(n)},i),n}close(t){if(t){const i=this.messageElements[t];i.className=i.className.replace("move-in",""),i.className+="move-out",i.addEventListener("animationend",()=>{i.remove(),delete this.messageElements[t]})}else Object.keys(this.messageElements).forEach(i=>this.close(Number(i)))}destory(){document.body.removeChild(this.containerElement)}}const Es=/#.*$/u,Zp=e=>{const t=Es.exec(e);return t?t[0]:""},Vo=e=>decodeURI(e).replace(Es,"").replace(/(index)?\.(md|html)$/,""),kl=(e,t)=>{if(t===void 0)return!1;const i=Vo(e.path),a=Vo(t),n=Zp(t);return n?n===e.hash&&(!a||i===a):i===a};var aa=Uint8Array,ri=Uint16Array,Jp=Uint32Array,Qp=new aa([0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0,0]),Xp=new aa([0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13,0,0]),Ps=function(e,t){for(var i=new ri(31),a=0;a<31;++a)i[a]=t+=1<>>1|(Le&21845)<<1;St=(St&52428)>>>2|(St&13107)<<2,St=(St&61680)>>>4|(St&3855)<<4,Cn[Le]=((St&65280)>>>8|(St&255)<<8)>>>1}for(var Ga=function(t,i,a){for(var n=t.length,l=0,o=new ri(i);l>>d]=h}else for(c=new ri(n),l=0;l>>15-t[l]);return c},Ti=new aa(288),Le=0;Le<144;++Le)Ti[Le]=8;for(var Le=144;Le<256;++Le)Ti[Le]=9;for(var Le=256;Le<280;++Le)Ti[Le]=7;for(var Le=280;Le<288;++Le)Ti[Le]=8;for(var yl=new aa(32),Le=0;Le<32;++Le)yl[Le]=5;Ga(Ti,9,0);Ga(Ti,9,1);Ga(yl,5,0);Ga(yl,5,1);var im=new aa(0);typeof TextEncoder<"u"&&new TextEncoder;var am=typeof TextDecoder<"u"&&new TextDecoder,nm=0;try{am.decode(im,{stream:!0}),nm=1}catch{}const lm=e=>typeof e=="function",om=e=>typeof e=="string",na=(e,...t)=>{const i=e.resolve(...t),a=i.matched[i.matched.length-1];if(!(a!=null&&a.redirect))return i;const{redirect:n}=a,l=lm(n)?n(i):n,o=om(l)?{path:l}:l;return na(e,{hash:i.hash,query:i.query,params:i.params,...o})};var Ya=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{},Ds={exports:{}};(function(e,t){(function(i,a){e.exports=a()})(Ya,function(){var i=1e3,a=6e4,n=36e5,l="millisecond",o="second",r="minute",c="hour",d="day",h="week",p="month",m="quarter",f="year",y="date",_="Invalid Date",x=/^(\d{4})[-/]?(\d{1,2})?[-/]?(\d{0,2})[Tt\s]*(\d{1,2})?:?(\d{1,2})?:?(\d{1,2})?[.:]?(\d+)?$/,b=/\[([^\]]+)]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g,w={name:"en",weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_")},E=function(B,D,H){var W=String(B);return!W||W.length>=D?B:""+Array(D+1-W.length).join(H)+B},C={s:E,z:function(B){var D=-B.utcOffset(),H=Math.abs(D),W=Math.floor(H/60),M=H%60;return(D<=0?"+":"-")+E(W,2,"0")+":"+E(M,2,"0")},m:function B(D,H){if(D.date()1)return B(ee[0])}else{var de=D.name;S[de]=D,M=de}return!W&&M&&(F=M),M||!W&&F},N=function(B,D){if(T(B))return B.clone();var H=typeof D=="object"?D:{};return H.date=B,H.args=arguments,new le(H)},z=C;z.l=A,z.i=T,z.w=function(B,D){return N(B,{locale:D.$L,utc:D.$u,x:D.$x,$offset:D.$offset})};var le=function(){function B(H){this.$L=A(H.locale,null,!0),this.parse(H)}var D=B.prototype;return D.parse=function(H){this.$d=function(W){var M=W.date,J=W.utc;if(M===null)return new Date(NaN);if(z.u(M))return new Date;if(M instanceof Date)return new Date(M);if(typeof M=="string"&&!/Z$/i.test(M)){var ee=M.match(x);if(ee){var de=ee[2]-1||0,fe=(ee[7]||"0").substring(0,3);return J?new Date(Date.UTC(ee[1],de,ee[3]||1,ee[4]||0,ee[5]||0,ee[6]||0,fe)):new Date(ee[1],de,ee[3]||1,ee[4]||0,ee[5]||0,ee[6]||0,fe)}}return new Date(M)}(H),this.$x=H.x||{},this.init()},D.init=function(){var H=this.$d;this.$y=H.getFullYear(),this.$M=H.getMonth(),this.$D=H.getDate(),this.$W=H.getDay(),this.$H=H.getHours(),this.$m=H.getMinutes(),this.$s=H.getSeconds(),this.$ms=H.getMilliseconds()},D.$utils=function(){return z},D.isValid=function(){return this.$d.toString()!==_},D.isSame=function(H,W){var M=N(H);return this.startOf(W)<=M&&M<=this.endOf(W)},D.isAfter=function(H,W){return N(H)=0?1:x.date()),w=_.year||x.year(),E=_.month>=0?_.month:_.year||_.day?0:x.month(),C=_.hour||0,F=_.minute||0,S=_.second||0,T=_.millisecond||0;return y?new Date(Date.UTC(w,E,b,C,F,S,T)):new Date(w,E,b,C,F,S,T)}return f},r=l.parse;l.parse=function(p){p.date=o.bind(this)(p),r.bind(this)(p)};var c=l.set,d=l.add,h=function(p,m,f,y){if(y===void 0&&(y=1),m instanceof Object){var _=Object.keys(m),x=this;return _.forEach(function(b){x=p.bind(x)(m[b]*y,b)}),x}return p.bind(this)(m*y,f)};l.set=function(p,m){return m=m===void 0?p:m,h.bind(this)(function(f,y){return c.bind(this)(y,f)},m,p)},l.add=function(p,m){return h.bind(this)(d,p,m)},l.subtract=function(p,m){return h.bind(this)(d,p,m,-1)}}})})(As);var rm=As.exports,Is={exports:{}};(function(e,t){(function(i,a){e.exports=a()})(Ya,function(){var i={year:0,month:1,day:2,hour:3,minute:4,second:5},a={};return function(n,l,o){var r,c=function(m,f,y){y===void 0&&(y={});var _=new Date(m),x=function(b,w){w===void 0&&(w={});var E=w.timeZoneName||"short",C=b+"|"+E,F=a[C];return F||(F=new Intl.DateTimeFormat("en-US",{hour12:!1,timeZone:b,year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit",timeZoneName:E}),a[C]=F),F}(f,y);return x.formatToParts(_)},d=function(m,f){for(var y=c(m,f),_=[],x=0;x=0&&(_[C]=parseInt(E,10))}var F=_[3],S=F===24?0:F,T=_[0]+"-"+_[1]+"-"+_[2]+" "+S+":"+_[4]+":"+_[5]+":000",A=+m;return(o.utc(T).valueOf()-(A-=A%1e3))/6e4},h=l.prototype;h.tz=function(m,f){m===void 0&&(m=r);var y=this.utcOffset(),_=this.toDate(),x=_.toLocaleString("en-US",{timeZone:m}),b=Math.round((_-new Date(x))/1e3/60),w=o(x).$set("millisecond",this.$ms).utcOffset(15*-Math.round(_.getTimezoneOffset()/15)-b,!0);if(f){var E=w.utcOffset();w=w.add(y-E,"minute")}return w.$x.$timezone=m,w},h.offsetName=function(m){var f=this.$x.$timezone||o.tz.guess(),y=c(this.valueOf(),f,{timeZoneName:m}).find(function(_){return _.type.toLowerCase()==="timezonename"});return y&&y.value};var p=h.startOf;h.startOf=function(m,f){if(!this.$x||!this.$x.$timezone)return p.call(this,m,f);var y=o(this.format("YYYY-MM-DD HH:mm:ss:SSS"));return p.call(y,m,f).tz(this.$x.$timezone,!0)},o.tz=function(m,f,y){var _=y&&f,x=y||f||r,b=d(+o(),x);if(typeof m!="string")return o(m).tz(x);var w=function(S,T,A){var N=S-60*T*1e3,z=d(N,A);if(T===z)return[N,T];var le=d(N-=60*(z-T)*1e3,A);return z===le?[N,z]:[S-60*Math.min(z,le)*1e3,Math.max(z,le)]}(o.utc(m,_).valueOf(),b,x),E=w[0],C=w[1],F=o(E).utcOffset(C);return F.$x.$timezone=x,F},o.tz.guess=function(){return Intl.DateTimeFormat().resolvedOptions().timeZone},o.tz.setDefault=function(m){r=m}}})})(Is);var sm=Is.exports,Os={exports:{}};(function(e,t){(function(i,a){e.exports=a()})(Ya,function(){var i="minute",a=/[+-]\d\d(?::?\d\d)?/g,n=/([+-]|\d\d)/g;return function(l,o,r){var c=o.prototype;r.utc=function(_){var x={date:_,utc:!0,args:arguments};return new o(x)},c.utc=function(_){var x=r(this.toDate(),{locale:this.$L,utc:!0});return _?x.add(this.utcOffset(),i):x},c.local=function(){return r(this.toDate(),{locale:this.$L,utc:!1})};var d=c.parse;c.parse=function(_){_.utc&&(this.$u=!0),this.$utils().u(_.$offset)||(this.$offset=_.$offset),d.call(this,_)};var h=c.init;c.init=function(){if(this.$u){var _=this.$d;this.$y=_.getUTCFullYear(),this.$M=_.getUTCMonth(),this.$D=_.getUTCDate(),this.$W=_.getUTCDay(),this.$H=_.getUTCHours(),this.$m=_.getUTCMinutes(),this.$s=_.getUTCSeconds(),this.$ms=_.getUTCMilliseconds()}else h.call(this)};var p=c.utcOffset;c.utcOffset=function(_,x){var b=this.$utils().u;if(b(_))return this.$u?0:b(this.$offset)?p.call(this):this.$offset;if(typeof _=="string"&&(_=function(F){F===void 0&&(F="");var S=F.match(a);if(!S)return null;var T=(""+S[0]).match(n)||["-",0,0],A=T[0],N=60*+T[1]+ +T[2];return N===0?0:A==="+"?N:-N}(_),_===null))return this;var w=Math.abs(_)<=16?60*_:_,E=this;if(x)return E.$offset=w,E.$u=_===0,E;if(_!==0){var C=this.$u?this.toDate().getTimezoneOffset():-1*this.utcOffset();(E=this.local().add(w+C,i)).$offset=w,E.$x.$localOffset=C}else E=this.utc();return E};var m=c.format;c.format=function(_){var x=_||(this.$u?"YYYY-MM-DDTHH:mm:ss[Z]":"");return m.call(this,x)},c.valueOf=function(){var _=this.$utils().u(this.$offset)?0:this.$offset+(this.$x.$localOffset||this.$d.getTimezoneOffset());return this.$d.valueOf()-6e4*_},c.isUTC=function(){return!!this.$u},c.toISOString=function(){return this.toDate().toISOString()},c.toString=function(){return this.toDate().toUTCString()};var f=c.toDate;c.toDate=function(_){return _==="s"&&this.$offset?r(this.format("YYYY-MM-DD HH:mm:ss:SSS")).toDate():f.call(this)};var y=c.diff;c.diff=function(_,x,b){if(_&&this.$u===_.$u)return y.call(this,_,x,b);var w=this.local(),E=r(_).local();return y.call(w,E,x,b)}}})})(Os);var cm=Os.exports;Qt.extend(rm),Qt.extend(cm),Qt.extend(sm);const Sa=(e,t)=>{if(e){if(Qt(e instanceof Date?e:e.trim()).isValid()){const a=t?Qt(e).tz(t):Qt(e),n=a.year(),l=a.month()+1,o=a.date(),r=a.hour(),c=a.minute(),d=a.second(),h=a.millisecond(),p=r===0&&c===0&&d===0&&h===0;return{value:a.toDate(),info:{year:n,month:l,day:o,...p?{}:{hour:r,minute:c,second:d}},type:p?"date":"full"}}const i=/(?:(\d{2,4})[/-](\d{1,2})[/-](\d{1,2}))?\s*(?:(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?)?/u.exec(e.trim());if(i){const[,a,n,l,o,r,c]=i,d=x=>typeof x>"u"?void 0:Number(x),h=x=>x&&x<100?x+2e3:x,p=x=>o&&r&&!c?0:x,m={year:h(d(a)),month:d(n),day:d(l),hour:d(o),minute:d(r),second:p(d(c))},f=a===void 0&&n===void 0&&l===void 0,y=o===void 0&&r===void 0&&c===void 0,_=Qt({...m,month:m.month-1}).toDate();return{value:f?void 0:_,info:y?{year:m.year,month:m.month,day:m.day}:f?{hour:m.hour,minute:m.minute,second:m.second}:m,type:f?"time":y?"date":"full"}}}return null},Gi=(e,t=!1)=>e?Array.isArray(e)?e.map(i=>typeof i=="string"?{name:i}:i):typeof e=="string"?[{name:e}]:typeof e=="object"&&e.name?[e]:(console.error(`Expect 'author' to be \`AuthorInfo[] | AuthorInfo | string[] | string ${t?"":"| false"} | undefined\`, but got`,e),[]):[],Rs=e=>{if(e){if(Array.isArray(e))return e;if(typeof e=="string")return[e];console.error("Expect 'category' to be `string[] | string | undefined`, but got",e)}return[]},Cs=e=>{if(e){if(Array.isArray(e))return e;if(typeof e=="string")return[e];console.error("Expect 'tag' to be `string[] | string | undefined`, but got",e)}return[]};const Ss=()=>u(pe,{name:"back-to-top"},()=>[u("path",{d:"M512 843.2c-36.2 0-66.4-13.6-85.8-21.8-10.8-4.6-22.6 3.6-21.8 15.2l7 102c.4 6.2 7.6 9.4 12.6 5.6l29-22c3.6-2.8 9-1.8 11.4 2l41 64.2c3 4.8 10.2 4.8 13.2 0l41-64.2c2.4-3.8 7.8-4.8 11.4-2l29 22c5 3.8 12.2.6 12.6-5.6l7-102c.8-11.6-11-20-21.8-15.2-19.6 8.2-49.6 21.8-85.8 21.8z"}),u("path",{d:"m795.4 586.2-96-98.2C699.4 172 513 32 513 32S324.8 172 324.8 488l-96 98.2c-3.6 3.6-5.2 9-4.4 14.2L261.2 824c1.8 11.4 14.2 17 23.6 10.8L419 744s41.4 40 94.2 40c52.8 0 92.2-40 92.2-40l134.2 90.8c9.2 6.2 21.6.6 23.6-10.8l37-223.8c.4-5.2-1.2-10.4-4.8-14zM513 384c-34 0-61.4-28.6-61.4-64s27.6-64 61.4-64c34 0 61.4 28.6 61.4 64S547 384 513 384z"})]);Ss.displayName="BacktoTopIcon";var um=K({name:"BackToTop",props:{threshold:{type:Number,default:300}},setup(e){const t=Pe(),i=ia({"/":{backToTop:"Back to top"}}),a=ne(0),n=P(()=>t.value.backToTop!==!1&&a.value>e.threshold),l=()=>window.pageYOffset||document.documentElement.scrollTop||document.body.scrollTop||0;return Se(()=>{a.value=l()}),Bt("scroll",bp(()=>{a.value=l()},100)),()=>u(Dt,{name:"fade"},()=>n.value?u("button",{class:"back-to-top","aria-label":i.value.backToTop,"data-balloon-pos":"left",onClick:()=>{window.scrollTo({top:0,behavior:"smooth"}),a.value=0}},u(Ss)):null)}});const dm=kt({enhance:({app:e})=>{e.component("Badge",ws),e.component("FontIcon",Rn)},setup:()=>{qp("https://kit.fontawesome.com/ca37c296c5.js")},rootComponents:[()=>u(um,{threshold:300})]});function hm(e,t,i){var a,n,l;t===void 0&&(t=50),i===void 0&&(i={});var o=(a=i.isImmediate)!=null&&a,r=(n=i.callback)!=null&&n,c=i.maxWait,d=Date.now(),h=[];function p(){if(c!==void 0){var f=Date.now()-d;if(f+t>=c)return c-f}return t}var m=function(){var f=[].slice.call(arguments),y=this;return new Promise(function(_,x){var b=o&&l===void 0;if(l!==void 0&&clearTimeout(l),l=setTimeout(function(){if(l=void 0,d=Date.now(),!o){var E=e.apply(y,f);r&&r(E),h.forEach(function(C){return(0,C.resolve)(E)}),h=[]}},p()),b){var w=e.apply(y,f);return r&&r(w),_(w)}h.push({resolve:_,reject:x})})};return m.cancel=function(f){l!==void 0&&clearTimeout(l),h.forEach(function(y){return(0,y.reject)(f)}),h=[]},m}/*! + * vue-router v4.1.6 + * (c) 2022 Eduardo San Martin Morote + * @license MIT + */const li=typeof window<"u";function pm(e){return e.__esModule||e[Symbol.toStringTag]==="Module"}const ke=Object.assign;function dn(e,t){const i={};for(const a in t){const n=t[a];i[a]=pt(n)?n.map(e):e(n)}return i}const Fi=()=>{},pt=Array.isArray,mm=/\/$/,gm=e=>e.replace(mm,"");function hn(e,t,i="/"){let a,n={},l="",o="";const r=t.indexOf("#");let c=t.indexOf("?");return r=0&&(c=-1),c>-1&&(a=t.slice(0,c),l=t.slice(c+1,r>-1?r:t.length),n=e(l)),r>-1&&(a=a||t.slice(0,r),o=t.slice(r,t.length)),a=bm(a!=null?a:t,i),{fullPath:a+(l&&"?")+l+o,path:a,query:n,hash:o}}function vm(e,t){const i=t.query?e(t.query):"";return t.path+(i&&"?")+i+(t.hash||"")}function Fo(e,t){return!t||!e.toLowerCase().startsWith(t.toLowerCase())?e:e.slice(t.length)||"/"}function fm(e,t,i){const a=t.matched.length-1,n=i.matched.length-1;return a>-1&&a===n&&fi(t.matched[a],i.matched[n])&&Vs(t.params,i.params)&&e(t.query)===e(i.query)&&t.hash===i.hash}function fi(e,t){return(e.aliasOf||e)===(t.aliasOf||t)}function Vs(e,t){if(Object.keys(e).length!==Object.keys(t).length)return!1;for(const i in e)if(!_m(e[i],t[i]))return!1;return!0}function _m(e,t){return pt(e)?zo(e,t):pt(t)?zo(t,e):e===t}function zo(e,t){return pt(t)?e.length===t.length&&e.every((i,a)=>i===t[a]):e.length===1&&e[0]===t}function bm(e,t){if(e.startsWith("/"))return e;if(!e)return t;const i=t.split("/"),a=e.split("/");let n=i.length-1,l,o;for(l=0;l1&&n--;else break;return i.slice(0,n).join("/")+"/"+a.slice(l-(l===a.length?1:0)).join("/")}var Yi;(function(e){e.pop="pop",e.push="push"})(Yi||(Yi={}));var zi;(function(e){e.back="back",e.forward="forward",e.unknown=""})(zi||(zi={}));function km(e){if(!e)if(li){const t=document.querySelector("base");e=t&&t.getAttribute("href")||"/",e=e.replace(/^\w+:\/\/[^\/]+/,"")}else e="/";return e[0]!=="/"&&e[0]!=="#"&&(e="/"+e),gm(e)}const ym=/^[^#]+#/;function wm(e,t){return e.replace(ym,"#")+t}function xm(e,t){const i=document.documentElement.getBoundingClientRect(),a=e.getBoundingClientRect();return{behavior:t.behavior,left:a.left-i.left-(t.left||0),top:a.top-i.top-(t.top||0)}}const Ka=()=>({left:window.pageXOffset,top:window.pageYOffset});function Tm(e){let t;if("el"in e){const i=e.el,a=typeof i=="string"&&i.startsWith("#"),n=typeof i=="string"?a?document.getElementById(i.slice(1)):document.querySelector(i):i;if(!n)return;t=xm(n,e)}else t=e;"scrollBehavior"in document.documentElement.style?window.scrollTo(t):window.scrollTo(t.left!=null?t.left:window.pageXOffset,t.top!=null?t.top:window.pageYOffset)}function Ho(e,t){return(history.state?history.state.position-t:-1)+e}const Sn=new Map;function Em(e,t){Sn.set(e,t)}function Pm(e){const t=Sn.get(e);return Sn.delete(e),t}let Lm=()=>location.protocol+"//"+location.host;function Ms(e,t){const{pathname:i,search:a,hash:n}=t,l=e.indexOf("#");if(l>-1){let r=n.includes(e.slice(l))?e.slice(l).length:1,c=n.slice(r);return c[0]!=="/"&&(c="/"+c),Fo(c,"")}return Fo(i,e)+a+n}function Dm(e,t,i,a){let n=[],l=[],o=null;const r=({state:m})=>{const f=Ms(e,location),y=i.value,_=t.value;let x=0;if(m){if(i.value=f,t.value=m,o&&o===y){o=null;return}x=_?m.position-_.position:0}else a(f);n.forEach(b=>{b(i.value,y,{delta:x,type:Yi.pop,direction:x?x>0?zi.forward:zi.back:zi.unknown})})};function c(){o=i.value}function d(m){n.push(m);const f=()=>{const y=n.indexOf(m);y>-1&&n.splice(y,1)};return l.push(f),f}function h(){const{history:m}=window;!m.state||m.replaceState(ke({},m.state,{scroll:Ka()}),"")}function p(){for(const m of l)m();l=[],window.removeEventListener("popstate",r),window.removeEventListener("beforeunload",h)}return window.addEventListener("popstate",r),window.addEventListener("beforeunload",h),{pauseListeners:c,listen:d,destroy:p}}function $o(e,t,i,a=!1,n=!1){return{back:e,current:t,forward:i,replaced:a,position:window.history.length,scroll:n?Ka():null}}function Am(e){const{history:t,location:i}=window,a={value:Ms(e,i)},n={value:t.state};n.value||l(a.value,{back:null,current:a.value,forward:null,position:t.length-1,replaced:!0,scroll:null},!0);function l(c,d,h){const p=e.indexOf("#"),m=p>-1?(i.host&&document.querySelector("base")?e:e.slice(p))+c:Lm()+e+c;try{t[h?"replaceState":"pushState"](d,"",m),n.value=d}catch(f){console.error(f),i[h?"replace":"assign"](m)}}function o(c,d){const h=ke({},t.state,$o(n.value.back,c,n.value.forward,!0),d,{position:n.value.position});l(c,h,!0),a.value=c}function r(c,d){const h=ke({},n.value,t.state,{forward:c,scroll:Ka()});l(h.current,h,!0);const p=ke({},$o(a.value,c,null),{position:h.position+1},d);l(c,p,!1),a.value=c}return{location:a,state:n,push:r,replace:o}}function Im(e){e=km(e);const t=Am(e),i=Dm(e,t.state,t.location,t.replace);function a(l,o=!0){o||i.pauseListeners(),history.go(l)}const n=ke({location:"",base:e,go:a,createHref:wm.bind(null,e)},t,i);return Object.defineProperty(n,"location",{enumerable:!0,get:()=>t.location.value}),Object.defineProperty(n,"state",{enumerable:!0,get:()=>t.state.value}),n}function Om(e){return typeof e=="string"||e&&typeof e=="object"}function Fs(e){return typeof e=="string"||typeof e=="symbol"}const Tt={path:"/",name:void 0,params:{},query:{},hash:"",fullPath:"/",matched:[],meta:{},redirectedFrom:void 0},zs=Symbol("");var No;(function(e){e[e.aborted=4]="aborted",e[e.cancelled=8]="cancelled",e[e.duplicated=16]="duplicated"})(No||(No={}));function _i(e,t){return ke(new Error,{type:e,[zs]:!0},t)}function wt(e,t){return e instanceof Error&&zs in e&&(t==null||!!(e.type&t))}const qo="[^/]+?",Rm={sensitive:!1,strict:!1,start:!0,end:!0},Cm=/[.+*?^${}()[\]/\\]/g;function Sm(e,t){const i=ke({},Rm,t),a=[];let n=i.start?"^":"";const l=[];for(const d of e){const h=d.length?[]:[90];i.strict&&!d.length&&(n+="/");for(let p=0;pt.length?t.length===1&&t[0]===40+40?1:-1:0}function Mm(e,t){let i=0;const a=e.score,n=t.score;for(;i0&&t[t.length-1]<0}const Fm={type:0,value:""},zm=/[a-zA-Z0-9_]/;function Hm(e){if(!e)return[[]];if(e==="/")return[[Fm]];if(!e.startsWith("/"))throw new Error(`Invalid path "${e}"`);function t(f){throw new Error(`ERR (${i})/"${d}": ${f}`)}let i=0,a=i;const n=[];let l;function o(){l&&n.push(l),l=[]}let r=0,c,d="",h="";function p(){!d||(i===0?l.push({type:0,value:d}):i===1||i===2||i===3?(l.length>1&&(c==="*"||c==="+")&&t(`A repeatable param (${d}) must be alone in its segment. eg: '/:ids+.`),l.push({type:1,value:d,regexp:h,repeatable:c==="*"||c==="+",optional:c==="*"||c==="?"})):t("Invalid state to consume buffer"),d="")}function m(){d+=c}for(;r{o(w)}:Fi}function o(h){if(Fs(h)){const p=a.get(h);p&&(a.delete(h),i.splice(i.indexOf(p),1),p.children.forEach(o),p.alias.forEach(o))}else{const p=i.indexOf(h);p>-1&&(i.splice(p,1),h.record.name&&a.delete(h.record.name),h.children.forEach(o),h.alias.forEach(o))}}function r(){return i}function c(h){let p=0;for(;p=0&&(h.record.path!==i[p].record.path||!Hs(h,i[p]));)p++;i.splice(p,0,h),h.record.name&&!jo(h)&&a.set(h.record.name,h)}function d(h,p){let m,f={},y,_;if("name"in h&&h.name){if(m=a.get(h.name),!m)throw _i(1,{location:h});_=m.record.name,f=ke(Uo(p.params,m.keys.filter(w=>!w.optional).map(w=>w.name)),h.params&&Uo(h.params,m.keys.map(w=>w.name))),y=m.stringify(f)}else if("path"in h)y=h.path,m=i.find(w=>w.re.test(y)),m&&(f=m.parse(y),_=m.record.name);else{if(m=p.name?a.get(p.name):i.find(w=>w.re.test(p.path)),!m)throw _i(1,{location:h,currentLocation:p});_=m.record.name,f=ke({},p.params,h.params),y=m.stringify(f)}const x=[];let b=m;for(;b;)x.unshift(b.record),b=b.parent;return{name:_,path:y,params:f,matched:x,meta:Um(x)}}return e.forEach(h=>l(h)),{addRoute:l,resolve:d,removeRoute:o,getRoutes:r,getRecordMatcher:n}}function Uo(e,t){const i={};for(const a of t)a in e&&(i[a]=e[a]);return i}function qm(e){return{path:e.path,redirect:e.redirect,name:e.name,meta:e.meta||{},aliasOf:void 0,beforeEnter:e.beforeEnter,props:Bm(e),children:e.children||[],instances:{},leaveGuards:new Set,updateGuards:new Set,enterCallbacks:{},components:"components"in e?e.components||null:e.component&&{default:e.component}}}function Bm(e){const t={},i=e.props||!1;if("component"in e)t.default=i;else for(const a in e.components)t[a]=typeof i=="boolean"?i:i[a];return t}function jo(e){for(;e;){if(e.record.aliasOf)return!0;e=e.parent}return!1}function Um(e){return e.reduce((t,i)=>ke(t,i.meta),{})}function Wo(e,t){const i={};for(const a in e)i[a]=a in t?t[a]:e[a];return i}function Hs(e,t){return t.children.some(i=>i===e||Hs(e,i))}const $s=/#/g,jm=/&/g,Wm=/\//g,Gm=/=/g,Ym=/\?/g,Ns=/\+/g,Km=/%5B/g,Zm=/%5D/g,qs=/%5E/g,Jm=/%60/g,Bs=/%7B/g,Qm=/%7C/g,Us=/%7D/g,Xm=/%20/g;function wl(e){return encodeURI(""+e).replace(Qm,"|").replace(Km,"[").replace(Zm,"]")}function eg(e){return wl(e).replace(Bs,"{").replace(Us,"}").replace(qs,"^")}function Vn(e){return wl(e).replace(Ns,"%2B").replace(Xm,"+").replace($s,"%23").replace(jm,"%26").replace(Jm,"`").replace(Bs,"{").replace(Us,"}").replace(qs,"^")}function tg(e){return Vn(e).replace(Gm,"%3D")}function ig(e){return wl(e).replace($s,"%23").replace(Ym,"%3F")}function ag(e){return e==null?"":ig(e).replace(Wm,"%2F")}function Va(e){try{return decodeURIComponent(""+e)}catch{}return""+e}function ng(e){const t={};if(e===""||e==="?")return t;const a=(e[0]==="?"?e.slice(1):e).split("&");for(let n=0;nl&&Vn(l)):[a&&Vn(a)]).forEach(l=>{l!==void 0&&(t+=(t.length?"&":"")+i,l!=null&&(t+="="+l))})}return t}function lg(e){const t={};for(const i in e){const a=e[i];a!==void 0&&(t[i]=pt(a)?a.map(n=>n==null?null:""+n):a==null?a:""+a)}return t}const og=Symbol(""),Yo=Symbol(""),Za=Symbol(""),xl=Symbol(""),Mn=Symbol("");function Li(){let e=[];function t(a){return e.push(a),()=>{const n=e.indexOf(a);n>-1&&e.splice(n,1)}}function i(){e=[]}return{add:t,list:()=>e,reset:i}}function Ft(e,t,i,a,n){const l=a&&(a.enterCallbacks[n]=a.enterCallbacks[n]||[]);return()=>new Promise((o,r)=>{const c=p=>{p===!1?r(_i(4,{from:i,to:t})):p instanceof Error?r(p):Om(p)?r(_i(2,{from:t,to:p})):(l&&a.enterCallbacks[n]===l&&typeof p=="function"&&l.push(p),o())},d=e.call(a&&a.instances[n],t,i,c);let h=Promise.resolve(d);e.length<3&&(h=h.then(c)),h.catch(p=>r(p))})}function pn(e,t,i,a){const n=[];for(const l of e)for(const o in l.components){let r=l.components[o];if(!(t!=="beforeRouteEnter"&&!l.instances[o]))if(rg(r)){const d=(r.__vccOpts||r)[t];d&&n.push(Ft(d,i,a,l,o))}else{let c=r();n.push(()=>c.then(d=>{if(!d)return Promise.reject(new Error(`Couldn't resolve component "${o}" at "${l.path}"`));const h=pm(d)?d.default:d;l.components[o]=h;const m=(h.__vccOpts||h)[t];return m&&Ft(m,i,a,l,o)()}))}}return n}function rg(e){return typeof e=="object"||"displayName"in e||"props"in e||"__vccOpts"in e}function Fn(e){const t=we(Za),i=we(xl),a=P(()=>t.resolve(at(e.to))),n=P(()=>{const{matched:c}=a.value,{length:d}=c,h=c[d-1],p=i.matched;if(!h||!p.length)return-1;const m=p.findIndex(fi.bind(null,h));if(m>-1)return m;const f=Ko(c[d-2]);return d>1&&Ko(h)===f&&p[p.length-1].path!==f?p.findIndex(fi.bind(null,c[d-2])):m}),l=P(()=>n.value>-1&&ug(i.params,a.value.params)),o=P(()=>n.value>-1&&n.value===i.matched.length-1&&Vs(i.params,a.value.params));function r(c={}){return cg(c)?t[at(e.replace)?"replace":"push"](at(e.to)).catch(Fi):Promise.resolve()}return{route:a,href:P(()=>a.value.href),isActive:l,isExactActive:o,navigate:r}}const sg=K({name:"RouterLink",compatConfig:{MODE:3},props:{to:{type:[String,Object],required:!0},replace:Boolean,activeClass:String,exactActiveClass:String,custom:Boolean,ariaCurrentValue:{type:String,default:"page"}},useLink:Fn,setup(e,{slots:t}){const i=Ut(Fn(e)),{options:a}=we(Za),n=P(()=>({[Zo(e.activeClass,a.linkActiveClass,"router-link-active")]:i.isActive,[Zo(e.exactActiveClass,a.linkExactActiveClass,"router-link-exact-active")]:i.isExactActive}));return()=>{const l=t.default&&t.default(i);return e.custom?l:u("a",{"aria-current":i.isExactActive?e.ariaCurrentValue:null,href:i.href,onClick:i.navigate,class:n.value},l)}}}),Qe=sg;function cg(e){if(!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)&&!e.defaultPrevented&&!(e.button!==void 0&&e.button!==0)){if(e.currentTarget&&e.currentTarget.getAttribute){const t=e.currentTarget.getAttribute("target");if(/\b_blank\b/i.test(t))return}return e.preventDefault&&e.preventDefault(),!0}}function ug(e,t){for(const i in t){const a=t[i],n=e[i];if(typeof a=="string"){if(a!==n)return!1}else if(!pt(n)||n.length!==a.length||a.some((l,o)=>l!==n[o]))return!1}return!0}function Ko(e){return e?e.aliasOf?e.aliasOf.path:e.path:""}const Zo=(e,t,i)=>e!=null?e:t!=null?t:i,dg=K({name:"RouterView",inheritAttrs:!1,props:{name:{type:String,default:"default"},route:Object},compatConfig:{MODE:3},setup(e,{attrs:t,slots:i}){const a=we(Mn),n=P(()=>e.route||a.value),l=we(Yo,0),o=P(()=>{let d=at(l);const{matched:h}=n.value;let p;for(;(p=h[d])&&!p.components;)d++;return d}),r=P(()=>n.value.matched[o.value]);Ke(Yo,P(()=>o.value+1)),Ke(og,r),Ke(Mn,n);const c=ne();return De(()=>[c.value,r.value,e.name],([d,h,p],[m,f,y])=>{h&&(h.instances[p]=d,f&&f!==h&&d&&d===m&&(h.leaveGuards.size||(h.leaveGuards=f.leaveGuards),h.updateGuards.size||(h.updateGuards=f.updateGuards))),d&&h&&(!f||!fi(h,f)||!m)&&(h.enterCallbacks[p]||[]).forEach(_=>_(d))},{flush:"post"}),()=>{const d=n.value,h=e.name,p=r.value,m=p&&p.components[h];if(!m)return Jo(i.default,{Component:m,route:d});const f=p.props[h],y=f?f===!0?d.params:typeof f=="function"?f(d):f:null,x=u(m,ke({},y,t,{onVnodeUnmounted:b=>{b.component.isUnmounted&&(p.instances[h]=null)},ref:c}));return Jo(i.default,{Component:x,route:d})||x}}});function Jo(e,t){if(!e)return null;const i=e(t);return i.length===1?i[0]:i}const js=dg;function hg(e){const t=Nm(e.routes,e),i=e.parseQuery||ng,a=e.stringifyQuery||Go,n=e.history,l=Li(),o=Li(),r=Li(),c=Dr(Tt);let d=Tt;li&&e.scrollBehavior&&"scrollRestoration"in history&&(history.scrollRestoration="manual");const h=dn.bind(null,I=>""+I),p=dn.bind(null,ag),m=dn.bind(null,Va);function f(I,G){let U,Q;return Fs(I)?(U=t.getRecordMatcher(I),Q=G):Q=I,t.addRoute(Q,U)}function y(I){const G=t.getRecordMatcher(I);G&&t.removeRoute(G)}function _(){return t.getRoutes().map(I=>I.record)}function x(I){return!!t.getRecordMatcher(I)}function b(I,G){if(G=ke({},G||c.value),typeof I=="string"){const v=hn(i,I,G.path),k=t.resolve({path:v.path},G),L=n.createHref(v.fullPath);return ke(v,k,{params:m(k.params),hash:Va(v.hash),redirectedFrom:void 0,href:L})}let U;if("path"in I)U=ke({},I,{path:hn(i,I.path,G.path).path});else{const v=ke({},I.params);for(const k in v)v[k]==null&&delete v[k];U=ke({},I,{params:p(I.params)}),G.params=p(G.params)}const Q=t.resolve(U,G),oe=I.hash||"";Q.params=h(m(Q.params));const _e=vm(a,ke({},I,{hash:eg(oe),path:Q.path})),se=n.createHref(_e);return ke({fullPath:_e,hash:oe,query:a===Go?lg(I.query):I.query||{}},Q,{redirectedFrom:void 0,href:se})}function w(I){return typeof I=="string"?hn(i,I,c.value.path):ke({},I)}function E(I,G){if(d!==I)return _i(8,{from:G,to:I})}function C(I){return T(I)}function F(I){return C(ke(w(I),{replace:!0}))}function S(I){const G=I.matched[I.matched.length-1];if(G&&G.redirect){const{redirect:U}=G;let Q=typeof U=="function"?U(I):U;return typeof Q=="string"&&(Q=Q.includes("?")||Q.includes("#")?Q=w(Q):{path:Q},Q.params={}),ke({query:I.query,hash:I.hash,params:"path"in Q?{}:I.params},Q)}}function T(I,G){const U=d=b(I),Q=c.value,oe=I.state,_e=I.force,se=I.replace===!0,v=S(U);if(v)return T(ke(w(v),{state:typeof v=="object"?ke({},oe,v.state):oe,force:_e,replace:se}),G||U);const k=U;k.redirectedFrom=G;let L;return!_e&&fm(a,Q,U)&&(L=_i(16,{to:k,from:Q}),de(Q,Q,!0,!1)),(L?Promise.resolve(L):N(k,Q)).catch(O=>wt(O)?wt(O,2)?O:ee(O):M(O,k,Q)).then(O=>{if(O){if(wt(O,2))return T(ke({replace:se},w(O.to),{state:typeof O.to=="object"?ke({},oe,O.to.state):oe,force:_e}),G||k)}else O=le(k,Q,!0,se,oe);return z(k,Q,O),O})}function A(I,G){const U=E(I,G);return U?Promise.reject(U):Promise.resolve()}function N(I,G){let U;const[Q,oe,_e]=pg(I,G);U=pn(Q.reverse(),"beforeRouteLeave",I,G);for(const v of Q)v.leaveGuards.forEach(k=>{U.push(Ft(k,I,G))});const se=A.bind(null,I,G);return U.push(se),ni(U).then(()=>{U=[];for(const v of l.list())U.push(Ft(v,I,G));return U.push(se),ni(U)}).then(()=>{U=pn(oe,"beforeRouteUpdate",I,G);for(const v of oe)v.updateGuards.forEach(k=>{U.push(Ft(k,I,G))});return U.push(se),ni(U)}).then(()=>{U=[];for(const v of I.matched)if(v.beforeEnter&&!G.matched.includes(v))if(pt(v.beforeEnter))for(const k of v.beforeEnter)U.push(Ft(k,I,G));else U.push(Ft(v.beforeEnter,I,G));return U.push(se),ni(U)}).then(()=>(I.matched.forEach(v=>v.enterCallbacks={}),U=pn(_e,"beforeRouteEnter",I,G),U.push(se),ni(U))).then(()=>{U=[];for(const v of o.list())U.push(Ft(v,I,G));return U.push(se),ni(U)}).catch(v=>wt(v,8)?v:Promise.reject(v))}function z(I,G,U){for(const Q of r.list())Q(I,G,U)}function le(I,G,U,Q,oe){const _e=E(I,G);if(_e)return _e;const se=G===Tt,v=li?history.state:{};U&&(Q||se?n.replace(I.fullPath,ke({scroll:se&&v&&v.scroll},oe)):n.push(I.fullPath,oe)),c.value=I,de(I,G,U,se),ee()}let Z;function B(){Z||(Z=n.listen((I,G,U)=>{if(!We.listening)return;const Q=b(I),oe=S(Q);if(oe){T(ke(oe,{replace:!0}),Q).catch(Fi);return}d=Q;const _e=c.value;li&&Em(Ho(_e.fullPath,U.delta),Ka()),N(Q,_e).catch(se=>wt(se,12)?se:wt(se,2)?(T(se.to,Q).then(v=>{wt(v,20)&&!U.delta&&U.type===Yi.pop&&n.go(-1,!1)}).catch(Fi),Promise.reject()):(U.delta&&n.go(-U.delta,!1),M(se,Q,_e))).then(se=>{se=se||le(Q,_e,!1),se&&(U.delta&&!wt(se,8)?n.go(-U.delta,!1):U.type===Yi.pop&&wt(se,20)&&n.go(-1,!1)),z(Q,_e,se)}).catch(Fi)}))}let D=Li(),H=Li(),W;function M(I,G,U){ee(I);const Q=H.list();return Q.length?Q.forEach(oe=>oe(I,G,U)):console.error(I),Promise.reject(I)}function J(){return W&&c.value!==Tt?Promise.resolve():new Promise((I,G)=>{D.add([I,G])})}function ee(I){return W||(W=!I,B(),D.list().forEach(([G,U])=>I?U(I):G()),D.reset()),I}function de(I,G,U,Q){const{scrollBehavior:oe}=e;if(!li||!oe)return Promise.resolve();const _e=!U&&Pm(Ho(I.fullPath,0))||(Q||!U)&&history.state&&history.state.scroll||null;return sl().then(()=>oe(I,G,_e)).then(se=>se&&Tm(se)).catch(se=>M(se,I,G))}const fe=I=>n.go(I);let ue;const Re=new Set,We={currentRoute:c,listening:!0,addRoute:f,removeRoute:y,hasRoute:x,getRoutes:_,resolve:b,options:e,push:C,replace:F,go:fe,back:()=>fe(-1),forward:()=>fe(1),beforeEach:l.add,beforeResolve:o.add,afterEach:r.add,onError:H.add,isReady:J,install(I){const G=this;I.component("RouterLink",Qe),I.component("RouterView",js),I.config.globalProperties.$router=G,Object.defineProperty(I.config.globalProperties,"$route",{enumerable:!0,get:()=>at(c)}),li&&!ue&&c.value===Tt&&(ue=!0,C(n.location).catch(oe=>{}));const U={};for(const oe in Tt)U[oe]=P(()=>c.value[oe]);I.provide(Za,G),I.provide(xl,Ut(U)),I.provide(Mn,c);const Q=I.unmount;Re.add(I),I.unmount=function(){Re.delete(I),Re.size<1&&(d=Tt,Z&&Z(),Z=null,c.value=Tt,ue=!1,W=!1),Q()}}};return We}function ni(e){return e.reduce((t,i)=>t.then(()=>i()),Promise.resolve())}function pg(e,t){const i=[],a=[],n=[],l=Math.max(t.matched.length,e.matched.length);for(let o=0;ofi(d,r))?a.push(r):i.push(r));const c=e.matched[o];c&&(t.matched.find(d=>fi(d,c))||n.push(c))}return[i,a,n]}function Xe(){return we(Za)}function xe(){return we(xl)}const mg=({headerLinkSelector:e,headerAnchorSelector:t,delay:i,offset:a=5})=>{const n=Xe(),o=hm(()=>{var _,x,b,w;const r=Math.max(window.scrollY,document.documentElement.scrollTop,document.body.scrollTop);if(Math.abs(r-0)m.some(C=>C.hash===E.hash));for(let E=0;E=((x=(_=C.parentElement)==null?void 0:_.offsetTop)!=null?x:0)-a,T=!F||r<((w=(b=F.parentElement)==null?void 0:b.offsetTop)!=null?w:0)-a;if(!(S&&T))continue;const N=decodeURIComponent(n.currentRoute.value.hash),z=decodeURIComponent(C.hash);if(N===z)return;if(p){for(let le=E+1;le{window.addEventListener("scroll",o)}),xi(()=>{window.removeEventListener("scroll",o)})},Qo=async(e,t)=>{const{scrollBehavior:i}=e.options;e.options.scrollBehavior=void 0,await e.replace({query:e.currentRoute.value.query,hash:t,force:!0}).finally(()=>e.options.scrollBehavior=i)},gg=".sidebar-link, .toc-link",vg=".header-anchor",fg=200,_g=5,bg=kt({setup(){mg({headerLinkSelector:gg,headerAnchorSelector:vg,delay:fg,offset:_g})}});const kg=u("svg",{class:"external-link-icon",xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",focusable:"false",x:"0px",y:"0px",viewBox:"0 0 100 100",width:"15",height:"15"},[u("path",{fill:"currentColor",d:"M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"}),u("polygon",{fill:"currentColor",points:"45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"})]),Ws=K({name:"ExternalLinkIcon",props:{locales:{type:Object,required:!1,default:()=>({})}},setup(e){const t=yt(),i=P(()=>{var a;return(a=e.locales[t.value])!=null?a:{openInNewWindow:"open in new window"}});return()=>u("span",[kg,u("span",{class:"external-link-icon-sr-only"},i.value.openInNewWindow)])}}),yg={},wg=kt({enhance({app:e}){e.component("ExternalLinkIcon",u(Ws,{locales:yg}))}});/** + * NProgress, (c) 2013, 2014 Rico Sta. Cruz - http://ricostacruz.com/nprogress + * @license MIT + */const me={settings:{minimum:.08,easing:"ease",speed:200,trickle:!0,trickleRate:.02,trickleSpeed:800,barSelector:'[role="bar"]',parent:"body",template:'
    '},status:null,set:e=>{const t=me.isStarted();e=mn(e,me.settings.minimum,1),me.status=e===1?null:e;const i=me.render(!t),a=i.querySelector(me.settings.barSelector),n=me.settings.speed,l=me.settings.easing;return i.offsetWidth,xg(o=>{ya(a,{transform:"translate3d("+Xo(e)+"%,0,0)",transition:"all "+n+"ms "+l}),e===1?(ya(i,{transition:"none",opacity:"1"}),i.offsetWidth,setTimeout(function(){ya(i,{transition:"all "+n+"ms linear",opacity:"0"}),setTimeout(function(){me.remove(),o()},n)},n)):setTimeout(()=>o(),n)}),me},isStarted:()=>typeof me.status=="number",start:()=>{me.status||me.set(0);const e=()=>{setTimeout(()=>{!me.status||(me.trickle(),e())},me.settings.trickleSpeed)};return me.settings.trickle&&e(),me},done:e=>!e&&!me.status?me:me.inc(.3+.5*Math.random()).set(1),inc:e=>{let t=me.status;return t?(typeof e!="number"&&(e=(1-t)*mn(Math.random()*t,.1,.95)),t=mn(t+e,0,.994),me.set(t)):me.start()},trickle:()=>me.inc(Math.random()*me.settings.trickleRate),render:e=>{if(me.isRendered())return document.getElementById("nprogress");er(document.documentElement,"nprogress-busy");const t=document.createElement("div");t.id="nprogress",t.innerHTML=me.settings.template;const i=t.querySelector(me.settings.barSelector),a=e?"-100":Xo(me.status||0),n=document.querySelector(me.settings.parent);return ya(i,{transition:"all 0 linear",transform:"translate3d("+a+"%,0,0)"}),n!==document.body&&er(n,"nprogress-custom-parent"),n==null||n.appendChild(t),t},remove:()=>{tr(document.documentElement,"nprogress-busy"),tr(document.querySelector(me.settings.parent),"nprogress-custom-parent");const e=document.getElementById("nprogress");e&&Tg(e)},isRendered:()=>!!document.getElementById("nprogress")},mn=(e,t,i)=>ei?i:e,Xo=e=>(-1+e)*100,xg=function(){const e=[];function t(){const i=e.shift();i&&i(t)}return function(i){e.push(i),e.length===1&&t()}}(),ya=function(){const e=["Webkit","O","Moz","ms"],t={};function i(o){return o.replace(/^-ms-/,"ms-").replace(/-([\da-z])/gi,function(r,c){return c.toUpperCase()})}function a(o){const r=document.body.style;if(o in r)return o;let c=e.length;const d=o.charAt(0).toUpperCase()+o.slice(1);let h;for(;c--;)if(h=e[c]+d,h in r)return h;return o}function n(o){return o=i(o),t[o]||(t[o]=a(o))}function l(o,r,c){r=n(r),o.style[r]=c}return function(o,r){for(const c in r){const d=r[c];d!==void 0&&Object.prototype.hasOwnProperty.call(r,c)&&l(o,c,d)}}}(),Gs=(e,t)=>(typeof e=="string"?e:Tl(e)).indexOf(" "+t+" ")>=0,er=(e,t)=>{const i=Tl(e),a=i+t;Gs(i,t)||(e.className=a.substring(1))},tr=(e,t)=>{const i=Tl(e);if(!Gs(e,t))return;const a=i.replace(" "+t+" "," ");e.className=a.substring(1,a.length-1)},Tl=e=>(" "+(e.className||"")+" ").replace(/\s+/gi," "),Tg=e=>{e&&e.parentNode&&e.parentNode.removeChild(e)};const Eg=()=>{Se(()=>{const e=Xe(),t=new Set;t.add(e.currentRoute.value.path),e.beforeEach(i=>{t.has(i.path)||me.start()}),e.afterEach(i=>{t.add(i.path),me.done()})})},Pg=kt({setup(){Eg()}}),Lg=JSON.parse(`{"blog":{},"encrypt":{},"pure":false,"darkmode":"switch","themeColor":false,"fullscreen":false,"locales":{"/":{"blog":{},"repoDisplay":true,"navbarIcon":true,"navbarAutoHide":"mobile","hideSiteNameonMobile":true,"sidebar":{"/":[],"/docs/":[{"text":"Launch Manual","icon":"solid fa-rocket","link":"launch-manual/","prefix":"launch-manual/sections/","collapsable":true,"children":[{"text":"Getting Started","icon":"solid fa-play","link":"getting-started/"},{"text":"Using Pulsar","icon":"solid fa-circle-info","link":"using-pulsar/"},{"text":"Hacking the Core","icon":"solid fa-screwdriver-wrench","link":"core-hacking/"},{"text":"Behind Pulsar","icon":"solid fa-microscope","link":"behind-pulsar"},{"text":"FAQ","icon":"solid fa-circle-question","link":"faq/"}]},{"text":"Packages","title":"packages","icon":"solid fa-box-open","link":"packages/"},{"text":"Resources","title":"resources","icon":"solid fa-wrench","link":"resources/","prefix":"resources/","collapsable":true,"children":[{"text":"Glossary","icon":"solid fa-book-bookmark","link":"glossary/"},{"text":"Pulsar API","icon":"solid fa-gear","link":"pulsar-api/"},{"text":"Tooling","icon":"solid fa-toolbox","link":"tooling/"},{"text":"Website","icon":"solid fa-table-columns","link":"website/"},{"text":"Privacy Policy","icon":"solid fa-file-shield","link":"privacy/"},{"text":"Code of Conduct","icon":"solid fa-gavel","link":"conduct/"}]},{"text":"Atom Archive","title":"atom archive","icon":"solid fa-box-archive","link":"atom-archive/","prefix":"atom-archive/","collapsable":true,"children":["getting-started/","using-atom/","hacking-atom/","behind-atom/","api/","resources/","faq/","shadow-dom/","upgrading-to-1-0-apis/","atom-server-side-apis/"]}],"/about/":[]},"sidebarIcon":true,"headerDepth":2,"lang":"en-US","navbarLocales":{"langName":"English","selectLangAriaLabel":"Select language"},"metaLocales":{"author":"Author","date":"Writing Date","origin":"Original","views":"Page views","category":"Category","tag":"Tag","readingTime":"Reading Time","words":"Words","toc":"On This Page","prev":"Prev","next":"Next","lastUpdated":"Last update","contributors":"Contributors","editLink":"Edit this page"},"blogLocales":{"article":"Articles","articleList":"Article List","category":"Category","tag":"Tag","timeline":"Timeline","timelineTitle":"Yesterday Once More!","all":"All","intro":"Personal Intro","star":"Star","slides":"Slides","encrypt":"Encrypted"},"paginationLocales":{"prev":"Prev","next":"Next","navigate":"Jump to","action":"Go","errorText":"Please enter a number between 1 and $page !"},"outlookLocales":{"themeColor":"Theme Color","darkmode":"Theme Mode","fullscreen":"Full Screen"},"encryptLocales":{"iconLabel":"Page Encrypted","placeholder":"Enter password","remember":"Remember password","errorHint":"Please enter the correct password!"},"routeLocales":{"notFoundMsg":["There\u2019s nothing here.","How did we get here?","That\u2019s a Four-Oh-Four.","Looks like we've got some broken links."],"back":"Go back","home":"Take me home","openInNewWindow":"Open in new window"},"logo":"/logo-name-navbar-light.svg","logoDark":"/logo-name-navbar-dark.svg","displayAllHeaders":true,"editLinks":true,"repo":"pulsar-edit","repoLabel":"GitHub","displayFooter":true,"footer":"\\n\\n \\n\\n
    \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n
    \\n\\n \\n\\n
    \\n\\n
    \\n
    \\n

    Pulsar

    \\n
    \\n \\n
    \\n
    \\n\\n
    \\n

    Team

    \\n
    \\n \\n
    \\n
    \\n\\n
    \\n

    Resources

    \\n
    \\n \\n
    \\n
    \\n\\n
    \\n\\n\\n\\n
    \\n\\n
    \\n Pulsar-Edit \xA9 2022-2023 \\n
    ","copyright":false,"docsRepo":"https://github.com/pulsar-edit/pulsar-edit.github.io","docsDir":"/docs","navbar":[{"text":"Download","icon":"solid fa-download","link":"/download/"},{"text":"Docs","icon":"solid fa-file-lines","link":"/docs/"},{"text":"Repositories","icon":"brands fa-github","link":"/repos/"},{"text":"About Us","icon":"solid fa-sun","link":"/about/"},{"text":"Blog","icon":"solid fa-blog","link":"/blog/"},{"text":"Community","icon":"solid fa-user-group","children":[{"text":"Community areas","icon":"solid fa-house","link":"/community/"},{"text":"Discussions","icon":"solid fa-comments","link":"https://github.com/orgs/pulsar-edit/discussions"},{"text":"Discord","icon":"brands fa-discord","link":"https://discord.gg/7aEbB9dGRT"},{"text":"Reddit","icon":"brands fa-reddit","link":"https://www.reddit.com/r/pulsaredit/"},{"text":"Mastodon","icon":"brands fa-mastodon","link":"https://fosstodon.org/@pulsaredit"},{"text":"Lemmy","icon":"solid fa-users","link":"https://lemmy.ml/c/pulsaredit"}]},{"text":"Donate","icon":"solid fa-hand-holding-dollar","link":"/donate/"},{"text":"Packages","icon":"solid fa-box-open","link":"https://web.pulsar-edit.dev/"}],"sidebarDepth":3,"selectLanguageName":"English"}}}`),Ys=ne(Lg),Ks=()=>Ys;import.meta.webpackHot&&(__VUE_HMR_RUNTIME__.updateThemeData=e=>{Ys.value=e});const Zs=Symbol(""),Dg=()=>{const e=we(Zs);if(!e)throw new Error("useThemeLocaleData() is called without provider.");return e},Ag=(e,t)=>{var i;return{...e,...(i=e.locales)==null?void 0:i[t]}},Ig=kt({enhance({app:e}){const t=Ks(),i=e._context.provides[Ml],a=P(()=>Ag(t.value,i.value));e.provide(Zs,a),Object.defineProperties(e.config.globalProperties,{$theme:{get(){return t.value}},$themeLocale:{get(){return a.value}}})}});const Og=async e=>{try{return navigator.clipboard.writeText(e)}catch{const t=document.createElement("textarea"),i=document.activeElement;t.value=e,t.setAttribute("readonly",""),t.style.contain="strict",t.style.position="absolute",t.style.left="-9999px",t.style.fontSize="12pt";const a=document.getSelection(),n=a?a.rangeCount>0&&a.getRangeAt(0):null;document.body.appendChild(t),t.select(),t.selectionStart=0,t.selectionEnd=e.length,document.execCommand("copy"),document.body.removeChild(t),n&&(a.removeAllRanges(),a.addRange(n)),i&&i.focus()}},Rg='',Di={selector:'.theme-hope-content div[class*="language-"] pre',pure:!1},ir=()=>navigator?/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/iu.test(navigator.userAgent):!1,gn=new Map,Cg=()=>{const e=xe(),t=ia({"/":{copy:"Copy code",copied:"Copied",hint:"Copied successfully"}});let i;const a=o=>{if(!o.hasAttribute("copy-code-registered")){const r=document.createElement("button");r.classList.add("copy"),r.innerHTML='
    ',r.setAttribute("aria-label",t.value.copy),r.setAttribute("data-copied",t.value.copied),r.setAttribute("data-balloon-pos","left"),o.parentElement&&o.parentElement.insertBefore(r,o),o.setAttribute("copy-code-registered","")}},n=()=>{const o=Di.selector;setTimeout(()=>{document.querySelectorAll(o).forEach(a)},Di.delay||500)},l=(o,r,c)=>{let{innerText:d=""}=r;/language-(shellscript|shell|bash|sh|zsh)/.test(o.classList.toString())&&(d=d.replace(/^ *(\$|>) /gm,"")),Og(d).then(()=>{c.classList.add("copied"),clearTimeout(gn.get(c));const h=setTimeout(()=>{c.classList.remove("copied"),c.blur(),gn.delete(c)},2e3);gn.set(c,h),i.pop(`${Rg}${t.value.hint} \u{1F389}`,Di.duration)})};Se(()=>{i=new Ts,(!ir()||Di.showInMobile)&&n(),window.addEventListener("click",o=>{const r=o.target;if(r.matches('div[class*="language-"] > button.copy')){const c=r.parentElement,d=r.nextElementSibling;d&&l(c,d,r)}else if(r.matches('div[class*="language-"] div.copy-icon')){const c=r.parentElement,d=c.parentElement,h=c.nextElementSibling;h&&l(d,h,c)}})}),De(()=>e.path,()=>{(!ir()||Di.showInMobile)&&n()})};var Sg=kt({setup:()=>{Cg()}});const vn=ys("VUEPRESS_TAB_STORE",{});var Vg=K({name:"Tabs",props:{active:{type:Number,default:0},data:{type:Array,required:!0},id:{type:String,required:!0},tabId:{type:String,default:""}},setup(e,{slots:t}){const i=ne((()=>{if(e.tabId){const c=e.data.findIndex(({title:d,value:h=d})=>vn.value[e.tabId]===h);if(c!==-1)return c}return e.active})()),a=ne([]),n=()=>{if(e.tabId){const{title:c,value:d=c}=e.data[i.value];vn.value[e.tabId]=d}},l=(c=i.value)=>{i.value=c{i.value=c>0?c-1:a.value.length-1,a.value[i.value].focus()},r=(c,d)=>{c.key===" "||c.key==="Enter"?(c.preventDefault(),i.value=d):c.key==="ArrowRight"?(c.preventDefault(),l()):c.key==="ArrowLeft"&&(c.preventDefault(),o()),n()};return De(()=>vn.value[e.tabId],(c,d)=>{if(e.tabId&&c!==d){const h=e.data.findIndex(({title:p,value:m=p})=>m===c);h!==-1&&(i.value=h)}}),()=>u(Xa,()=>u("div",{class:"tab-list"},[u("div",{class:"tab-list-nav",role:"tablist"},e.data.map(({title:c},d)=>{const h=d===i.value;return u("button",{ref:p=>{p&&(a.value[d]=p)},class:["tab-list-nav-item",{active:h}],role:"tab","aria-controls":`tab-${e.id}-${d}`,"aria-selected":h,onClick:()=>{i.value=d,n()},onKeydown:p=>r(p,d)},c)})),e.data.map(({title:c,value:d=c},h)=>{var p;const m=h===i.value;return u("div",{class:["tab-item",{active:m}],id:`tab-${e.id}-${h}`,role:"tabpanel","aria-expanded":m},(p=t[`tab${h}`])==null?void 0:p.call(t,{title:c,value:d,isActive:m}))})]))}});const Mg=kt({enhance:({app:e})=>{e.component("Tabs",Vg)}});const Fg=".theme-hope-content :not(a) > img",zg={"/":{closeTitle:"Close",downloadTitle:"Download Image",fullscreenTitle:"Switch to full screen",zoomTitle:"Zoom in/out",arrowPrevTitle:"Prev (Arrow Left)",arrowNextTitle:"Next (Arrow Right)"}},Hg=500,$g={},ar=e=>({src:e.src,width:e.naturalWidth,height:e.naturalHeight,alt:e.alt}),Ng=e=>{const t=Array.from(document.querySelectorAll(e));return Promise.all(t.map(i=>new Promise((a,n)=>{i.complete?a(ar(i)):(i.onload=()=>a(ar(i)),i.onerror=l=>n(l))}))).then(i=>({elements:t,infos:i}))},qg=()=>{const{isSupported:e,toggle:t}=bl(),i=ia(zg),a=xe(),n=()=>{Promise.all([s(()=>import("./photoswipe.esm.09e03fed.js"),[]),new Promise(l=>setTimeout(l,Hg)).then(()=>Ng(Fg))]).then(([l,o])=>{o.elements.forEach((r,c)=>{r.addEventListener("click",()=>{const d=new l.default({dataSource:o.infos,...i.value,...$g,index:c});d.on("uiRegister",()=>{e&&d.ui.registerElement({name:"fullscreen",order:7,isButton:!0,html:'',onClick:()=>{t()}}),d.ui.registerElement({name:"download",order:8,isButton:!0,tagName:"a",html:{isCustomSVG:!0,inner:'',outlineID:"pswp__icn-download"},onInit:(h,p)=>{h.setAttribute("download",""),h.setAttribute("target","_blank"),h.setAttribute("rel","noopener"),p.on("change",()=>{h.href=p.currSlide.data.src})}})}),d.init()})})})};De(()=>a.path,()=>n()),Se(()=>n())};var Bg=kt({setup:()=>{qg()}});const gt=()=>Ks(),ve=()=>Dg(),la=()=>P(()=>Boolean(gt().value.pure)),oa=()=>{const e=gt(),t=ne(!1),i=()=>{t.value=window.innerWidth<(e.value.mobileBreakPoint||719)};return Se(()=>{i(),Bt("resize",i,!1),Bt("orientationchange",i,!1)}),t},ra=()=>{const e=Xe(),t=xe();return i=>{if(i)if(i.startsWith("/"))t.path!==i&&e.push(i);else if(i.startsWith("http://")||i.startsWith("https://")||i.startsWith("mailto:"))window&&window.open(i);else{const a=t.path.slice(0,t.path.lastIndexOf("/"));e.push(`${a}/${encodeURI(i)}`)}}},hi=(e,t=!1)=>{const i=Xe(),{fullPath:a,meta:n,name:l}=na(i,encodeURI(e));return{text:!t&&n.shortTitle?n.shortTitle:n.title||e,link:l==="404"?e:a,...n.icon?{icon:n.icon}:{}}},Js=()=>{const e=ve(),t=Pe();return P(()=>{const{author:i}=t.value;return i?Gi(i):i===!1?[]:Gi(e.value.author,!1)})},Ug=()=>{const e=Pe();return P(()=>Rs(e.value.category).map(t=>{var i,a;return{name:t,path:((a=(i=we(Symbol.for("categoryMap")))==null?void 0:i.value.map[t])==null?void 0:a.path)||""}}))},jg=()=>{const e=Pe();return P(()=>Cs(e.value.tag).map(t=>{var i,a;return{name:t,path:((a=(i=we(Symbol.for("tagMap")))==null?void 0:i.value.map[t])==null?void 0:a.path)||""}}))},Wg=()=>{const e=Pe(),t=je();return P(()=>{const{date:i}=e.value;if(i)return Sa(i);const{createdTime:a}=t.value.git||{};return a?Sa(new Date(a)):null})},Gg=()=>{const e=ve(),t=je(),i=Pe(),a=Js(),n=Ug(),l=jg(),o=Wg(),r=Ut({author:a.value,category:n.value,date:o.value,localizedDate:t.value.localizedDate,tag:l.value,isOriginal:i.value.isOriginal||!1,readingTime:t.value.readingTime,pageview:!1}),c=P(()=>"pageInfo"in i.value?i.value.pageInfo:"pageInfo"in e.value?e.value.pageInfo:null);return{config:r,items:c}};let fn=null,Ai=null;const Yg={wait:()=>fn,pending:()=>{fn=new Promise(e=>Ai=e)},resolve:()=>{Ai==null||Ai(),fn=null,Ai=null}},Qs=()=>Yg;const Kg=K({name:"PageFooter",setup(){const e=Pe(),t=ve(),i=Js(),a=P(()=>{const{copyright:o,footer:r}=e.value;return r!==!1&&Boolean(o||r||t.value.displayFooter)}),n=P(()=>{const{footer:o}=e.value;return o===!1?!1:typeof o=="string"?o:t.value.footer||""}),l=P(()=>"copyright"in e.value?e.value.copyright:"copyright"in t.value?t.value.copyright:i.value.length?`Copyright \xA9 ${new Date().getFullYear()} ${i.value[0].name}`:!1);return()=>a.value?u("footer",{class:"footer-wrapper"},[u("div",{class:"footer",innerHTML:n.value}),l.value?u("div",{class:"copyright",innerHTML:l.value}):null]):null}}),Je=e=>{const{icon:t=""}=e;return ti(t)?u("img",{class:"icon",src:t}):t.startsWith("/")?u("img",{class:"icon",src:mt(t)}):u(Me("FontIcon"),e)};Je.displayName="Icon";const ot=K({name:"AutoLink",inheritAttrs:!1,props:{config:{type:Object,required:!0},exact:Boolean,externalLinkIcon:{type:Boolean,default:!0}},emits:["focusout"],setup(e,{attrs:t,emit:i,slots:a}){const n=xe(),l=Fl(),o=Ji(e,"config"),r=P(()=>ti(o.value.link)),c=P(()=>cp(o.value.link)||up(o.value.link)),d=P(()=>c.value?void 0:o.value.target||(r.value?"_blank":void 0)),h=P(()=>d.value==="_blank"),p=P(()=>!r.value&&!c.value&&!h.value),m=P(()=>c.value?void 0:o.value.rel||(h.value?"noopener noreferrer":void 0)),f=P(()=>o.value.ariaLabel||o.value.text),y=P(()=>{if(e.exact)return!1;const x=Object.keys(l.value.locales);return x.length?x.every(b=>b!==o.value.link):o.value.link!=="/"}),_=P(()=>p.value?o.value.activeMatch?new RegExp(o.value.activeMatch).test(n.path):y.value?n.path.startsWith(o.value.link):n.path===o.value.link:!1);return()=>{var E,C,F;const{text:x,icon:b,link:w}=o.value;return p.value?u(Qe,{to:w,"aria-label":f.value,...t,class:["nav-link",{active:_.value},t.class],onFocusout:()=>i("focusout")},()=>{var S,T,A;return((S=a.default)==null?void 0:S.call(a))||[((T=a.before)==null?void 0:T.call(a))||u(Je,{icon:b}),x,(A=a.after)==null?void 0:A.call(a)]}):u("a",{href:w,rel:m.value,target:d.value,"aria-label":f.value,...t,class:["nav-link",t.class],onFocusout:()=>i("focusout")},((E=a.default)==null?void 0:E.call(a))||[((C=a.before)==null?void 0:C.call(a))||u(Je,{icon:b}),x,e.externalLinkIcon?u(Ws):null,(F=a.after)==null?void 0:F.call(a)])}}}),bi=(e,t,i=!1)=>"activeMatch"in t?new RegExp(t.activeMatch).test(e.path):kl(e,t.link)?!0:t.children&&!i?t.children.some(a=>bi(e,a)):!1,Xs=(e,t)=>t.type==="group"?t.children.some(i=>i.type==="group"?Xs(e,i):i.type==="page"&&bi(e,i,!0))||"prefix"in t&&kl(e,t.prefix):!1,ec=(e,t)=>e.link?u(ot,{...t,config:e}):u("p",t,[u(Je,{icon:e.icon}),e.text]),tc=e=>{const t=xe();return e?u("ul",{class:"sidebar-sub-headers"},e.map(i=>{const a=bi(t,i,!0);return u("li",{class:"sidebar-sub-header"},[ec(i,{class:["sidebar-link","heading",{active:a}]}),tc(i.children)])})):null},El={},_n=(e="",t="")=>t.startsWith("/")?t:`${op(e)}${t}`,Zg=(e,t)=>{const i=je();return{type:"heading",text:e.title,link:`${i.value.path}#${e.slug}`,children:Pl(e.children,t)}},Pl=(e,t)=>t>0?e.map(i=>Zg(i,t-1)):[],Jg=e=>{const t=je();return Pl(t.value.headers,e)},zn=(e,t,i="")=>{const a=je(),n=xe(),l=(o,r=i)=>{var d;const c=ye(o)?hi(_n(r,o)):o.link?{...o,...Oa(o.link)?{}:{link:hi(_n(r,o.link)).link}}:o;if("children"in c){const h=_n(r,c.prefix),p=c.children==="structure"?El[h]:c.children;return{type:"group",...c,prefix:h,children:p.map(m=>l(m,h))}}return{type:"page",...c,children:c.link===n.path?Pl(((d=a.value.headers[0])==null?void 0:d.level)===1?a.value.headers[0].children:a.value.headers,t):[]}};return e.map(o=>l(o))},Qg=(e,t)=>{const i=xe(),a=Object.keys(e).sort((n,l)=>l.length-n.length);for(const n of a)if(decodeURI(i.path).startsWith(n)){const l=e[n];return l?zn(l==="structure"?El[n]:l,t,n):[]}return console.warn(`${i.path} do not have valid sidebar config`),[]},Xg=()=>{var l,o,r,c;const e=yt(),t=Pe(),i=ve(),a=t.value.home?!1:(o=(l=t.value.sidebar)!=null?l:i.value.sidebar)!=null?o:"structure",n=(c=(r=t.value.headerDepth)!=null?r:i.value.headerDepth)!=null?c:2;return a===!1?[]:a==="heading"?Jg(n):a==="structure"?zn(El[e.value],n,e.value):ae(a)?zn(a,n):vl(a)?Qg(a,n):[]},ic=Symbol.for("sidebarItems"),e0=()=>{const e=P(()=>Xg());Ke(ic,e)},Ll=()=>{const e=we(ic);if(!e)throw new Error("useSidebarItems() is called without provider.");return e};const t0=K({name:"CommonWrapper",props:{navbar:{type:Boolean,default:!0},sidebar:{type:Boolean,default:!0}},setup(e,{slots:t}){const i=Xe(),a=je(),n=Pe(),l=ve(),o=oa(),r=ne(!1),c=P(()=>e.navbar===!1||n.value.navbar===!1||l.value.navbar===!1?!1:Boolean(a.value.title||l.value.logo||l.value.repo||l.value.navbar)),d=Ll(),h=P(()=>e.sidebar===!1?!1:n.value.sidebar!==!1&&d.value.length!==0&&!n.value.home),p=ne(!1),m=ne(!1),f=S=>{p.value=typeof S=="boolean"?S:!p.value},y=S=>{m.value=typeof S=="boolean"?S:!m.value},_={x:0,y:0},x=S=>{_.x=S.changedTouches[0].clientX,_.y=S.changedTouches[0].clientY},b=S=>{const T=S.changedTouches[0].clientX-_.x,A=S.changedTouches[0].clientY-_.y;Math.abs(T)>Math.abs(A)*1.5&&Math.abs(T)>40&&(T>0&&_.x<=80?f(!0):f(!1))},w=P(()=>n.value.toc||l.value.toc!==!1&&n.value.toc!==!1),E=()=>window.pageYOffset||document.documentElement.scrollTop||document.body.scrollTop||0;let C,F=0;return Bt("scroll",kp(()=>{const S=E();F58?p.value||(r.value=!0):r.value=!1,F=S},300,!0)),De(o,S=>{S||f(!1)}),Se(()=>{C=i.afterEach(()=>{f(!1)})}),xi(()=>{C()}),()=>u("div",{class:["theme-container",{"no-navbar":!c.value,"no-sidebar":!h.value&&!(t.sidebar||t.sidebarTop||t.sidebarBottom),"has-toc":w.value,"hide-navbar":r.value,"sidebar-collapsed":!o.value&&m.value,"sidebar-open":o.value&&p.value},n.value.containerClass||""],onTouchStart:x,onTouchEnd:b},u(Wi("GloablEncrypt")?Me("GloablEncrypt"):xs,()=>{var S;return[c.value?u(Me("Navbar"),{onToggleSidebar:()=>f()},{leftStart:()=>{var T;return(T=t.navbarLeftStart)==null?void 0:T.call(t)},leftEnd:()=>{var T;return(T=t.navbarLeftEnd)==null?void 0:T.call(t)},centerStart:()=>{var T;return(T=t.navbarCenterStart)==null?void 0:T.call(t)},centerEnd:()=>{var T;return(T=t.navbarCenterEnd)==null?void 0:T.call(t)},rightStart:()=>{var T;return(T=t.navbarRightStart)==null?void 0:T.call(t)},rightEnd:()=>{var T;return(T=t.navbarRightEnd)==null?void 0:T.call(t)},screenTop:()=>{var T;return(T=t.navScreenTop)==null?void 0:T.call(t)},screenBottom:()=>{var T;return(T=t.navScreenBottom)==null?void 0:T.call(t)}}):null,u(Dt,{name:"fade"},()=>p.value?u("div",{class:"sidebar-mask",onClick:()=>f(!1)}):null),u(Dt,{name:"fade"},()=>o.value?null:u("div",{class:"toggle-sidebar-wrapper",onClick:()=>y()},u("span",{class:["arrow",m.value?"right":"left"]}))),u(Me("Sidebar"),{},{...t.sidebar?{default:()=>{var T;return(T=t.sidebar)==null?void 0:T.call(t)}}:{},top:()=>{var T;return(T=t.sidebarTop)==null?void 0:T.call(t)},bottom:()=>{var T;return(T=t.sidebarBottom)==null?void 0:T.call(t)}}),(S=t.default)==null?void 0:S.call(t),u(Kg)]}))}}),i0="/text-only-light.svg",a0="/logo-only-light.svg",n0="/tutanota-logo.webp",l0=K({name:"HomeFeatures",setup(){const e=Pe(),t=P(()=>ae(e.value.features)?e.value.features:[]);return()=>{var i;return t.value.length?u("div",{class:"features"},(i=e.value.features)==null?void 0:i.map(a=>{const n=[u(Je,{icon:a.icon}),u("h2",{innerHTML:a.title}),u("p",{innerHTML:a.details})];return a.link?Oa(a.link)?u("a",{class:"feature link",href:a.link,role:"navigation","aria-label":a.title,target:"_blank"},n):u(Qe,{class:"feature link",to:a.link,role:"navigation","aria-label":a.title},()=>n):u("div",{class:"feature"},n)})):null}}}),ac=(e,t)=>{const i=e.__vccOpts||e;for(const[a,n]of t)i[a]=n;return i},o0={__name:"Starfield",setup(e){const t=ne(null),i=ne(null);return Se(()=>{const l="rgba(26, 5, 40, 0.6)";class o{constructor(x=0,b=0,w=0){ai(this,"x");ai(this,"y");ai(this,"z");ai(this,"xPrev");ai(this,"yPrev");this.x=x,this.y=b,this.z=w,this.xPrev=x,this.yPrev=b}update(x,b,w){this.xPrev=this.x,this.yPrev=this.y,this.z+=w*.0675,this.x+=this.x*(w*.0225)*this.z,this.y+=this.y*(w*.0225)*this.z,(this.x>x/2||this.x<-x/2||this.y>b/2||this.y<-b/2)&&(this.x=Math.random()*x-x/2,this.y=Math.random()*b-b/2,this.xPrev=this.x,this.yPrev=this.y,this.z=0)}draw(x){x.lineWidth=this.z,x.beginPath(),x.moveTo(this.x,this.y),x.lineTo(this.xPrev,this.yPrev),x.stroke()}}const r=Array.from({length:40},()=>new o(0,0,0));let c=0;const d=document.querySelector("#starfield-canvas"),h=d==null?void 0:d.getContext("2d"),p=document.querySelector("#starfield"),m=new ResizeObserver(f);h&&m.observe(p);function f(){c>0&&cancelAnimationFrame(c);const{clientWidth:_,clientHeight:x}=p,b=window.devicePixelRatio||1;d.width=_*b,d.height=x*b,d.style.width=`${_}px`,d.style.height=`${x}px`,h.scale(b,b);for(const w of r)w.x=Math.random()*_-_/2,w.y=Math.random()*x-x/2,w.z=0;h.translate(_/2,x/2),h.fillStyle=l,h.strokeStyle="white",c=requestAnimationFrame(y)}function y(){const{clientWidth:_,clientHeight:x}=p;for(const b of r)b.update(_,x,.1),b.draw(h);h.fillRect(-_/2,-x/2,_,x),c=requestAnimationFrame(y)}}),(a,n)=>(Mi(),wa("div",{ref_key:"_container",ref:i,id:"starfield",style:{position:"absolute",top:"0",left:"0",right:"0",bottom:"0"}},[$e("canvas",{ref_key:"_canvas",ref:t,id:"starfield-canvas"},null,512)],512))}},r0=ac(o0,[["__file","Starfield.vue"]]);const s0={id:"hero"},c0=$e("img",{src:i0},null,-1),u0=["href"],d0=$e("img",{src:a0},null,-1),h0={class:"home project",id:"main-content","aria-labelledby":"main-title"},p0=$e("hr",null,null,-1),m0=$e("div",{id:"sponsors"},[$e("h1",null,"Our Sponsors"),$e("h3",null,"Thank you to the following organizations for sponsoring our project!"),$e("div",{class:"links"},[$e("a",{href:"https://tutanota.com/",target:"_blank"},[$e("img",{src:n0,alt:"tutanota.com"})])])],-1),g0={__name:"HomePage",setup(e){const{actions:t}=Pe().value,{description:i}=Fl().value;return(a,n)=>{const l=Me("Content");return Mi(),wa(Be,null,[Oe(r0),$e("div",s0,[$e("div",null,[c0,$e("h1",null,ql(at(i)),1),(Mi(!0),wa(Be,null,Wd(at(t),({text:o,link:r,type:c})=>(Mi(),wa("a",{href:r,class:Fa(`nav-link action-button ${c||"default"}`)},ql(o),11,u0))),256))]),d0]),$e("main",h0,[Oe(at(l0)),Oe(l),p0,m0])],64)}}},v0=ac(g0,[["__file","HomePage.vue"]]),f0=(e,t)=>{const i=e.path.replace(t,"/").split("/"),a=[];let n=fl(t);return i.forEach((l,o)=>{o!==i.length-1?(n+=`${l}/`,a.push(n)):l!==""&&(n+=l,a.push(n))}),a},nc=e=>!ti(e)||/github\.com/.test(e)?"GitHub":/bitbucket\.org/.test(e)?"Bitbucket":/gitlab\.com/.test(e)?"GitLab":/gitee\.com/.test(e)?"Gitee":null;const _0=K({name:"BreadCrumb",setup(){const e=Xe(),t=xe(),i=yt(),a=Pe(),n=ve(),l=ne([]),o=P(()=>(a.value.breadcrumb||a.value.breadcrumb!==!1&&n.value.breadcrumb!==!1)&&l.value.length>1),r=P(()=>a.value.breadcrumbIcon||a.value.breadcrumbIcon!==!1&&n.value.breadcrumbIcon!==!1),c=()=>{const d=e.getRoutes(),h=f0(t,i.value).map(p=>{const m=d.find(f=>f.path===p);if(m){const{meta:f,path:y}=na(e,m.path);if(f.shortTitle||f.title)return{title:f.shortTitle||f.title,icon:f.icon,path:y}}return null}).filter(p=>p!==null);h.length>1&&(l.value=h)};return Se(()=>{c(),De(()=>t.path,c)}),()=>u("nav",{class:["breadcrumb",{disable:!o.value}]},o.value?u("ol",{vocab:"https://schema.org/",typeof:"BreadcrumbList"},l.value.map((d,h)=>u("li",{class:{"is-active":l.value.length-1===h},property:"itemListElement",typeof:"ListItem"},[u(Qe,{to:d.path,property:"item",typeof:"WebPage"},()=>[r.value?u(Je,{icon:d.icon}):null,u("span",{property:"name"},d.title||"Unknown")]),u("meta",{property:"position",content:h+1})]))):[])}}),Ja=({custom:e})=>u(bu,{class:["theme-hope-content",{custom:e}]});Ja.displayName="MarkdownContent";Ja.props={custom:Boolean};const nr=e=>e===!1?!1:ye(e)?hi(e,!0):vl(e)?e:null,Hn=(e,t,i)=>{const a=e.findIndex(n=>n.link===t);if(a!==-1){const n=e[a+i];return n!=null&&n.link?n:null}for(const n of e)if(n.children){const l=Hn(n.children,t,i);if(l)return l}return null},b0=K({name:"PageNav",setup(){const e=ve(),t=Pe(),i=Ll(),a=xe(),n=ra(),l=P(()=>{const r=nr(t.value.prev);return r===!1?null:r||(e.value.prevLink===!1?null:Hn(i.value,a.path,-1))}),o=P(()=>{const r=nr(t.value.next);return r===!1?null:r||(e.value.nextLink===!1?null:Hn(i.value,a.path,1))});return Bt("keydown",r=>{r.altKey&&(r.key==="ArrowRight"?o.value&&(n(o.value.link),r.preventDefault()):r.key==="ArrowLeft"&&l.value&&(n(l.value.link),r.preventDefault()))}),()=>l.value||o.value?u("nav",{class:"page-nav"},[l.value?u(ot,{class:"prev",config:l.value},()=>{var r,c;return[u("div",{class:"hint"},[u("span",{class:"arrow left"}),e.value.metaLocales.prev]),u("div",{class:"link"},[u(Je,{icon:(r=l.value)==null?void 0:r.icon}),(c=l.value)==null?void 0:c.text])]}):null,o.value?u(ot,{class:"next",config:o.value},()=>{var r,c;return[u("div",{class:"hint"},[e.value.metaLocales.next,u("span",{class:"arrow right"})]),u("div",{class:"link"},[(r=o.value)==null?void 0:r.text,u(Je,{icon:(c=o.value)==null?void 0:c.icon})])]}):null]):null}}),lc=()=>u(pe,{name:"author"},()=>u("path",{d:"M649.6 633.6c86.4-48 147.2-144 147.2-249.6 0-160-128-288-288-288s-288 128-288 288c0 108.8 57.6 201.6 147.2 249.6-121.6 48-214.4 153.6-240 288-3.2 9.6 0 19.2 6.4 25.6 3.2 9.6 12.8 12.8 22.4 12.8h704c9.6 0 19.2-3.2 25.6-12.8 6.4-6.4 9.6-16 6.4-25.6-25.6-134.4-121.6-240-243.2-288z"}));lc.displayName="AuthorIcon";const oc=()=>u(pe,{name:"calendar"},()=>u("path",{d:"M716.4 110.137c0-18.753-14.72-33.473-33.472-33.473-18.753 0-33.473 14.72-33.473 33.473v33.473h66.993v-33.473zm-334.87 0c0-18.753-14.72-33.473-33.473-33.473s-33.52 14.72-33.52 33.473v33.473h66.993v-33.473zm468.81 33.52H716.4v100.465c0 18.753-14.72 33.473-33.472 33.473a33.145 33.145 0 01-33.473-33.473V143.657H381.53v100.465c0 18.753-14.72 33.473-33.473 33.473a33.145 33.145 0 01-33.473-33.473V143.657H180.6A134.314 134.314 0 0046.66 277.595v535.756A134.314 134.314 0 00180.6 947.289h669.74a134.36 134.36 0 00133.94-133.938V277.595a134.314 134.314 0 00-133.94-133.938zm33.473 267.877H147.126a33.145 33.145 0 01-33.473-33.473c0-18.752 14.72-33.473 33.473-33.473h736.687c18.752 0 33.472 14.72 33.472 33.473a33.145 33.145 0 01-33.472 33.473z"}));oc.displayName="CalendarIcon";const rc=()=>u(pe,{name:"category"},()=>u("path",{d:"M148.41 106.992h282.176c22.263 0 40.31 18.048 40.31 40.31V429.48c0 22.263-18.047 40.31-40.31 40.31H148.41c-22.263 0-40.311-18.047-40.311-40.31V147.302c0-22.263 18.048-40.31 40.311-40.31zM147.556 553.478H429.73c22.263 0 40.311 18.048 40.311 40.31v282.176c0 22.263-18.048 40.312-40.31 40.312H147.555c-22.263 0-40.311-18.049-40.311-40.312V593.79c0-22.263 18.048-40.311 40.31-40.311zM593.927 106.992h282.176c22.263 0 40.31 18.048 40.31 40.31V429.48c0 22.263-18.047 40.31-40.31 40.31H593.927c-22.263 0-40.311-18.047-40.311-40.31V147.302c0-22.263 18.048-40.31 40.31-40.31zM730.22 920.502H623.926c-40.925 0-74.22-33.388-74.22-74.425V623.992c0-41.038 33.387-74.424 74.425-74.424h222.085c41.038 0 74.424 33.226 74.424 74.067v114.233c0 10.244-8.304 18.548-18.547 18.548s-18.548-8.304-18.548-18.548V623.635c0-20.388-16.746-36.974-37.33-36.974H624.13c-20.585 0-37.331 16.747-37.331 37.33v222.086c0 20.585 16.654 37.331 37.126 37.331H730.22c10.243 0 18.547 8.304 18.547 18.547 0 10.244-8.304 18.547-18.547 18.547z"}));rc.displayName="CategoryIcon";const sc=()=>u(pe,{name:"eye"},()=>u("path",{d:"M992 512.096c0-5.76-.992-10.592-1.28-11.136-.192-2.88-1.152-8.064-2.08-10.816-.256-.672-.544-1.376-.832-2.08-.48-1.568-1.024-3.104-1.6-4.32C897.664 290.112 707.104 160 512 160c-195.072 0-385.632 130.016-473.76 322.592-1.056 2.112-1.792 4.096-2.272 5.856a55.512 55.512 0 00-.64 1.6c-1.76 5.088-1.792 8.64-1.632 7.744-.832 3.744-1.568 11.168-1.568 11.168-.224 2.272-.224 4.032.032 6.304 0 0 .736 6.464 1.088 7.808.128 1.824.576 4.512 1.12 6.976h-.032c.448 2.08 1.12 4.096 1.984 6.08.48 1.536.992 2.976 1.472 4.032C126.432 733.856 316.992 864 512 864c195.136 0 385.696-130.048 473.216-321.696 1.376-2.496 2.24-4.832 2.848-6.912.256-.608.48-1.184.672-1.728 1.536-4.48 1.856-8.32 1.728-8.32l-.032.032c.608-3.104 1.568-7.744 1.568-13.28zM512 672c-88.224 0-160-71.776-160-160s71.776-160 160-160 160 71.776 160 160-71.776 160-160 160z"}));sc.displayName="EyeIcon";const cc=()=>u(pe,{name:"fire"},()=>u("path",{d:"M726.4 201.6c-12.8-9.6-28.8-6.4-38.4 0-9.6 9.6-16 25.6-9.6 38.4 6.4 12.8 9.6 28.8 12.8 44.8C604.8 83.2 460.8 38.4 454.4 35.2c-9.6-3.2-22.4 0-28.8 6.4-9.6 6.4-12.8 19.2-9.6 28.8 12.8 86.4-25.6 188.8-115.2 310.4-6.4-25.6-16-51.2-32-80-9.6-9.6-22.4-16-35.2-12.8-16 3.2-25.6 12.8-25.6 28.8-3.2 48-25.6 92.8-51.2 140.8C134.4 499.2 112 544 102.4 592c-32 150.4 99.2 329.6 233.6 380.8 9.6 3.2 19.2 6.4 32 9.6-25.6-19.2-41.6-51.2-48-96C294.4 691.2 505.6 640 515.2 460.8c153.6 105.6 224 336 137.6 505.6 3.2 0 6.4-3.2 9.6-3.2 0 0 3.2 0 3.2-3.2 163.2-89.6 252.8-208 259.2-345.6 16-211.2-163.2-390.4-198.4-412.8z"}));cc.displayName="FireIcon";const uc=()=>u(pe,{name:"tag"},()=>u("path",{d:"M939.902 458.563L910.17 144.567c-1.507-16.272-14.465-29.13-30.737-30.737L565.438 84.098h-.402c-3.215 0-5.726 1.005-7.634 2.913l-470.39 470.39a10.004 10.004 0 000 14.164l365.423 365.424c1.909 1.908 4.42 2.913 7.132 2.913s5.223-1.005 7.132-2.913l470.39-470.39c2.01-2.11 3.014-5.023 2.813-8.036zm-240.067-72.121c-35.458 0-64.286-28.828-64.286-64.286s28.828-64.285 64.286-64.285 64.286 28.828 64.286 64.285-28.829 64.286-64.286 64.286z"}));uc.displayName="TagIcon";const dc=()=>u(pe,{name:"timer"},()=>u("path",{d:"M799.387 122.15c4.402-2.978 7.38-7.897 7.38-13.463v-1.165c0-8.933-7.38-16.312-16.312-16.312H256.33c-8.933 0-16.311 7.38-16.311 16.312v1.165c0 5.825 2.977 10.874 7.637 13.592 4.143 194.44 97.22 354.963 220.201 392.763-122.204 37.542-214.893 196.511-220.2 389.397-4.661 5.049-7.638 11.651-7.638 19.03v5.825h566.49v-5.825c0-7.379-2.849-13.981-7.509-18.9-5.049-193.016-97.867-351.985-220.2-389.527 123.24-37.67 216.446-198.453 220.588-392.892zM531.16 450.445v352.632c117.674 1.553 211.787 40.778 211.787 88.676H304.097c0-48.286 95.149-87.382 213.728-88.676V450.445c-93.077-3.107-167.901-81.297-167.901-177.093 0-8.803 6.99-15.793 15.793-15.793 8.803 0 15.794 6.99 15.794 15.793 0 80.261 63.69 145.635 142.01 145.635s142.011-65.374 142.011-145.635c0-8.803 6.99-15.793 15.794-15.793s15.793 6.99 15.793 15.793c0 95.019-73.789 172.82-165.96 177.093z"}));dc.displayName="TimerIcon";const hc=()=>u(pe,{name:"word"},()=>[u("path",{d:"M518.217 432.64V73.143A73.143 73.143 0 01603.43 1.097a512 512 0 01419.474 419.474 73.143 73.143 0 01-72.046 85.212H591.36a73.143 73.143 0 01-73.143-73.143z"}),u("path",{d:"M493.714 566.857h340.297a73.143 73.143 0 0173.143 85.577A457.143 457.143 0 11371.566 117.76a73.143 73.143 0 0185.577 73.143v339.383a36.571 36.571 0 0036.571 36.571z"})]);hc.displayName="WordIcon";const It=()=>{const e=ve();return P(()=>e.value.metaLocales)},pc={"/":{word:"About $word words",less1Minute:"Less than 1 minute",time:"About $time min"}},k0={GitHub:":repo/edit/:branch/:path",GitLab:":repo/-/edit/:branch/:path",Gitee:":repo/edit/:branch/:path",Bitbucket:":repo/src/:branch/:path?mode=edit&spa=0&at=:branch&fileviewer=file-view-default"},y0=({docsRepo:e,docsBranch:t,docsDir:i,filePathRelative:a,editLinkPattern:n})=>{if(!a)return null;const l=nc(e);let o;return n?o=n:l!==null&&(o=k0[l]),o?o.replace(/:repo/,ti(e)?e:`https://github.com/${e}`).replace(/:branch/,t).replace(/:path/,us(`${fl(i)}/${a}`)):null},w0=()=>{const e=ve(),t=je(),i=Pe();return P(()=>{var p,m;const{repo:a,docsRepo:n=a,docsBranch:l="main",docsDir:o="",editLink:r,editLinkPattern:c=""}=e.value;if(!((m=(p=i.value.editLink)!=null?p:r)!=null?m:!0)||!n)return null;const h=y0({docsRepo:n,docsBranch:l,docsDir:o,editLinkPattern:c,filePathRelative:t.value.filePathRelative});return h?{text:e.value.metaLocales.editLink,link:h}:null})},x0=()=>{const e=Qa(),t=ve(),i=je(),a=Pe();return P(()=>{var o,r,c,d;return!((r=(o=a.value.lastUpdated)!=null?o:t.value.lastUpdated)!=null?r:!0)||!((c=i.value.git)!=null&&c.updatedTime)?null:new Date((d=i.value.git)==null?void 0:d.updatedTime).toLocaleString(e.value.lang)})},T0=()=>{const e=ve(),t=je(),i=Pe();return P(()=>{var n,l,o,r;return((l=(n=i.value.contributors)!=null?n:e.value.contributors)!=null?l:!0)&&(r=(o=t.value.git)==null?void 0:o.contributors)!=null?r:null})},E0=K({name:"AuthorInfo",inheritAttrs:!1,props:{author:{type:Array,required:!0},pure:Boolean},setup(e){const t=It();return()=>e.author.length?u("span",{class:"author-info","aria-label":`${t.value.author}${e.pure?"":"\u{1F58A}"}`,...e.pure?{}:{"data-balloon-pos":"down"}},[u(lc),u("span",e.author.map(i=>i.url?u("a",{class:"author-item",href:i.url,target:"_blank",rel:"noopener noreferrer"},i.name):u("span",{class:"author-item"},i.name))),u("span",{property:"author",content:e.author.map(i=>i.name).join(", ")})]):null}});const P0=K({name:"CategoryInfo",inheritAttrs:!1,props:{category:{type:Array,required:!0},pure:Boolean},setup(e){const t=Xe(),i=xe(),a=It(),n=(l="")=>{l&&i.path!==l&&t.push(l)};return()=>e.category.length?u("span",{class:"category-info","aria-label":`${a.value.category}${e.pure?"":"\u{1F308}"}`,...e.pure?{}:{"data-balloon-pos":"down"}},[u(rc),u("ul",{class:"categories-wrapper"},e.category.map(({name:l,path:o})=>u("li",u("span",{class:["category",{[`category${Wa(l,9)}`]:!e.pure,clickable:o}],role:o?"navigation":"",onClick:()=>n(o)},l)))),u("meta",{property:"articleSection",content:e.category.map(({name:l})=>l).join(",")})]):null}}),L0=K({name:"DateInfo",inheritAttrs:!1,props:{date:{type:Object,default:null},localizedDate:{type:String,default:""},pure:Boolean},setup(e){const t=fu(),i=It();return()=>{var a,n,l;return e.date?u("span",{class:"date-info","aria-label":`${i.value.date}${e.pure?"":"\u{1F4C5}"}`,...e.pure?{}:{"data-balloon-pos":"down"}},[u(oc),u("span",e.localizedDate||((a=e.date.value)==null?void 0:a.toLocaleDateString(t.value))),u("meta",{property:"datePublished",content:((l=(n=e.date)==null?void 0:n.value)==null?void 0:l.toISOString())||""})]):null}}}),D0=K({name:"PageViewInfo",inheritAttrs:!1,props:{pageview:{type:[Boolean,String],default:!1},pure:Boolean},setup(e){const t=xe(),i=It(),a=ne(0),n=()=>{const l=document.querySelector(".waline-pageview-count");if(l){const o=l.textContent;o&&!isNaN(Number(o))?a.value=Number(o):setTimeout(n,500)}};return Se(()=>{setTimeout(n,1500)}),De(()=>t.path,(l,o)=>{l!==o&&setTimeout(n,500)}),()=>e.pageview?u("span",{class:"visitor-info","aria-label":`${i.value.views}${e.pure?"":"\u{1F522}"}`,...e.pure?{}:{"data-balloon-pos":"down"}},[u(a.value<1e3?sc:cc),u("span",{class:"waline-pageview-count","data-path":typeof e.pageview=="string"?e.pageview:mt(t.path)},"...")]):null}}),A0=K({name:"ReadingTimeInfo",inheritAttrs:!1,props:{readingTime:{type:Object,default:()=>null},pure:Boolean},setup(e){const t=It(),i=ia(pc),a=P(()=>{if(!e.readingTime)return null;const{minutes:n}=e.readingTime;return n<1?{text:i.value.less1Minute,time:"PT1M"}:{text:i.value.time.replace("$time",Math.round(n).toString()),time:`PT${Math.round(n)}M`}});return()=>a.value?u("span",{class:"reading-time-info","aria-label":`${t.value.readingTime}${e.pure?"":"\u231B"}`,...e.pure?{}:{"data-balloon-pos":"down"}},[u(dc),u("span",a.value.text),u("meta",{property:"timeRequired",content:a.value.time})]):null}});const I0=K({name:"TagInfo",inheritAttrs:!1,props:{tag:{type:Array,default:()=>[]},pure:Boolean},setup(e){const t=Xe(),i=xe(),a=It(),n=(l="")=>{l&&i.path!==l&&t.push(l)};return()=>e.tag.length?u("span",{"aria-label":`${a.value.tag}${e.pure?"":"\u{1F3F7}"}`,...e.pure?{}:{"data-balloon-pos":"down"}},[u(uc),u("ul",{class:"tags-wrapper"},e.tag.map(({name:l,path:o})=>u("li",u("span",{class:["tag",{[`tag${Wa(l,9)}`]:!e.pure,clickable:o}],role:o?"navigation":"",onClick:()=>n(o)},l)))),u("meta",{property:"keywords",content:e.tag.map(({name:l})=>l).join(",")})]):null}}),O0=K({name:"OriginalMark",inheritAttrs:!1,props:{isOriginal:Boolean},setup(e){const t=It();return()=>e.isOriginal?u("span",{class:"origin"},t.value.origin):null}}),R0=K({name:"ReadTimeInfo",inheritAttrs:!1,props:{readingTime:{type:Object,default:()=>null},pure:Boolean},setup(e){const t=It(),i=ia(pc),a=P(()=>{var l;return(l=e.readingTime)==null?void 0:l.words.toString()}),n=P(()=>i.value.word.replace("$word",a.value||""));return()=>a.value?u("span",{class:"words-info","aria-label":`${t.value.words}${e.pure?"":"\u{1F520}"}`,...e.pure?{}:{"data-balloon-pos":"down"}},[u(hc),u("span",n.value),u("meta",{property:"wordCount",content:a.value})]):null}});const mc=K({name:"PageInfo",components:{AuthorInfo:E0,CategoryInfo:P0,DateInfo:L0,OriginalInfo:O0,PageViewInfo:D0,ReadingTimeInfo:A0,TagInfo:I0,WordInfo:R0},props:{items:{type:[Array,Boolean],default:()=>["Author","Original","Date","Category","Tag","ReadingTime"]},config:{type:Object,required:!0}},setup(e){const t=la();return()=>e.items?u("div",{class:"page-info"},e.items.map(i=>u(Me(`${i}Info`),{...e.config,pure:t.value}))):null}});const C0=K({name:"PageTitle",setup(){const e=je(),t=Pe(),i=ve(),{config:a,items:n}=Gg();return()=>u("div",{class:"page-title"},[u("h1",[i.value.titleIcon!==!1?u(Je,{icon:t.value.icon}):null,e.value.title]),u(mc,{config:at(a),...n.value===null?{}:{items:n.value}}),u("hr")])}}),gc=()=>u(pe,{name:"edit"},()=>[u("path",{d:"M430.818 653.65a60.46 60.46 0 0 1-50.96-93.281l71.69-114.012 7.773-10.365L816.038 80.138A60.46 60.46 0 0 1 859.225 62a60.46 60.46 0 0 1 43.186 18.138l43.186 43.186a60.46 60.46 0 0 1 0 86.373L588.879 565.55l-8.637 8.637-117.466 68.234a60.46 60.46 0 0 1-31.958 11.229z"}),u("path",{d:"M728.802 962H252.891A190.883 190.883 0 0 1 62.008 771.98V296.934a190.883 190.883 0 0 1 190.883-192.61h267.754a60.46 60.46 0 0 1 0 120.92H252.891a69.962 69.962 0 0 0-69.098 69.099V771.98a69.962 69.962 0 0 0 69.098 69.098h475.911A69.962 69.962 0 0 0 797.9 771.98V503.363a60.46 60.46 0 1 1 120.922 0V771.98A190.883 190.883 0 0 1 728.802 962z"})]);gc.displayName="EditIcon";const S0=()=>u("svg",{xmlns:"http://www.w3.org/2000/svg",class:"not-found-icon",viewBox:"0 0 178 130",innerHTML:''});const V0=K({name:"PageMeta",setup(){const e=ve(),t=w0(),i=x0(),a=T0();return()=>{const{metaLocales:n}=e.value;return u("footer",{class:"page-meta"},[t.value?u("div",{class:"meta-item edit-link"},u(ot,{class:"label",config:t.value},{before:()=>u(gc)})):null,i.value?u("div",{class:"meta-item update-time"},[u("span",{class:"label"},`${n.lastUpdated}: `),u(Xa,()=>u("span",{class:"info"},i.value))]):null,a.value&&a.value.length?u("div",{class:"meta-item contributors"},[u("span",{class:"label"},`${n.contributors}: `),a.value.map(({email:l,name:o},r)=>[u("span",{class:"contributor",title:`email: ${l}`},o),r!==a.value.length-1?",":""])]):null])}}});const M0=({title:e,level:t,slug:i})=>u(Qe,{to:`#${i}`,class:["toc-link",`level${t}`]},()=>e),$n=(e,t)=>{const i=xe();return e.length&&t>0?u("ul",{class:"toc-list"},e.map(a=>[u("li",{class:["toc-item",{active:kl(i,`#${a.slug}`)}]},M0(a)),$n(a.children,t-1)])):null},vc=K({name:"TOC",props:{items:{type:Array,default:()=>[]},headerDepth:{type:Number,default:2}},setup(e){const t=xe(),i=je(),a=It(),n=ne(null),l=o=>{var r;(r=n.value)==null||r.scrollTo({top:o,behavior:"smooth"})};return Se(()=>{De(()=>t.hash,o=>{if(n.value){const r=document.querySelector(`#toc a.toc-link[href$="${o}"]`);if(!r)return;const{top:c,height:d}=n.value.getBoundingClientRect(),{top:h,height:p}=r.getBoundingClientRect();hc+d&&l(n.value.scrollTop+h+p-c-d)}})}),()=>{const o=e.items.length?$n(e.items,e.headerDepth):i.value.headers?$n(i.value.headers,e.headerDepth):null;return o?u("div",{class:"toc-place-holder"},[u("aside",{id:"toc"},[u("div",{class:"toc-header"},a.value.toc),u("div",{class:"toc-wrapper",ref:n},[o])])]):null}}}),fc=Symbol.for("darkMode"),Dl=()=>{const e=we(fc);if(!e)throw new Error("useDarkMode() is called without provider.");return e},F0=e=>{const t=gt(),i=Np(),a=ys("vuepress-theme-hope-scheme","auto"),n=P(()=>{const{darkmode:l}=t.value;return l==="disable"?!1:l==="enable"?!0:l==="auto"?i.value:l==="toggle"?a.value==="dark":a.value==="dark"||a.value==="auto"&&i.value});e.provide(fc,{isDarkMode:n,status:a}),Object.defineProperties(e.config.globalProperties,{$isDarkMode:{get:()=>n.value}})},z0=()=>{const{isDarkMode:e}=Dl(),t=(i=e.value)=>{const a=window==null?void 0:window.document.querySelector("html");a==null||a.setAttribute("data-theme",i?"dark":"light")};Se(()=>{De(e,t,{immediate:!0})})};const H0=K({name:"NormalPage",setup(e,{slots:t}){const i=Pe(),{isDarkMode:a}=Dl(),n=ve(),l=P(()=>i.value.toc||i.value.toc!==!1&&n.value.toc!==!1);return()=>u("main",{class:"page",id:"main-content"},u(Wi("LocalEncrypt")?Me("LocalEncrypt"):xs,()=>{var o,r,c,d,h,p;return[(o=t.top)==null?void 0:o.call(t),u(_0),u(C0),l.value?u(vc,{headerDepth:(c=(r=i.value.headerDepth)!=null?r:n.value.headerDepth)!=null?c:2}):null,(d=t.contentBefore)==null?void 0:d.call(t),u(Ja),(h=t.contentAfter)==null?void 0:h.call(t),u(V0),u(b0),Wi("CommentService")?u(Me("CommentService"),{darkmode:a.value}):null,(p=t.bottom)==null?void 0:p.call(t)]}))}}),_c=()=>u(pe,{name:"i18n"},()=>[u("path",{d:"M379.392 460.8 494.08 575.488l-42.496 102.4L307.2 532.48 138.24 701.44l-71.68-72.704L234.496 460.8l-45.056-45.056c-27.136-27.136-51.2-66.56-66.56-108.544h112.64c7.68 14.336 16.896 27.136 26.112 35.84l45.568 46.08 45.056-45.056C382.976 312.32 409.6 247.808 409.6 204.8H0V102.4h256V0h102.4v102.4h256v102.4H512c0 70.144-37.888 161.28-87.04 210.944L378.88 460.8zM576 870.4 512 1024H409.6l256-614.4H768l256 614.4H921.6l-64-153.6H576zM618.496 768h196.608L716.8 532.48 618.496 768z"})]);_c.displayName="I18nIcon";const bc=()=>u(pe,{name:"github"},()=>u("path",{d:"M511.957 21.333C241.024 21.333 21.333 240.981 21.333 512c0 216.832 140.544 400.725 335.574 465.664 24.49 4.395 32.256-10.07 32.256-23.083 0-11.69.256-44.245 0-85.205-136.448 29.61-164.736-64.64-164.736-64.64-22.315-56.704-54.4-71.765-54.4-71.765-44.587-30.464 3.285-29.824 3.285-29.824 49.195 3.413 75.179 50.517 75.179 50.517 43.776 75.008 114.816 53.333 142.762 40.79 4.523-31.66 17.152-53.377 31.19-65.537-108.971-12.458-223.488-54.485-223.488-242.602 0-53.547 19.114-97.323 50.517-131.67-5.035-12.33-21.93-62.293 4.779-129.834 0 0 41.258-13.184 134.912 50.346a469.803 469.803 0 0 1 122.88-16.554c41.642.213 83.626 5.632 122.88 16.554 93.653-63.488 134.784-50.346 134.784-50.346 26.752 67.541 9.898 117.504 4.864 129.834 31.402 34.347 50.474 78.123 50.474 131.67 0 188.586-114.73 230.016-224.042 242.09 17.578 15.232 33.578 44.672 33.578 90.454v135.85c0 13.142 7.936 27.606 32.854 22.87C862.25 912.597 1002.667 728.747 1002.667 512c0-271.019-219.648-490.667-490.71-490.667z"}));bc.displayName="GitHubIcon";const kc=()=>u(pe,{name:"gitlab"},()=>u("path",{d:"M229.333 78.688C223.52 62 199.895 62 193.895 78.688L87.958 406.438h247.5c-.188 0-106.125-327.75-106.125-327.75zM33.77 571.438c-4.875 15 .563 31.687 13.313 41.25l464.812 345L87.77 406.438zm301.5-165 176.813 551.25 176.812-551.25zm655.125 165-54-165-424.312 551.25 464.812-345c12.938-9.563 18.188-26.25 13.5-41.25zM830.27 78.688c-5.812-16.688-29.437-16.688-35.437 0l-106.125 327.75h247.5z"}));kc.displayName="GitlabIcon";const yc=()=>u(pe,{name:"gitee"},()=>u("path",{d:"M512 992C246.92 992 32 777.08 32 512S246.92 32 512 32s480 214.92 480 480-214.92 480-480 480zm242.97-533.34H482.39a23.7 23.7 0 0 0-23.7 23.7l-.03 59.28c0 13.08 10.59 23.7 23.7 23.7h165.96a23.7 23.7 0 0 1 23.7 23.7v11.85a71.1 71.1 0 0 1-71.1 71.1H375.71a23.7 23.7 0 0 1-23.7-23.7V423.11a71.1 71.1 0 0 1 71.1-71.1h331.8a23.7 23.7 0 0 0 23.7-23.7l.06-59.25a23.73 23.73 0 0 0-23.7-23.73H423.11a177.78 177.78 0 0 0-177.78 177.75v331.83c0 13.08 10.62 23.7 23.7 23.7h349.62a159.99 159.99 0 0 0 159.99-159.99V482.33a23.7 23.7 0 0 0-23.7-23.7z"}));yc.displayName="GiteeIcon";const wc=()=>u(pe,{name:"bitbucket"},()=>u("path",{d:"M575.256 490.862c6.29 47.981-52.005 85.723-92.563 61.147-45.714-20.004-45.714-92.562-1.133-113.152 38.29-23.442 93.696 7.424 93.696 52.005zm63.451-11.996c-10.276-81.152-102.29-134.839-177.152-101.156-47.433 21.138-79.433 71.424-77.129 124.562 2.853 69.705 69.157 126.866 138.862 120.576S647.3 548.571 638.708 478.83zm136.558-309.723c-25.161-33.134-67.986-38.839-105.728-45.13-106.862-17.151-216.576-17.7-323.438 1.134-35.438 5.706-75.447 11.996-97.719 43.996 36.572 34.304 88.576 39.424 135.424 45.129 84.553 10.862 171.447 11.447 256 .585 47.433-5.705 99.987-10.276 135.424-45.714zm32.585 591.433c-16.018 55.99-6.839 131.438-66.304 163.986-102.29 56.576-226.304 62.867-338.87 42.862-59.43-10.862-129.135-29.696-161.72-85.723-14.3-54.858-23.442-110.848-32.585-166.84l3.438-9.142 10.276-5.157c170.277 112.567 408.576 112.567 579.438 0 26.844 8.01 6.84 40.558 6.29 60.014zm103.424-549.157c-19.42 125.148-41.728 249.71-63.415 374.272-6.29 36.572-41.728 57.162-71.424 72.558-106.862 53.724-231.424 62.866-348.562 50.286-79.433-8.558-160.585-29.696-225.134-79.433-30.28-23.443-30.28-63.415-35.986-97.134-20.005-117.138-42.862-234.277-57.161-352.585 6.839-51.42 64.585-73.728 107.447-89.71 57.16-21.138 118.272-30.866 178.87-36.571 129.134-12.58 261.157-8.01 386.304 28.562 44.581 13.13 92.563 31.415 122.844 69.705 13.714 17.7 9.143 40.01 6.29 60.014z"}));wc.displayName="BitbucketIcon";const xc=()=>u(pe,{name:"source"},()=>u("path",{d:"M601.92 475.2c0 76.428-8.91 83.754-28.512 99.594-14.652 11.88-43.956 14.058-78.012 16.434-18.81 1.386-40.392 2.97-62.172 6.534-18.612 2.97-36.432 9.306-53.064 17.424V299.772c37.818-21.978 63.36-62.766 63.36-109.692 0-69.894-56.826-126.72-126.72-126.72S190.08 120.186 190.08 190.08c0 46.926 25.542 87.714 63.36 109.692v414.216c-37.818 21.978-63.36 62.766-63.36 109.692 0 69.894 56.826 126.72 126.72 126.72s126.72-56.826 126.72-126.72c0-31.086-11.286-59.598-29.7-81.576 13.266-9.504 27.522-17.226 39.996-19.206 16.038-2.574 32.868-3.762 50.688-5.148 48.312-3.366 103.158-7.326 148.896-44.55 61.182-49.698 74.25-103.158 75.24-187.902V475.2h-126.72zM316.8 126.72c34.848 0 63.36 28.512 63.36 63.36s-28.512 63.36-63.36 63.36-63.36-28.512-63.36-63.36 28.512-63.36 63.36-63.36zm0 760.32c-34.848 0-63.36-28.512-63.36-63.36s28.512-63.36 63.36-63.36 63.36 28.512 63.36 63.36-28.512 63.36-63.36 63.36zM823.68 158.4h-95.04V63.36h-126.72v95.04h-95.04v126.72h95.04v95.04h126.72v-95.04h95.04z"}));xc.displayName="SourceIcon";const Tc=K({name:"NavbarDropdownLink",props:{config:{type:Object,required:!0}},setup(e,{slots:t}){const i=xe(),a=Ji(e,"config"),n=P(()=>a.value.ariaLabel||a.value.text),l=ne(!1);De(()=>i.path,()=>{l.value=!1});const o=r=>{r.detail===0&&(l.value=!l.value)};return()=>{var r;return u("div",{class:["dropdown-wrapper",{open:l.value}]},[u("button",{class:"dropdown-title",type:"button","aria-label":n.value,onClick:o},[((r=t.title)==null?void 0:r.call(t))||u("span",{class:"title"},[u(Je,{icon:a.value.icon}),e.config.text]),u("span",{class:"arrow"}),u("ul",{class:"nav-dropdown"},a.value.children.map((c,d)=>{const h=d===a.value.children.length-1;return u("li",{class:"dropdown-item"},"children"in c?[u("h4",{class:"dropdown-subtitle"},c.link?u(ot,{config:c,onFocusout:()=>{c.children.length===0&&h&&(l.value=!1)}}):u("span",c.text)),u("ul",{class:"dropdown-subitem-wrapper"},c.children.map((p,m)=>u("li",{class:"dropdown-subitem"},u(ot,{config:p,onFocusout:()=>{m===c.children.length-1&&h&&(l.value=!1)}}))))]:u(ot,{config:c,onFocusout:()=>{h&&(l.value=!1)}}))}))])])}}}),Ec=(e,t="")=>ye(e)?hi(`${t}${e}`):"children"in e?{...e,...e.link&&!Oa(e.link)?hi(`${t}${e.link}`):{},children:e.children.map(i=>Ec(i,`${t}${e.prefix||""}`))}:{...e,link:Oa(e.link)?e.link:hi(`${t}${e.link}`).link},Pc=()=>P(()=>(ve().value.navbar||[]).map(e=>Ec(e))),$0=()=>{const e=Xe(),t=yt(),i=Qa(),a=gt(),n=ve();return P(()=>{const l=Object.keys(i.value.locales);if(l.length<2)return null;const{path:o,hash:r}=e.currentRoute.value,{navbarLocales:c}=n.value;return{text:"",ariaLabel:c==null?void 0:c.selectLangAriaLabel,children:l.map(h=>{var x,b,w,E,C,F,S;const p=(b=(x=i.value.locales)==null?void 0:x[h])!=null?b:{},m=(E=(w=a.value.locales)==null?void 0:w[h])!=null?E:{},f=p.lang||"",y=(F=(C=m.navbarLocales)==null?void 0:C.langName)!=null?F:f;let _;if(f===i.value.lang)_=o;else{const T=o.replace(t.value,h);_=e.getRoutes().some(A=>A.path===T)?`${T}${r}`:(S=m.home)!=null?S:h}return{text:y,link:_}})}})},N0=()=>{const e=ve(),t=P(()=>e.value.repo||null),i=P(()=>t.value?nc(t.value):null),a=P(()=>t.value&&!ti(t.value)?`https://github.com/${t.value}`:t.value),n=P(()=>{var l;return a.value?(l=e.value.repoLabel)!=null?l:i.value===null?"Source":i.value:null});return P(()=>!a.value||!n.value||e.value.repoDisplay===!1?null:{type:i.value||"Source",label:n.value,link:a.value})},q0=K({name:"LanguageDropdown",setup(){const e=$0();return()=>e.value?u("div",{class:"nav-item"},u(Tc,{class:"i18n-dropdown",config:e.value},{title:()=>{var t;return u(_c,{"aria-label":(t=e.value)==null?void 0:t.ariaLabel,style:{width:"1rem",height:"1rem",verticalAlign:"middle"}})}})):null}});const B0=K({name:"NavbarBrand",setup(){const e=yt(),t=Qa(),i=ve(),a=P(()=>i.value.home||e.value),n=P(()=>t.value.title),l=P(()=>i.value.logo?mt(i.value.logo):null),o=P(()=>i.value.logoDark?mt(i.value.logoDark):null);return()=>u(Qe,{to:a.value,class:"brand"},()=>[l.value?u("img",{class:["logo",{light:Boolean(o.value)}],src:l.value,alt:n.value}):null,o.value?u("img",{class:["logo dark"],src:o.value,alt:n.value}):null,n.value?u("span",{class:["site-name",{"hide-in-pad":l.value}]},n.value):null])}});const U0=K({name:"NavbarLinks",setup(){const e=Pc();return()=>e.value.length?u("nav",{class:"nav-links"},[...e.value.map(t=>u("div",{class:"nav-item hide-in-mobile"},"children"in t?u(Tc,{config:t}):u(ot,{config:t})))]):null}});const j0=K({name:"NavScreenDropdown",props:{config:{type:Object,required:!0}},setup(e){const t=xe(),i=Ji(e,"config"),a=P(()=>i.value.ariaLabel||i.value.text),n=ne(!1);De(()=>t.path,()=>{n.value=!1});const l=(o,r)=>r[r.length-1]===o;return()=>[u("button",{class:["nav-screen-dropdown-title",{active:n.value}],type:"button","aria-label":a.value,onClick:()=>{n.value=!n.value}},[u("span",{class:"title"},[u(Je,{icon:i.value.icon}),e.config.text]),u("span",{class:["arrow",n.value?"down":"right"]})]),u("ul",{class:["nav-screen-dropdown",{hide:!n.value}]},i.value.children.map(o=>u("li",{class:"dropdown-item"},"children"in o?[u("h4",{class:"dropdown-subtitle"},o.link?u(ot,{config:o,onFocusout:()=>{l(o,i.value.children)&&o.children.length===0&&(n.value=!1)}}):u("span",o.text)),u("ul",{class:"dropdown-subitem-wrapper"},o.children.map(r=>u("li",{class:"dropdown-subitem"},u(ot,{config:r,onFocusout:()=>{l(r,o.children)&&l(o,i.value.children)&&(n.value=!1)}}))))]:u(ot,{config:o,onFocusout:()=>{l(o,i.value.children)&&(n.value=!1)}}))))]}});const W0=K({name:"NavScreenLinks",setup(){const e=Pc();return()=>e.value.length?u("nav",{class:"nav-screen-links"},e.value.map(t=>u("div",{class:"navbar-links-item"},"children"in t?u(j0,{config:t}):u(ot,{config:t})))):null}}),Lc=()=>u(pe,{name:"dark"},()=>u("path",{d:"M524.8 938.667h-4.267a439.893 439.893 0 0 1-313.173-134.4 446.293 446.293 0 0 1-11.093-597.334A432.213 432.213 0 0 1 366.933 90.027a42.667 42.667 0 0 1 45.227 9.386 42.667 42.667 0 0 1 10.24 42.667 358.4 358.4 0 0 0 82.773 375.893 361.387 361.387 0 0 0 376.747 82.774 42.667 42.667 0 0 1 54.187 55.04 433.493 433.493 0 0 1-99.84 154.88 438.613 438.613 0 0 1-311.467 128z"}));Lc.displayName="DarkIcon";const Dc=()=>u(pe,{name:"light"},()=>u("path",{d:"M952 552h-80a40 40 0 0 1 0-80h80a40 40 0 0 1 0 80zM801.88 280.08a41 41 0 0 1-57.96-57.96l57.96-58a41.04 41.04 0 0 1 58 58l-58 57.96zM512 752a240 240 0 1 1 0-480 240 240 0 0 1 0 480zm0-560a40 40 0 0 1-40-40V72a40 40 0 0 1 80 0v80a40 40 0 0 1-40 40zm-289.88 88.08-58-57.96a41.04 41.04 0 0 1 58-58l57.96 58a41 41 0 0 1-57.96 57.96zM192 512a40 40 0 0 1-40 40H72a40 40 0 0 1 0-80h80a40 40 0 0 1 40 40zm30.12 231.92a41 41 0 0 1 57.96 57.96l-57.96 58a41.04 41.04 0 0 1-58-58l58-57.96zM512 832a40 40 0 0 1 40 40v80a40 40 0 0 1-80 0v-80a40 40 0 0 1 40-40zm289.88-88.08 58 57.96a41.04 41.04 0 0 1-58 58l-57.96-58a41 41 0 0 1 57.96-57.96z"}));Dc.displayName="LightIcon";const Ac=()=>u(pe,{name:"auto"},()=>u("path",{d:"M512 992C246.92 992 32 777.08 32 512S246.92 32 512 32s480 214.92 480 480-214.92 480-480 480zm0-840c-198.78 0-360 161.22-360 360 0 198.84 161.22 360 360 360s360-161.16 360-360c0-198.78-161.22-360-360-360zm0 660V212c165.72 0 300 134.34 300 300 0 165.72-134.28 300-300 300z"}));Ac.displayName="AutoIcon";const Ic=()=>u(pe,{name:"enter-fullscreen"},()=>u("path",{d:"M762.773 90.24h-497.28c-96.106 0-174.4 78.293-174.4 174.4v497.28c0 96.107 78.294 174.4 174.4 174.4h497.28c96.107 0 175.04-78.293 174.4-174.4V264.64c0-96.213-78.186-174.4-174.4-174.4zm-387.2 761.173H215.04c-21.867 0-40.427-17.92-41.067-41.066V649.92c0-22.507 17.92-40.427 40.427-40.427 11.307 0 21.227 4.694 28.48 11.947 7.253 7.253 11.947 17.92 11.947 28.48v62.293l145.28-145.28c15.893-15.893 41.813-15.893 57.706 0 15.894 15.894 15.894 41.814 0 57.707l-145.28 145.28h62.294c22.506 0 40.426 17.92 40.426 40.427s-17.173 41.066-39.68 41.066zM650.24 165.76h160.427c21.866 0 40.426 17.92 41.066 41.067v160.426c0 22.507-17.92 40.427-40.426 40.427-11.307 0-21.227-4.693-28.48-11.947-7.254-7.253-11.947-17.92-11.947-28.48v-62.186L625.6 450.347c-15.893 15.893-41.813 15.893-57.707 0-15.893-15.894-15.893-41.814 0-57.707l145.28-145.28H650.88c-22.507 0-40.427-17.92-40.427-40.427s17.174-41.173 39.787-41.173z"}));Ic.displayName="EnterFullScreenIcon";const Oc=()=>u(pe,{name:"cancel-fullscreen"},()=>u("path",{d:"M778.468 78.62H247.922c-102.514 0-186.027 83.513-186.027 186.027V795.08c0 102.514 83.513 186.027 186.027 186.027h530.432c102.514 0 186.71-83.513 186.026-186.027V264.647C964.494 162.02 880.981 78.62 778.468 78.62zM250.88 574.35h171.122c23.324 0 43.122 19.115 43.804 43.805v171.121c0 24.008-19.114 43.122-43.122 43.122-12.06 0-22.641-5.006-30.378-12.743s-12.743-19.115-12.743-30.379V722.83L224.597 877.91c-16.953 16.952-44.6 16.952-61.553 0-16.953-16.954-16.953-44.602 0-61.554L318.009 661.39h-66.446c-24.007 0-43.122-19.114-43.122-43.122 0-24.12 18.432-43.918 42.439-43.918zm521.899-98.873H601.657c-23.325 0-43.122-19.114-43.805-43.804V260.55c0-24.007 19.115-43.122 43.122-43.122 12.06 0 22.642 5.007 30.379 12.743s12.743 19.115 12.743 30.38v66.445l154.965-154.965c16.953-16.953 44.601-16.953 61.554 0 16.953 16.953 16.953 44.6 0 61.554L705.536 388.55h66.446c24.007 0 43.122 19.115 43.122 43.122.114 24.007-18.318 43.804-42.325 43.804z"}));Oc.displayName="CancelFullScreenIcon";const Rc=()=>u(pe,{name:"outlook"},()=>[u("path",{d:"M224 800c0 9.6 3.2 44.8 6.4 54.4 6.4 48-48 76.8-48 76.8s80 41.6 147.2 0 134.4-134.4 38.4-195.2c-22.4-12.8-41.6-19.2-57.6-19.2C259.2 716.8 227.2 761.6 224 800zM560 675.2l-32 51.2c-51.2 51.2-83.2 32-83.2 32 25.6 67.2 0 112-12.8 128 25.6 6.4 51.2 9.6 80 9.6 54.4 0 102.4-9.6 150.4-32l0 0c3.2 0 3.2-3.2 3.2-3.2 22.4-16 12.8-35.2 6.4-44.8-9.6-12.8-12.8-25.6-12.8-41.6 0-54.4 60.8-99.2 137.6-99.2 6.4 0 12.8 0 22.4 0 12.8 0 38.4 9.6 48-25.6 0-3.2 0-3.2 3.2-6.4 0-3.2 3.2-6.4 3.2-6.4 6.4-16 6.4-16 6.4-19.2 9.6-35.2 16-73.6 16-115.2 0-105.6-41.6-198.4-108.8-268.8C704 396.8 560 675.2 560 675.2zM224 419.2c0-28.8 22.4-51.2 51.2-51.2 28.8 0 51.2 22.4 51.2 51.2 0 28.8-22.4 51.2-51.2 51.2C246.4 470.4 224 448 224 419.2zM320 284.8c0-22.4 19.2-41.6 41.6-41.6 22.4 0 41.6 19.2 41.6 41.6 0 22.4-19.2 41.6-41.6 41.6C339.2 326.4 320 307.2 320 284.8zM457.6 208c0-12.8 12.8-25.6 25.6-25.6 12.8 0 25.6 12.8 25.6 25.6 0 12.8-12.8 25.6-25.6 25.6C470.4 233.6 457.6 220.8 457.6 208zM128 505.6C128 592 153.6 672 201.6 736c28.8-60.8 112-60.8 124.8-60.8-16-51.2 16-99.2 16-99.2l316.8-422.4c-48-19.2-99.2-32-150.4-32C297.6 118.4 128 291.2 128 505.6zM764.8 86.4c-22.4 19.2-390.4 518.4-390.4 518.4-22.4 28.8-12.8 76.8 22.4 99.2l9.6 6.4c35.2 22.4 80 12.8 99.2-25.6 0 0 6.4-12.8 9.6-19.2 54.4-105.6 275.2-524.8 288-553.6 6.4-19.2-3.2-32-19.2-32C777.6 76.8 771.2 80 764.8 86.4z"})]);Rc.displayName="OutlookIcon";const Cc=K({name:"AppearanceSwitch",setup(){const e=gt(),{status:t}=Dl(),i=P(()=>e.value.darkmode),a=()=>{i.value==="switch"?t.value={light:"dark",dark:"auto",auto:"light"}[t.value]:t.value=t.value==="light"?"dark":"light"};return()=>u("button",{id:"appearance-switch",onClick:()=>a()},[u(Ac,{style:{display:t.value==="auto"?"block":"none"}}),u(Lc,{style:{display:t.value==="dark"?"block":"none"}}),u(Dc,{style:{display:t.value==="light"?"block":"none"}})])}}),G0=K({name:"AppearanceMode",setup(){const e=gt(),t=ve(),i=P(()=>t.value.outlookLocales.darkmode),a=P(()=>e.value.darkmode),n=P(()=>a.value==="switch"||a.value==="toggle");return()=>n.value?u("div",{class:"appearance-wrapper"},[u("label",{class:"appearance-title",for:"appearance-switch"},i.value),u(Cc)]):null}});const Y0=K({name:"ThemeColorPicker",props:{themeColor:{type:Object,required:!0}},setup(e){const t=(i="")=>{const a=document.documentElement.classList,n=Object.keys(e.themeColor).map(l=>`theme-${l}`);if(!i){localStorage.removeItem("theme"),a.remove(...n);return}a.remove(...n.filter(l=>l!==`theme-${i}`)),a.add(`theme-${i}`),localStorage.setItem("theme",i)};return Se(()=>{const i=localStorage.getItem("theme");i&&t(i)}),()=>u("ul",{id:"themecolor-picker"},[u("li",u("span",{class:"theme-color",onClick:()=>t()})),...Object.entries(e.themeColor).map(([i,a])=>u("li",u("span",{style:{background:a},onClick:()=>t(i)})))])}}),K0=K({name:"ThemeColor",setup(){const e=gt(),t=ve(),i=P(()=>t.value.outlookLocales.themeColor),a=P(()=>{const{themeColor:n}=e.value;return n===!1?null:n});return()=>a.value?u("div",{class:"themecolor-wrapper"},[u("label",{class:"themecolor-title",for:"theme-color-picker"},i.value),u(Y0,{themeColor:a.value})]):null}});const Sc=K({name:"ToggleFullScreenButton",setup(){const e=ve(),{isSupported:t,isFullscreen:i,toggle:a}=bl(),n=P(()=>e.value.outlookLocales.fullscreen);return()=>t?u("div",{class:"fullscreen-wrapper"},[u("label",{class:"full-screen-title",for:"full-screen-switch"},n.value),u("button",{class:"full-screen",id:"full-screen-switch",ariaPressed:i.value,onClick:()=>a()},i.value?u(Oc):u(Ic))]):null}}),Vc=K({name:"OutlookSettings",setup(){const e=gt(),t=la(),i=P(()=>e.value.darkmode!=="disable"&&e.value.darkmode!=="enable"),a=P(()=>!t.value&&Boolean(e.value.themeColor)),n=P(()=>!t.value&&e.value.fullscreen);return()=>u(Xa,()=>[a.value?u(K0):null,i.value?u(G0):null,n.value?u(Sc):null])}});const Z0=K({name:"NavScreen",props:{active:Boolean},emits:["close"],setup(e,{emit:t,slots:i}){const a=xe(),n=oa(),l=ne(),o=Up(l);return De(n,r=>{!r&&e.active&&t("close")}),De(()=>a.path,()=>{o.value=!1,t("close")}),Se(()=>{l.value=document.body}),xi(()=>{o.value=!1}),()=>u(Dt,{name:"fade",onEnter:()=>{o.value=!0},onAfterLeave:()=>{o.value=!1}},()=>{var r,c;return e.active?u("div",{id:"nav-screen"},u("div",{class:"container"},[(r=i.before)==null?void 0:r.call(i),u(W0),u("div",{class:"outlook-wrapper"},u(Vc)),(c=i.after)==null?void 0:c.call(i)])):null})}});const J0=K({name:"OutlookButton",setup(){const{isSupported:e}=bl(),t=gt(),i=la(),a=xe(),n=ne(!1),l=P(()=>t.value.darkmode!=="disable"&&t.value.darkmode!=="enable"),o=P(()=>!i.value&&Boolean(t.value.themeColor)),r=P(()=>!i.value&&t.value.fullscreen&&e);return De(()=>a.path,()=>{n.value=!1}),()=>l.value||r.value||o.value?u("div",{class:"nav-item hide-in-mobile"},l.value&&!r.value&&!o.value?u(Cc):r.value&&!l.value&&!o.value?u(Sc):u("button",{class:["outlook-button",{open:n.value}],tabindex:"-1",ariaHidden:!0},[u(Rc),u("div",{class:"outlook-dropdown"},u(Vc))])):null}});const Mc=({active:e=!1},{emit:t})=>u("button",{class:["toggle-navbar-button",{"is-active":e}],"aria-label":"Toggle Navbar","aria-expanded":e,"aria-controls":"nav-screen",onClick:()=>t("toggle")},u("span",{class:"button-container"},[u("span",{class:"button-top"}),u("span",{class:"button-middle"}),u("span",{class:"button-bottom"})]));Mc.displayName="ToggleNavbarButton";const Al=(e,{emit:t})=>u("button",{class:"toggle-sidebar-button",title:"Toggle Sidebar",onClick:()=>t("toggle")},u("span",{class:"icon"}));Al.displayName="ToggleSidebarButton";Al.emits=["toggle"];const Q0=K({name:"RepoLink",components:{BitbucketIcon:wc,GiteeIcon:yc,GitHubIcon:bc,GitlabIcon:kc,SourceIcon:xc},setup(){const e=N0();return()=>e.value?u("div",{class:"nav-item"},u("a",{class:"repo-link",href:e.value.link,target:"_blank",rel:"noopener noreferrer","aria-label":e.value.label},u(Me(`${e.value.type}Icon`),{style:{width:"1.25rem",height:"1.25rem",verticalAlign:"middle"}}))):null}});const X0=K({name:"NavBar",emits:["toggle-sidebar"],setup(e,{emit:t,slots:i}){const a=ve(),n=oa(),l=ne(!1),o=P(()=>{const{navbarAutoHide:c}=a.value;return c!=="none"&&(c==="always"||n.value)}),r=P(()=>a.value.navbarLayout||{left:["Brand"],center:["Links"],right:["Language","Repo","Outlook","Search"]});return()=>{var d,h,p,m,f,y;const c={Brand:u(B0),Language:u(q0),Links:u(U0),Repo:u(Q0),Outlook:u(J0),Search:Wi("Docsearch")?u(Me("Docsearch")):Wi("SearchBox")?u(Me("SearchBox")):null};return[u("header",{class:["navbar",{"auto-hide":o.value,"hide-icon":!a.value.navbarIcon}]},[u("div",{class:"navbar-left"},[u(Al,{onToggle:()=>{l.value&&(l.value=!1),t("toggle-sidebar")}}),(d=i["left-start"])==null?void 0:d.call(i),...r.value.left.map(_=>c[_]),(h=i["left-end"])==null?void 0:h.call(i)]),u("div",{class:"navbar-center"},[(p=i["center-start"])==null?void 0:p.call(i),...r.value.center.map(_=>c[_]),(m=i["center-end"])==null?void 0:m.call(i)]),u("div",{class:"navbar-right"},[(f=i["right-start"])==null?void 0:f.call(i),...r.value.right.map(_=>c[_]),(y=i["right-start"])==null?void 0:y.call(i),u(Mc,{active:l.value,onToggle:()=>{l.value=!l.value}})])]),u(Z0,{active:l.value,onClose:()=>{l.value=!1}},{before:()=>{var _;return(_=i.screenTop)==null?void 0:_.call(i)},after:()=>{var _;return(_=i.screenBottom)==null?void 0:_.call(i)}})]}}});const ev=K({name:"SidebarChild",props:{config:{type:Object,required:!0}},setup(e){const t=xe();return()=>[ec(e.config,{class:["sidebar-link",`sidebar-${e.config.type}`,{active:bi(t,e.config,!0)}],exact:!0}),tc(e.config.children)]}});const tv=K({name:"SidebarGroup",props:{config:{type:Object,required:!0},open:{type:Boolean,required:!0}},emits:["toggle"],setup(e,{emit:t}){const i=xe(),a=P(()=>bi(i,e.config)),n=P(()=>bi(i,e.config,!0));return()=>{const{collapsable:l,children:o=[],icon:r,prefix:c,link:d,text:h}=e.config;return u("section",{class:"sidebar-group"},[u(l?"button":"p",{class:["sidebar-heading",{clickable:l||d,exact:n.value,active:a.value}],...l?{onClick:()=>t("toggle"),onKeydown:p=>{p.key==="Enter"&&t("toggle")}}:{}},[u(Je,{icon:r}),d?u(Qe,{to:d,class:"title"},()=>h):u("span",{class:"title"},h),l?u("span",{class:["arrow",e.open?"down":"right"]}):null]),e.open||!l?u(Fc,{key:c,config:o}):null])}}});const Fc=K({name:"SidebarLinks",props:{config:{type:Array,required:!0}},setup(e){const t=xe(),i=ne(-1),a=n=>{i.value=n===i.value?-1:n};return De(()=>[t.path,e.config],()=>{const n=e.config.findIndex(l=>Xs(t,l));i.value=n},{immediate:!0}),()=>u("ul",{class:"sidebar-links"},e.config.map((n,l)=>u("li",n.type==="group"?u(tv,{config:n,open:l===i.value,onToggle:()=>a(l)}):u(ev,{config:n}))))}});const iv=K({name:"SideBar",setup(e,{slots:t}){const i=xe(),a=ve(),n=Ll(),l=ne(null);return Se(()=>{De(()=>i.hash,o=>{const r=document.querySelector(`.sidebar a.sidebar-link[href="${i.path}${o}"]`);if(!r)return;const{top:c,height:d}=l.value.getBoundingClientRect(),{top:h,height:p}=r.getBoundingClientRect();hc+d&&r.scrollIntoView(!1)})}),()=>{var o,r,c;return u("aside",{class:["sidebar",{"hide-icon":!a.value.sidebarIcon}],ref:l},[(o=t.top)==null?void 0:o.call(t),((r=t.default)==null?void 0:r.call(t))||u(Fc,{config:n.value}),(c=t.bottom)==null?void 0:c.call(t)])}}});const av=K({name:"FadeSlideY",setup(e,{slots:t}){const i=Qs(),a=i.resolve,n=i.pending;return()=>u(Dt,{name:"fade-slide-y",mode:"out-in",onBeforeEnter:a,onBeforeLeave:n},()=>{var l;return(l=t.default)==null?void 0:l.call(t)})}});const Il=K({name:"SkipLink",props:{content:{type:String,default:"main-content"}},setup(e){const t=xe(),i=ne();De(()=>t.path,()=>i.value.focus());const a=({target:n})=>{const l=document.querySelector(n.hash);if(l){const o=()=>{l.removeAttribute("tabindex"),l.removeEventListener("blur",o)};l.setAttribute("tabindex","-1"),l.addEventListener("blur",o),l.focus(),window.scrollTo(0,0)}};return()=>[u("span",{ref:i,tabindex:"-1"}),u("a",{href:`#${e.content}`,class:"skip-link sr-only",onClick:a},"Skip to content")]}}),nv=K({name:"Layout",setup(){const e=gt(),t=ve(),i=je(),a=Pe(),n=oa(),l=P(()=>t.value.blog.sidebarDisplay||e.value.blog.sidebarDisplay||"mobile");return()=>[u(Il),u(Me("CommonWrapper"),{},{default:()=>a.value.home?u(Me("HomePage")):u(av,()=>u(Me("NormalPage"),{key:i.value.path})),...l.value!=="none"?{navScreenBottom:()=>u(Me("BloggerInfo"))}:{},...!n.value&&l.value==="always"?{sidebar:()=>u(Me("BloggerInfo"))}:{}})]}});const lv=K({name:"NotFound",setup(){var n;const e=yt(),t=ve(),i=()=>{const l=t.value.routeLocales.notFoundMsg;return l[Math.floor(Math.random()*l.length)]},{navigate:a}=Fn({to:(n=t.value.home)!=null?n:e.value});return()=>[u(Il),u(Me("CommonWrapper"),{sidebar:!1},()=>u("main",{class:"page not-found",id:"main-content"},[u(S0),u("blockquote",i()),u("button",{class:"action-button",onClick:()=>{window.history.go(-1)}},t.value.routeLocales.back),u("button",{class:"action-button",onClick:()=>a()},t.value.routeLocales.home)]))]}});const ov={},zc={category:{"/":{path:"/category/",map:{dev:{path:"/category/dev/",keys:["v-d8729a94","v-cdb5fcd6","v-83b0b1e4","v-3ac80fd4","v-88d8cd3c","v-21124b56","v-1181f0fe","v-67f98f2c","v-3500d176","v-15a14140","v-6409cd37","v-77f85357","v-168f28ae","v-5fc19751","v-e8324d12","v-8b6c6124","v-5bad2e3c","v-25bd77f6","v-42666037","v-356c6c42","v-0730d41e","v-6bf84516","v-5e247e06","v-7e31f297","v-dbbf0e92","v-3a723159","v-0af04e6e","v-7f7a1a1a","v-6fcbebb6","v-f88b1c54","v-5fdcc4a1","v-59933042","v-4769b7e2","v-0075970e"]},log:{path:"/category/log/",keys:["v-703ed5aa","v-4f70c30a","v-04bbc674","v-879bed80","v-6cc4d458","v-4dac8919","v-3cb6722f","v-4b1ee0b9","v-54f2f18e","v-638b3e6c","v-e1c35862","v-29fd1295","v-3a050031","v-0075970e"]},news:{path:"/category/news/",keys:["v-703ed5aa","v-4f70c30a","v-04bbc674","v-879bed80","v-6cc4d458","v-64adae58","v-4dac8919","v-7b64cdf7","v-3cb6722f","v-4b1ee0b9","v-25d26d73","v-54f2f18e","v-638b3e6c","v-e1c35862","v-29fd1295","v-5999d3d4","v-3a050031","v-cfc0df38"]},survey:{path:"/category/survey/",keys:["v-aebfc812","v-1d4a9cb2","v-91e3038e","v-5b193c62"]}}}},tag:{"/":{path:"/tag/",map:{backend:{path:"/tag/backend/",keys:["v-7f7a1a1a","v-0075970e"]},sunset:{path:"/tag/sunset/",keys:["v-4769b7e2","v-0075970e"]},video:{path:"/tag/video/",keys:["v-cfc0df38"]},update:{path:"/tag/update/",keys:["v-703ed5aa","v-4f70c30a","v-04bbc674","v-879bed80","v-6cc4d458","v-4dac8919","v-3cb6722f","v-4b1ee0b9","v-54f2f18e","v-638b3e6c","v-e1c35862","v-29fd1295","v-3a050031"]},modernization:{path:"/tag/modernization/",keys:["v-1181f0fe","v-67f98f2c","v-168f28ae","v-5fc19751","v-8b6c6124","v-5bad2e3c","v-25bd77f6","v-5fdcc4a1"]},"tree-sitter":{path:"/tag/tree-sitter/",keys:["v-67f98f2c","v-168f28ae","v-5fc19751","v-8b6c6124","v-5bad2e3c","v-25bd77f6","v-5fdcc4a1"]},release:{path:"/tag/release/",keys:["v-d8729a94","v-cdb5fcd6","v-83b0b1e4","v-3ac80fd4","v-88d8cd3c","v-21124b56","v-3500d176","v-15a14140","v-6409cd37","v-77f85357","v-e8324d12","v-42666037","v-0730d41e","v-6bf84516","v-5e247e06","v-7e31f297","v-dbbf0e92","v-3a723159","v-6fcbebb6","v-f88b1c54"]},releases:{path:"/tag/releases/",keys:["v-5999d3d4"]},rolling:{path:"/tag/rolling/",keys:["v-5999d3d4"]},regular:{path:"/tag/regular/",keys:["v-5999d3d4"]},community:{path:"/tag/community/",keys:["v-aebfc812","v-1d4a9cb2","v-91e3038e","v-5b193c62"]},socials:{path:"/tag/socials/",keys:["v-1d4a9cb2","v-91e3038e","v-5b193c62"]},joke:{path:"/tag/joke/",keys:["v-0af04e6e"]},feedback:{path:"/tag/feedback/",keys:["v-aebfc812"]},github:{path:"/tag/github/",keys:["v-25d26d73"]},stars:{path:"/tag/stars/",keys:["v-25d26d73"]},windows:{path:"/tag/windows/",keys:["v-64adae58","v-7b64cdf7"]},chocolatey:{path:"/tag/chocolatey/",keys:["v-64adae58","v-7b64cdf7"]},"package manager":{path:"/tag/package-manager/",keys:["v-64adae58","v-7b64cdf7"]},ci:{path:"/tag/ci/",keys:["v-356c6c42"]},electron:{path:"/tag/electron/",keys:["v-1181f0fe"]}}}}};import.meta.webpackHot&&(import.meta.webpackHot.accept(),__VUE_HMR_RUNTIME__.updateBlogCategory&&__VUE_HMR_RUNTIME__.updateBlogCategory(zc));const Hc={article:{"/":{path:"/blog/",keys:["v-d8729a94","v-cdb5fcd6","v-83b0b1e4","v-3ac80fd4","v-88d8cd3c","v-21124b56","v-703ed5aa","v-1181f0fe","v-67f98f2c","v-3500d176","v-4f70c30a","v-15a14140","v-6409cd37","v-04bbc674","v-77f85357","v-168f28ae","v-879bed80","v-5fc19751","v-e8324d12","v-8b6c6124","v-6cc4d458","v-5bad2e3c","v-25bd77f6","v-64adae58","v-42666037","v-356c6c42","v-4dac8919","v-7b64cdf7","v-0730d41e","v-3cb6722f","v-6bf84516","v-5e247e06","v-4b1ee0b9","v-7e31f297","v-25d26d73","v-54f2f18e","v-aebfc812","v-dbbf0e92","v-638b3e6c","v-1d4a9cb2","v-3a723159","v-e1c35862","v-0af04e6e","v-91e3038e","v-7f7a1a1a","v-6fcbebb6","v-29fd1295","v-5b193c62","v-5999d3d4","v-f88b1c54","v-5fdcc4a1","v-3a050031","v-59933042","v-4769b7e2","v-cfc0df38","v-0075970e"]}},encrypted:{"/":{path:"/encrypted/",keys:[]}},slide:{"/":{path:"/slide/",keys:[]}},star:{"/":{path:"/star/",keys:[]}},timeline:{"/":{path:"/timeline/",keys:["v-d8729a94","v-cdb5fcd6","v-83b0b1e4","v-3ac80fd4","v-88d8cd3c","v-21124b56","v-703ed5aa","v-1181f0fe","v-67f98f2c","v-3500d176","v-4f70c30a","v-15a14140","v-6409cd37","v-04bbc674","v-77f85357","v-168f28ae","v-879bed80","v-5fc19751","v-e8324d12","v-8b6c6124","v-6cc4d458","v-5bad2e3c","v-25bd77f6","v-64adae58","v-42666037","v-356c6c42","v-4dac8919","v-7b64cdf7","v-0730d41e","v-3cb6722f","v-6bf84516","v-5e247e06","v-4b1ee0b9","v-7e31f297","v-25d26d73","v-54f2f18e","v-aebfc812","v-dbbf0e92","v-638b3e6c","v-1d4a9cb2","v-3a723159","v-e1c35862","v-0af04e6e","v-91e3038e","v-7f7a1a1a","v-6fcbebb6","v-29fd1295","v-5b193c62","v-5999d3d4","v-f88b1c54","v-5fdcc4a1","v-3a050031","v-59933042","v-4769b7e2","v-cfc0df38","v-0075970e"]}}};import.meta.webpackHot&&(import.meta.webpackHot.accept(),__VUE_HMR_RUNTIME__.updateBlogType&&__VUE_HMR_RUNTIME__.updateBlogType(Hc));const Nn=ne(zc),$c=(e="")=>{const t=Xe(),i=xe(),a=yt();return P(()=>{var n;const l=e||((n=Pe().value.blog)==null?void 0:n.key)||"",o=t.getRoutes();if(!Nn.value[l])throw new Error(`useBlogCategory: ${e?`key ${e} is invalid`:"can not bind to an exisiting key on non blog pages"}`);const r=Nn.value[l][a.value],c={path:r.path,map:{}};for(const d in r.map){const h=r.map[d];c.map[d]={path:h.path,items:[]};for(const p of h.keys){const m=o.find(({name:f})=>f===p);if(m){const f=na(t,m.path);c.map[d].items.push({path:f.path,info:f.meta})}}i.path===h.path&&(c.currentItems=c.map[d].items)}return c})};import.meta.webpackHot&&(__VUE_HMR_RUNTIME__.updateBlogCategory=e=>{Nn.value=e});const qn=ne(Hc),sa=(e="")=>{const t=Xe(),i=yt();return P(()=>{var a;const n=e||((a=Pe().value.blog)==null?void 0:a.key)||"";if(!qn.value[n])throw new Error(`useBlogType: ${e?`key ${e} is invalid`:"can not bind to an exisiting key on non blog pages"}`);const l=t.getRoutes(),o=qn.value[n][i.value],r={path:o.path,items:[]};for(const c of o.keys){const d=l.find(({name:h})=>h===c);if(d){const h=na(t,d.path);r.items.push({path:h.path,info:h.meta})}}return r})};import.meta.webpackHot&&(__VUE_HMR_RUNTIME__.updateBlogType=e=>{qn.value=e});const Nc=Symbol.for("categoryMap"),ca=()=>{const e=we(Nc);if(!e)throw new Error("useCategoryMap() is called without provider.");return e},rv=()=>{const e=$c("category");Ke(Nc,e)},ua=()=>{const e=gt(),t=ve();return P(()=>({...e.value.blog,...t.value.blog}))},qc=Symbol.for("tagMap"),da=()=>{const e=we(qc);if(!e)throw new Error("useTagMap() is called without provider.");return e},sv=()=>{const e=$c("tag");Ke(qc,e)},cv=e=>{const t=ve();return P(()=>{const{author:i}=e.value;return i?Gi(i):i===!1?[]:Gi(t.value.author,!1)})},uv=e=>{const t=ca();return P(()=>Rs(e.value.category).map(i=>({name:i,path:t.value.map[i].path})))},dv=e=>{const t=da();return P(()=>Cs(e.value.tag).map(i=>({name:i,path:t.value.map[i].path})))},hv=e=>P(()=>{const{date:t}=e.value;return t?Sa(t):null}),pv=e=>{const t=ua(),i=cv(e),a=uv(e),n=dv(e),l=hv(e),o=Ut({author:i.value,category:a.value,date:l.value,localizedDate:e.value.localizedDate||"",tag:n.value,isOriginal:e.value.isOriginal||!1,readingTime:e.value.readingTime||null}),r=P(()=>t.value.articleInfo);return{config:o,items:r}},Bc=Symbol.for("articles"),ha=()=>{const e=we(Bc);if(!e)throw new Error("useArticles() is called without provider.");return e},mv=()=>{const e=sa("article");Ke(Bc,e)},Uc=Symbol.for("encryptedArticles"),jc=()=>{const e=we(Uc);if(!e)throw new Error("useEncryptedArticles() is called without provider.");return e},gv=()=>{const e=sa("encrypted");Ke(Uc,e)},Wc=Symbol.for("slides"),Gc=()=>{const e=we(Wc);if(!e)throw new Error("useSlides() is called without provider.");return e},vv=()=>{const e=sa("slide");Ke(Wc,e)},Yc=Symbol.for("stars"),Ol=()=>{const e=we(Yc);if(!e)throw new Error("useStars() is called without provider.");return e},fv=()=>{const e=sa("star");Ke(Yc,e)},Kc=Symbol.for("timelines"),Rl=()=>{const e=we(Kc);if(!e)throw new Error("useTimelines() is called without provider.");return e},_v=()=>{const e=sa("timeline"),t=P(()=>{const i=[];return e.value.items.forEach(({info:a,path:n})=>{var c;const{year:l,month:o,day:r}=((c=Sa(a.date))==null?void 0:c.info)||{};l&&o&&r&&((!i[0]||i[0].year!==l)&&i.unshift({year:l,items:[]}),i[0].items.push({date:`${o}/${r}`,info:a,path:n}))}),{...e.value,config:i.reverse()}});Ke(Kc,t)},bv=()=>{mv(),rv(),gv(),vv(),fv(),sv(),_v()};const kv=K({name:"SocialMedia",setup(){const e=ua(),t=la(),i=P(()=>{const a=e.value.medias;return a?Object.entries(a).map(([n,l])=>({name:n,icon:ov[n],url:l})):[]});return()=>i.value.length?u("div",{class:"social-media-wrapper"},i.value.map(({name:a,icon:n,url:l})=>u("a",{class:"social-media",href:l,rel:"noopener noreferrer",target:"_blank","aria-label":a,...t.value?{}:{"data-balloon-pos":"up"},innerHTML:n}))):null}});const Cl=K({name:"BloggerInfo",setup(){const e=ua(),t=Qa(),i=ve(),a=ha(),n=ca(),l=da(),o=Rl(),r=ra(),c=P(()=>{var m;return e.value.name||((m=Gi(i.value.author)[0])==null?void 0:m.name)||t.value.title}),d=P(()=>e.value.avatar||i.value.logo),h=P(()=>i.value.blogLocales),p=P(()=>e.value.intro);return()=>u("div",{class:"blogger-info",vocab:"https://schema.org/",typeof:"Person"},[u("div",{class:"blogger",...p.value?{style:{cursor:"pointer"},"aria-label":h.value.intro,"data-balloon-pos":"down",role:"navigation",onClick:()=>r(p.value)}:{}},[d.value?u("img",{class:["blogger-avatar",{round:e.value.roundAvatar}],src:mt(d.value),property:"image",alt:"Blogger Avatar"}):null,c.value?u("div",{class:"blogger-name",property:"name"},c.value):null,e.value.description?u("div",{class:"blogger-description",innerHTML:e.value.description}):null,p.value?u("meta",{property:"url",content:mt(p.value)}):null]),u("div",{class:"num-wrapper"},[u("div",{onClick:()=>r(a.value.path)},[u("div",{class:"num"},a.value.items.length),u("div",h.value.article)]),u("div",{onClick:()=>r(n.value.path)},[u("div",{class:"num"},Object.keys(n.value.map).length),u("div",h.value.category)]),u("div",{onClick:()=>r(l.value.path)},[u("div",{class:"num"},Object.keys(l.value.map).length),u("div",h.value.tag)]),u("div",{onClick:()=>r(o.value.path)},[u("div",{class:"num"},o.value.items.length),u("div",h.value.timeline)])]),u(kv)])}}),Bn=()=>u(pe,{name:"category"},()=>u("path",{d:"M148.41 106.992h282.176c22.263 0 40.31 18.048 40.31 40.31V429.48c0 22.263-18.047 40.31-40.31 40.31H148.41c-22.263 0-40.311-18.047-40.311-40.31V147.302c0-22.263 18.048-40.31 40.311-40.31zM147.556 553.478H429.73c22.263 0 40.311 18.048 40.311 40.31v282.176c0 22.263-18.048 40.312-40.31 40.312H147.555c-22.263 0-40.311-18.049-40.311-40.312V593.79c0-22.263 18.048-40.311 40.31-40.311zM593.927 106.992h282.176c22.263 0 40.31 18.048 40.31 40.31V429.48c0 22.263-18.047 40.31-40.31 40.31H593.927c-22.263 0-40.311-18.047-40.311-40.31V147.302c0-22.263 18.048-40.31 40.31-40.31zM730.22 920.502H623.926c-40.925 0-74.22-33.388-74.22-74.425V623.992c0-41.038 33.387-74.424 74.425-74.424h222.085c41.038 0 74.424 33.226 74.424 74.067v114.233c0 10.244-8.304 18.548-18.547 18.548s-18.548-8.304-18.548-18.548V623.635c0-20.388-16.746-36.974-37.33-36.974H624.13c-20.585 0-37.331 16.747-37.331 37.33v222.086c0 20.585 16.654 37.331 37.126 37.331H730.22c10.243 0 18.547 8.304 18.547 18.547 0 10.244-8.304 18.547-18.547 18.547z"}));Bn.displayName="CategoryIcon";const Un=()=>u(pe,{name:"tag"},()=>u("path",{d:"M939.902 458.563L910.17 144.567c-1.507-16.272-14.465-29.13-30.737-30.737L565.438 84.098h-.402c-3.215 0-5.726 1.005-7.634 2.913l-470.39 470.39a10.004 10.004 0 000 14.164l365.423 365.424c1.909 1.908 4.42 2.913 7.132 2.913s5.223-1.005 7.132-2.913l470.39-470.39c2.01-2.11 3.014-5.023 2.813-8.036zm-240.067-72.121c-35.458 0-64.286-28.828-64.286-64.286s28.828-64.285 64.286-64.285 64.286 28.828 64.286 64.285-28.829 64.286-64.286 64.286z"}));Un.displayName="TagIcon";const Sl=()=>u(pe,{name:"timeline"},()=>u("path",{d:"M511.997 70.568c-243.797 0-441.429 197.633-441.429 441.435 0 243.797 197.632 441.429 441.43 441.429S953.431 755.8 953.431 512.002c0-243.796-197.637-441.434-441.435-441.434zm150.158 609.093-15.605 15.61c-8.621 8.615-22.596 8.615-31.215 0L472.197 552.126c-4.95-4.944-4.34-14.888-4.34-24.677V247.14c0-12.19 9.882-22.07 22.07-22.07h22.07c12.19 0 22.07 9.882 22.07 22.07v273.218l128.088 128.088c8.62 8.62 8.62 22.595 0 31.215zm0 0"}));Sl.displayName="TimelineIcon";const Zc=()=>u(pe,{name:"slides"},()=>u("path",{d:"M896 170.667v426.666a85.333 85.333 0 0 1-85.333 85.334h-256v61.184l192.597 115.584-43.861 73.13-148.736-89.173v95.275h-85.334v-95.318l-148.736 89.216-43.861-73.13 192.597-115.627v-61.141h-256A85.333 85.333 0 0 1 128 597.333V170.667H85.333V85.333h853.334v85.334H896zm-682.667 0v426.666h597.334V170.667H213.333zM426.667 512h-85.334V341.333h85.334V512zm128 0h-85.334V256h85.334v256zm128 0h-85.334V384h85.334v128z"}));Zc.displayName="SlideIcon";const Jc=()=>u(pe,{name:"sticky"},()=>[u("path",{d:"m381.3 733.8l-161.9 118c-5.9 4.5-13.2 6.6-20.1 6.6-8.7 0-17.7-3.4-24.3-10-12.2-12.2-13.9-31.3-3.5-45.2l144.5-195.5-113.6-112.9c-11.1-11.1-13.2-28.4-5.5-42 5.5-8.7 52.1-76.4 155.5-51 1.8 0.3 3.5 0.3 5.6 0.7 4.2 0.3 9 0.7 14.2 1.7 21.9 3.5 60.8-13.9 94.5-42.7 32.3-27.5 53.1-59.4 53.1-81.6 0-5.2 0-10.8-0.3-16-0.7-20.8-2.1-52.8 21.5-76.4 28.1-28.1 72.9-30.6 103.9-5.2 0.6 0.3 1 1 1.7 1.7 16.7 16.3 187.5 187.2 189.3 188.9 14.5 14.6 22.9 34.4 22.9 55.3 0 20.8-8 40.2-22.9 54.8-23.7 23.6-56 22.6-77.1 21.6-4.9 0-10.5-0.4-15.7-0.4-20.8 0-45.8 14.6-70.5 41.3-34.3 37.5-55.5 85.8-53.8 107.7 0.7 6.9 2.1 19.1 2.4 20.8 25 101.4-42.7 147.6-50.7 152.8-13.9 8.4-31.6 6.3-42.7-4.8l-112.1-112.2z"})]);Jc.displayName="StickyIcon";const Ma=()=>u(pe,{name:"article"},()=>u("path",{d:"M853.333 938.667H170.667A42.667 42.667 0 0 1 128 896V128a42.667 42.667 0 0 1 42.667-42.667h682.666A42.667 42.667 0 0 1 896 128v768a42.667 42.667 0 0 1-42.667 42.667zm-42.666-85.334V170.667H213.333v682.666h597.334zM298.667 256h170.666v170.667H298.667V256zm0 256h426.666v85.333H298.667V512zm0 170.667h426.666V768H298.667v-85.333zm256-384h170.666V384H554.667v-85.333z"}));Ma.displayName="ArticleIcon";const Qc=()=>u(pe,{name:"book"},()=>u("path",{d:"M256 853.333h426.667A85.333 85.333 0 0 0 768 768V256a85.333 85.333 0 0 0-85.333-85.333H469.333a42.667 42.667 0 0 1 0-85.334h213.334A170.667 170.667 0 0 1 853.333 256v512a170.667 170.667 0 0 1-170.666 170.667H213.333A42.667 42.667 0 0 1 170.667 896V128a42.667 42.667 0 0 1 42.666-42.667h128A42.667 42.667 0 0 1 384 128v304.256l61.653-41.088a42.667 42.667 0 0 1 47.36 0l61.654 41.045V256A42.667 42.667 0 0 1 640 256v256a42.667 42.667 0 0 1-66.347 35.499l-104.32-69.547-104.32 69.547A42.667 42.667 0 0 1 298.667 512V170.667H256v682.666z"}));Qc.displayName="BookIcon";const Xc=()=>u(pe,{name:"link"},()=>u("path",{d:"M460.8 584.533c17.067 17.067 17.067 42.667 0 59.734-17.067 17.066-42.667 17.066-59.733 0-85.334-85.334-85.334-217.6 0-302.934L554.667 192C640 110.933 776.533 110.933 857.6 196.267c81.067 81.066 81.067 213.333 0 294.4l-68.267 64c0-34.134-4.266-68.267-17.066-102.4l21.333-21.334c51.2-46.933 55.467-128 4.267-179.2s-128-55.466-179.2-4.266c-4.267 0-4.267 4.266-4.267 4.266L465.067 401.067c-51.2 51.2-51.2 132.266-4.267 183.466m123.733-183.466C601.6 384 627.2 384 644.267 401.067c85.333 85.333 85.333 217.6 0 302.933l-153.6 149.333C405.333 934.4 268.8 934.4 187.733 849.067c-81.066-81.067-81.066-213.334 0-294.4l68.267-64c0 34.133 4.267 72.533 17.067 102.4L251.733 614.4C204.8 665.6 204.8 746.667 256 793.6c51.2 46.933 123.733 46.933 174.933 0l149.334-149.333c51.2-51.2 51.2-128 0-179.2-12.8-17.067-17.067-46.934 4.266-64z"}));Xc.displayName="LinkIcon";const eu=()=>u(pe,{name:"project"},()=>u("path",{d:"M987.456 425.152H864V295.296a36.48 36.48 0 0 0-36.544-36.544h-360l-134.08-128.256A9.344 9.344 0 0 0 327.04 128H36.48A36.48 36.48 0 0 0 0 164.544v676.608a36.48 36.48 0 0 0 36.544 36.544h797.76a36.672 36.672 0 0 0 33.92-22.848L1021.44 475.52a36.48 36.48 0 0 0-33.92-50.304zM82.304 210.304h215.424l136.64 130.752h347.328v84.096H198.848A36.672 36.672 0 0 0 164.928 448L82.304 652.8V210.304zM808.32 795.456H108.544l118.08-292.608h699.904L808.32 795.52z"}));eu.displayName="ProjectIcon";const tu=()=>u(pe,{name:"friend"},()=>u("path",{d:"M860.16 213.333A268.373 268.373 0 0 0 512 186.027a267.52 267.52 0 0 0-348.16 404.48L428.8 855.893a118.613 118.613 0 0 0 166.4 0l264.96-265.386a267.52 267.52 0 0 0 0-377.174zM800 531.627l-264.96 264.96a32.427 32.427 0 0 1-46.08 0L224 530.347a183.04 183.04 0 0 1 0-256 182.187 182.187 0 0 1 256 0 42.667 42.667 0 0 0 60.587 0 182.187 182.187 0 0 1 256 0 183.04 183.04 0 0 1 3.413 256z"}));tu.displayName="FriendIcon";const jn=()=>u(pe,{name:"slide-down"},()=>u("path",{d:"M108.775 312.23c13.553 0 27.106 3.734 39.153 11.806l375.205 250.338 363.641-252.808c32.587-21.624 76.499-12.83 98.123 19.757 21.685 32.467 12.95 76.56-19.576 98.184l-402.854 278.89c-23.733 15.901-54.694 15.962-78.547.12L69.501 442.097c-32.647-21.685-41.441-65.777-19.817-98.304 13.734-20.54 36.201-31.563 59.09-31.563Z"}));jn.displayName="SlideDownIcon";const iu=()=>u("svg",{xmlns:"http://www.w3.org/2000/svg","xmlns:xlink":"http://www.w3.org/1999/xlink",class:"empty-icon",viewBox:"0 0 1024 1024",innerHTML:''});iu.displayName="EmptyIcon";const au=()=>u(pe,{name:"lock"},()=>u("path",{d:"M787.168 952.268H236.832c-30.395 0-55.033-24.638-55.033-55.033V429.45c0-30.395 24.638-55.034 55.033-55.034h82.55V264.35c0-106.38 86.238-192.618 192.618-192.618S704.618 157.97 704.618 264.35v110.066h82.55c30.395 0 55.033 24.639 55.033 55.034v467.785c0 30.395-24.639 55.033-55.033 55.033zM484.483 672.046v115.122h55.034V672.046c31.99-11.373 55.033-41.605 55.033-77.496 0-45.592-36.958-82.55-82.55-82.55s-82.55 36.958-82.55 82.55c0 35.89 23.042 66.123 55.033 77.496zM622.067 264.35c0-60.788-49.28-110.067-110.067-110.067s-110.067 49.28-110.067 110.067v110.066h220.135V264.35z"}));au.displayName="LockIcon";const yv=K({name:"ArticleItem",props:{info:{type:Object,required:!0},path:{type:String,required:!0}},setup(e){const t=Ji(e,"info"),{config:i,items:a}=pv(t);return()=>u("article",{class:"article",vocab:"https://schema.org/",typeof:"Article"},u(Qe,{to:e.path},()=>[t.value.sticky?u(Jc):null,u("header",{class:"title"},[t.value.isEncrypted?u(au):null,t.value.type==="slide"?u(Zc):null,u("span",{property:"headline"},t.value.title),t.value.cover?u("meta",{property:"image",content:mt(t.value.cover)}):null]),t.value.excerpt?u("div",{class:"excerpt",innerHTML:t.value.excerpt}):null,u("hr",{class:"hr"}),u(mc,{config:at(i),...a.value?{items:a.value}:{}})]))}});const wv=K({name:"Pagination",props:{total:{type:Number,default:10},perPage:{type:Number,default:10},currentPage:{type:Number,default:1}},emits:["updateCurrentPage"],setup(e,{emit:t}){let i;const a=ve(),n=ne(""),l=P(()=>a.value.paginationLocales),o=P(()=>Math.ceil(e.total/e.perPage)),r=P(()=>Boolean(o.value)&&o.value!==1),c=P(()=>o.value<7?!1:e.currentPage>4),d=P(()=>o.value<7?!1:e.currentPage{const{currentPage:f}=e;let y=1,_=o.value;const x=[];o.value>=7&&(f<=4&&f4&&f>=o.value-3?(_=o.value,y=o.value-4):o.value>7&&(y=f-2,_=f+2));for(let b=y;b<=_;b++)x.push(b);return x}),p=f=>t("updateCurrentPage",f),m=f=>{const y=parseInt(f);y<=o.value&&y>0?p(y):i.pop(`${l.value.errorText.replace(/\$page/g,o.value.toString())}`)};return Se(()=>{i=new Ts}),()=>u("div",{class:"pagination-wrapper"},r.value?u("div",{class:"pagination-list"},[u("div",{class:"page-number"},[e.currentPage>1?u("div",{class:"prev",role:"navigation",unselectable:"on",onClick:()=>p(e.currentPage-1)},l.value.prev):null,...c.value?[u("div",{role:"navigation",onClick:()=>p(1)},1),u("div",{class:"ellipsis"},"...")]:[],...h.value.map(f=>u("div",{key:f,class:{active:e.currentPage===f},role:"navigation",onClick:()=>p(f)},f)),...d.value?[u("div",{class:"ellipsis"},"..."),u("div",{role:"navigation",onClick:()=>p(o.value)},o.value)]:[],e.currentPagep(e.currentPage+1)},l.value.next):null]),u("div",{class:"navigate-wrapper"},[u("label",{for:"navigation-text"},`${l.value.navigate}: `),u("input",{id:"navigation-text",value:n.value,onInput:({target:f})=>{n.value=f.value},onKeydown:f=>{f.key==="Enter"&&(f.preventDefault(),m(n.value))}}),u("button",{class:"navigate",role:"navigation",title:l.value.action,onClick:()=>m(n.value)},l.value.action)])]):[])}}),Ce=K({name:"DropTransition",components:{Transition:Dt,TransitionGroup:wo},props:{type:{type:String,default:"single"},delay:{type:Number,default:0},duration:{type:Number,default:.25},appear:Boolean},setup(e,{slots:t}){const i=n=>{n.style.transition=`transform ${e.duration}s ease-in-out ${e.delay}s, opacity ${e.duration}s ease-in-out ${e.delay}s`,n.style.transform="translateY(-20px)",n.style.opacity="0"},a=n=>{n.style.transform="translateY(0)",n.style.opacity="1"};return()=>u(e.type==="single"?Dt:wo,{name:"drop",appear:e.appear,onAppear:i,onAfterAppear:a,onEnter:i,onAfterEnter:a,onBeforeLeave:i},()=>{var n;return(n=t.default)==null?void 0:n.call(t)})}});const nu=K({name:"ArticleList",props:{items:{type:Array,default:()=>[]}},setup(e){const t=xe(),i=Xe(),a=ua(),n=ne(1),l=P(()=>a.value.articlePerPage||10),o=P(()=>e.items.slice((n.value-1)*l.value,n.value*l.value)),r=c=>{n.value=c;const d={...t.query};d.page===c.toString()||c===1&&!d.page||(c===1?delete d.page:d.page=c.toString(),i.push({path:t.path,query:d}))};return De(n,()=>{const c=document.querySelector("#article-list").getBoundingClientRect().top+window.scrollY;setTimeout(()=>{window.scrollTo(0,c)},100)}),Se(()=>{const{page:c}=t.query;r(c?Number(c):1)}),()=>u("div",{id:"article-list",class:"article-wrapper"},o.value.length?[...o.value.map(({info:c,path:d},h)=>u(Ce,{appear:!0,delay:h*.04},()=>u(yv,{key:d,info:c,path:d}))),u(wv,{currentPage:n.value,perPage:l.value,total:e.items.length,onUpdateCurrentPage:r})]:u(iu))}}),xv="/assets/hero.197a9d2d.jpg";const Tv=K({name:"BlogHero",setup(e,{slots:t}){const i=Wv(),a=Pe(),n=ne(null),l=P(()=>a.value.heroImage||null),o=P(()=>a.value.heroFullScreen||!1),r=P(()=>({...{maxHeight:"180px",margin:a.value.heroText===!1?"6rem auto 1.5rem":"1rem auto"},...a.value.heroImageStyle})),c=P(()=>{var h;return a.value.bgImage?mt(a.value.bgImage):(h=a.value.bgImage)!=null?h:xv}),d=P(()=>({...{height:"350px",textAlign:"center",overflow:"hidden"},...a.value.bgImageStyle}));return()=>{var h;return a.value.hero!==!1?u("div",{ref:n,class:["blog-hero",{fullscreen:o.value}],style:d.value},[c.value?u("div",{class:"mask",style:{background:`url(${c.value}) center/cover no-repeat`}}):null,((h=t.heroImage)==null?void 0:h.call(t))||u(Ce,{appear:!0,delay:.04},()=>l.value?u("img",{class:"hero-image",style:r.value,src:mt(l.value),alt:a.value.heroAlt||"hero image"}):null),u(Ce,{appear:!0,delay:.08},()=>a.value.heroText!==!1?u("h1",a.value.heroText||i.value):null),u(Ce,{appear:!0,delay:.12},()=>a.value.tagline?u("p",{class:"description",innerHTML:a.value.tagline}):null),o.value?u("button",{class:"slide-down-button",onClick:()=>{window.scrollTo({top:n.value.clientHeight,behavior:"smooth"})}},[u(jn),u(jn)]):null]):null}}});const lu=K({name:"CategoryList",setup(){const e=xe(),t=ca();return()=>u("ul",{class:"category-list-wrapper"},Object.entries(t.value.map).map(([i,{path:a,items:n}])=>u("li",{class:["category",`category${Wa(i,9)}`,{active:a===e.path}]},u(Qe,{to:a},()=>[i,u("span",{class:"category-num"},n.length)]))))}});const ou=K({name:"TagList",setup(){const e=Pe(),t=da(),i=P(()=>Object.entries(t.value.map).map(([n,{path:l}])=>({name:n,path:l}))),a=n=>{var l;return n===((l=e.value.blog)==null?void 0:l.name)};return()=>u("ul",{class:"tag-list-wrapper"},i.value.map(({name:n,path:l})=>u("li",{class:["tag",`tag${Wa(n,9)}`,{active:a(n)}]},u(Qe,{to:l},()=>u("div",{class:"tag-name"},n)))))}});const Ev=K({name:"TimelineList",setup(){const e=ve(),t=Rl(),i=ra(),a=P(()=>e.value.blogLocales.timeline);return()=>u("div",{class:"timeline-list-wrapper"},[u("div",{class:"timeline-list-title",onClick:()=>i(t.value.path)},[u(Sl),u("span",{class:"num"},t.value.items.length),a.value]),u("hr"),u("div",{class:"timeline-content"},u("ul",{class:"timeline-list"},t.value.config.map(({year:n,items:l},o)=>u(Ce,{appear:!0,delay:.08*(o+1)},()=>u("li",[u("h3",{class:"timeline-year"},n),u("ul",{class:"timeline-year-wrapper"},l.map(({date:r,info:c,path:d})=>u("li",{class:"timeline-item"},[u("span",{class:"timeline-date"},r),u(Qe,{class:"timeline-title",to:d},()=>c.title)])))])))))])}});const ru=K({name:"InfoList",setup(){const e=ve(),t=ha(),i=ca(),a=P(()=>Object.keys(i.value.map).length),n=Ol(),l=da(),o=P(()=>Object.keys(l.value.map).length),r=ra(),c=ne("article"),d=P(()=>e.value.blogLocales),h=[["article",Ma],["category",Bn],["tag",Un],["timeline",Sl]];return()=>u("div",{class:"blog-info-list"},[u("div",{class:"blog-type-wrapper"},h.map(([p,m])=>u("button",{class:"blog-type-button",onClick:()=>{c.value=p}},u("div",{class:["icon-wapper",{active:c.value===p}],"aria-label":d.value[p],"data-balloon-pos":"up"},u(m))))),c.value==="article"?u(Ce,()=>[u("div",{class:"sticky-article-wrapper"},[u("div",{class:"title",onClick:()=>r(t.value.path)},[u(Ma),u("span",{class:"num"},t.value.items.length),d.value.article]),u("hr"),u("ul",{class:"sticky-article-list"},n.value.items.map(({info:p,path:m},f)=>u(Ce,{appear:!0,delay:.08*(f+1)},()=>u("li",{class:"sticky-article",onClick:()=>r(m)},p.title))))])]):null,c.value==="category"?u(Ce,()=>[u("div",{class:"category-wrapper"},[a.value?u("div",{class:"title",onClick:()=>r(i.value.path)},[u(Bn),u("span",{class:"num"},a.value),d.value.category]):null,u("hr"),u(Ce,{delay:.04},()=>u(lu))])]):null,c.value==="tag"?u(Ce,()=>[u("div",{class:"tag-wrapper"},[o.value?u("div",{class:"title",onClick:()=>r(l.value.path)},[u(Un),u("span",{class:"num"},o.value),d.value.tag]):null,u("hr"),u(Ce,{delay:.04},()=>u(ou))])]):null,c.value==="timeline"?u(Ce,()=>u(Ev)):null])}});const Vl=()=>u("aside",{class:"blog-info-wrapper"},[u(Ce,()=>u(Cl)),u(Ce,{delay:.04},()=>u(ru))]);Vl.displayName="InfoPanel";const Pv=["link","article","book","project","friend"],Lv=K({name:"ProjectPanel",components:{ArticleIcon:Ma,BookIcon:Qc,FriendIcon:tu,LinkIcon:Xc,ProjectIcon:eu},setup(){const e=Pe(),t=la(),i=ra(),a=(n="",l="icon")=>Pv.includes(n)?u(Me(`${n}-icon`)):n.match(/^https?:\/\//)?u("img",{src:n,alt:l,class:"image"}):n.startsWith("/")?u("img",{src:mt(n),alt:l,class:"image"}):u(Je,{icon:n});return()=>{var n;return(n=e.value.projects)!=null&&n.length?u("div",{class:"project-panel"},e.value.projects.map(({icon:l,link:o,name:r,desc:c},d)=>u("div",{class:["project",{[`project${d%9}`]:!t.value}],onClick:()=>i(o)},[a(l,r),u("div",{class:"name"},r),u("div",{class:"desc"},c)]))):null}}});const su=K({name:"BlogHome",setup(){const e=ha();return()=>u("div",{class:"page blog"},[u(Tv),u("div",{class:"blog-page-wrapper"},[u("main",{class:"blog-home",id:"main-content"},[u(Ce,{appear:!0,delay:.16},()=>u(Lv)),u(Ce,{appear:!0,delay:.24},()=>u(nu,{items:e.value.items}))]),u(Ce,{appear:!0,delay:.16},()=>u(Vl))]),u(Ce,{appear:!0,delay:.28},()=>u(Ja))])}});const Dv=K({name:"ArticleType",setup(){const e=ve(),t=xe(),i=ha(),a=jc(),n=Gc(),l=Ol(),o=P(()=>{const r=e.value.blogLocales;return[{text:r.all,path:i.value.path},{text:r.star,path:l.value.path},{text:r.slides,path:n.value.path},{text:r.encrypt,path:a.value.path}]});return()=>u("ul",{class:"article-type-wrapper"},o.value.map(r=>u("li",{class:["article-type",{active:r.path===t.path}]},u(Qe,{to:r.path},()=>r.text))))}});const Av=K({name:"TimelineItems",setup(){const e=ua(),t=ve(),i=Rl(),a=P(()=>e.value.timeline||t.value.blogLocales.timelineTitle),n=P(()=>i.value.config.map(({year:l})=>({title:l.toString(),level:2,slug:l.toString(),children:[]})));return()=>u("div",{class:"timeline-wrapper"},u("ul",{class:"timeline-content"},[u(Ce,()=>u("li",{class:"motto"},a.value)),u(vc,{items:n.value}),...i.value.config.map(({year:l,items:o},r)=>u(Ce,{appear:!0,delay:.08*(r+1),type:"group"},()=>[u("h3",{key:"title",id:l,class:"timeline-year-title"},u("span",l)),u("li",{key:"content",class:"timeline-year-list"},[u("ul",{class:"timeline-year-wrapper"},o.map(({date:c,info:d,path:h})=>u("li",{class:"timeline-item"},[u("span",{class:"timeline-date"},c),u(Qe,{class:"timeline-title",to:h},()=>d.title)])))])]))]))}});const cu=K({name:"BlogPage",components:{ArticleType:Dv,CategoryList:lu,TagList:ou},setup(){const e=Pe(),t=xe(),i=ha(),a=ca(),n=jc(),l=Gc(),o=Ol(),r=da(),c=P(()=>{const{key:h}=e.value.blog||{};return h==="category"?"CategoryList":h==="tag"?"TagList":h==="timeline"?"":"ArticleType"}),d=P(()=>{const{name:h="",key:p=""}=e.value.blog||{};return p==="encrypted"?n.value.items:p==="star"?o.value.items:p==="slide"?l.value.items:p==="timeline"?[]:p==="category"?h?a.value.map[h].items:[]:p==="tag"?h?r.value.map[h].items:[]:i.value.items});return()=>u("div",{class:"page blog"},u("div",{class:"blog-page-wrapper"},[u("main",{class:"blog-main",id:"main-content"},[u(Ce,()=>c.value?u(Me(c.value)):null),u(Ce,{appear:!0,delay:.24},()=>{var h;return((h=e.value.blog)==null?void 0:h.key)==="timeline"?u(Av):u(nu,{key:t.path,items:d.value})})]),u(Ce,{delay:.16},()=>u(Vl))]))}});const Iv=K({name:"Blog",setup(){const e=Pe(),t=oa();return()=>[u(Il),u(Me("CommonWrapper"),{sidebar:!1},{default:()=>e.value.home?u(su):u(cu),navScreenBottom:()=>u(Cl),...t.value?{sidebar:()=>u(ru)}:{}})]}}),Ov=kt({enhance:({app:e,router:t})=>{const{scrollBehavior:i}=t.options;t.options.scrollBehavior=async(...a)=>(await Qs().wait(),i(...a)),F0(e),e.component("CommonWrapper",t0),e.component("HomePage",v0),e.component("NormalPage",H0),e.component("Navbar",X0),e.component("Sidebar",iv),e.component("BloggerInfo",Cl),e.component("BlogHome",su),e.component("BlogPage",cu)},setup:()=>{z0(),e0(),bv()},layouts:{Layout:nv,NotFound:lv,Blog:Iv}}),Rv=(e,t)=>t.some(i=>{if(ye(i))return i===e.key;const{key:a,ctrl:n=!1,shift:l=!1,alt:o=!1}=i;return a===e.key&&n===e.ctrlKey&&l===e.shiftKey&&o===e.altKey}),Cv=/[^\x00-\x7F]/,Sv=e=>e.split(/\s+/g).map(t=>t.trim()).filter(t=>!!t),lr=e=>e.replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&"),or=(e,t)=>{const i=t.join(" "),a=Sv(e);if(Cv.test(e))return a.some(o=>i.toLowerCase().indexOf(o)>-1);const n=e.endsWith(" ");return new RegExp(a.map((o,r)=>a.length===r+1&&!n?`(?=.*\\b${lr(o)})`:`(?=.*\\b${lr(o)}\\b)`).join("")+".+","gi").test(i)},Vv=({input:e,hotKeys:t})=>{if(t.value.length===0)return;const i=a=>{!e.value||Rv(a,t.value)&&!e.value.contains(a.target)&&(a.preventDefault(),e.value.focus())};Se(()=>{document.addEventListener("keydown",i)}),xi(()=>{document.removeEventListener("keydown",i)})},Mv=[{title:"About Us",headers:[{level:2,title:"The team",slug:"the-team",link:"#the-team",children:[]},{level:2,title:"The goals",slug:"the-goals",link:"#the-goals",children:[]}],path:"/about.html",pathLocale:"/",extraFields:[]},{title:"Community areas",headers:[],path:"/community.html",pathLocale:"/",extraFields:[]},{title:"Donate to Pulsar",headers:[],path:"/donate.html",pathLocale:"/",extraFields:[]},{title:"Pulsar Downloads",headers:[{level:2,title:"Rolling Release",slug:"rolling-release",link:"#rolling-release",children:[]},{level:2,title:"Regular Releases",slug:"regular-releases",link:"#regular-releases",children:[]},{level:2,title:"Manual download",slug:"manual-download",link:"#manual-download",children:[]}],path:"/download.html",pathLocale:"/",extraFields:[]},{title:"Home",headers:[{level:2,title:"Notices",slug:"notices",link:"#notices",children:[{level:3,title:"Downloads and Releases",slug:"downloads-and-releases",link:"#downloads-and-releases",children:[]},{level:3,title:"App Updates",slug:"app-updates",link:"#app-updates",children:[]},{level:3,title:"Blog",slug:"blog",link:"#blog",children:[]},{level:3,title:"Packages",slug:"packages",link:"#packages",children:[]},{level:3,title:"Support and Community",slug:"support-and-community",link:"#support-and-community",children:[]}]}],path:"/",pathLocale:"/",extraFields:[]},{title:"Pulsar Repositories",headers:[{level:2,title:"Editor",slug:"editor",link:"#editor",children:[]},{level:2,title:"Websites and services",slug:"websites-and-services",link:"#websites-and-services",children:[]}],path:"/repos.html",pathLocale:"/",extraFields:[]},{title:"Example post",headers:[],path:"/blog/20221112-Daeraxa-ExamplePost.html",pathLocale:"/",extraFields:[]},{title:"A Sunset and the Misadventures of a Backend",headers:[{level:3,title:"The Archival",slug:"the-archival",link:"#the-archival",children:[]},{level:3,title:"What to do with this Data",slug:"what-to-do-with-this-data",link:"#what-to-do-with-this-data",children:[]},{level:3,title:"The Misadventures Part",slug:"the-misadventures-part",link:"#the-misadventures-part",children:[]},{level:3,title:"The Good News",slug:"the-good-news",link:"#the-good-news",children:[]}],path:"/blog/20221127-confused-Techie-SunsetMisadventureBackend.html",pathLocale:"/",extraFields:[]},{title:"Featured on DistroTube!",headers:[],path:"/blog/20221208-Daeraxa-DistroTubeVideo.html",pathLocale:"/",extraFields:[]},{title:"Our First Release!",headers:[{level:3,title:"Pulsar",slug:"pulsar",link:"#pulsar",children:[]},{level:3,title:"ppm",slug:"ppm",link:"#ppm",children:[]},{level:3,title:"autocomplete-html",slug:"autocomplete-html",link:"#autocomplete-html",children:[]},{level:3,title:"settings-view",slug:"settings-view",link:"#settings-view",children:[]},{level:3,title:"snippets",slug:"snippets",link:"#snippets",children:[]},{level:3,title:"background-tips",slug:"background-tips",link:"#background-tips",children:[]}],path:"/blog/20221215-confused-Techie-v1.100.1-beta.html",pathLocale:"/",extraFields:[]},{title:"Our Second Beta!",headers:[{level:3,title:"Pulsar",slug:"pulsar",link:"#pulsar",children:[]},{level:3,title:"ppm",slug:"ppm",link:"#ppm",children:[]}],path:"/blog/20230114-confused-Techie-v1.101.0-beta.html",pathLocale:"/",extraFields:[]},{title:"Community Update",headers:[{level:2,title:"Tree-sitter",slug:"tree-sitter",link:"#tree-sitter",children:[]},{level:2,title:"Package repository",slug:"package-repository",link:"#package-repository",children:[]},{level:2,title:"Donations and binary signing",slug:"donations-and-binary-signing",link:"#donations-and-binary-signing",children:[]},{level:2,title:"Matrix space",slug:"matrix-space",link:"#matrix-space",children:[]}],path:"/blog/20230201-Daeraxa-FebUpdate.html",pathLocale:"/",extraFields:[]},{title:"Tales of Tree-Sitter part 1: the start of a tale",headers:[{level:2,title:"Current Pulsar implementation",slug:"current-pulsar-implementation",link:"#current-pulsar-implementation",children:[]},{level:2,title:"Modernizing the current implementation",slug:"modernizing-the-current-implementation",link:"#modernizing-the-current-implementation",children:[]}],path:"/blog/20230209-mauricioszabo-tree-sitter-part-1.html",pathLocale:"/",extraFields:[]},{title:"New Regular Release (v1.102.0)",headers:[{level:3,title:"Pulsar",slug:"pulsar",link:"#pulsar",children:[]},{level:3,title:"ppm",slug:"ppm",link:"#ppm",children:[]},{level:3,title:"github",slug:"github",link:"#github",children:[]}],path:"/blog/20230215-Daeraxa-v1.102.0.html",pathLocale:"/",extraFields:[]},{title:"Changes to our release strategy",headers:[{level:2,title:"Rolling Release",slug:"rolling-release",link:"#rolling-release",children:[]},{level:2,title:"Regular Releases",slug:"regular-releases",link:"#regular-releases",children:[]},{level:2,title:"What can I expect going forwards?",slug:"what-can-i-expect-going-forwards",link:"#what-can-i-expect-going-forwards",children:[]}],path:"/blog/20230216-Daeraxa-ReleaseStrategyUpdate.html",pathLocale:"/",extraFields:[]},{title:"Survey: How did you hear about us?",headers:[],path:"/blog/20230227-Daeraxa-Survey1.html",pathLocale:"/",extraFields:[]},{title:"Community Update",headers:[{level:2,title:"Community Spotlight - HTML Tim on YouTube",slug:"community-spotlight-html-tim-on-youtube",link:"#community-spotlight-html-tim-on-youtube",children:[]},{level:2,title:"macOS Binary Signing",slug:"macos-binary-signing",link:"#macos-binary-signing",children:[]},{level:2,title:"Tree-sitter Modernization",slug:"tree-sitter-modernization",link:"#tree-sitter-modernization",children:[]},{level:2,title:"Autocomplete CSS/HTML Automatic Updates",slug:"autocomplete-css-html-automatic-updates",link:"#autocomplete-css-html-automatic-updates",children:[]},{level:2,title:"Backend Version Updates",slug:"backend-version-updates",link:"#backend-version-updates",children:[]},{level:2,title:"Snippets Package Updates",slug:"snippets-package-updates",link:"#snippets-package-updates",children:[]},{level:2,title:"action-pulsar-dependency Stabilization Updates",slug:"action-pulsar-dependency-stabilization-updates",link:"#action-pulsar-dependency-stabilization-updates",children:[]}],path:"/blog/20230301-Daeraxa-MarUpdate.html",pathLocale:"/",extraFields:[]},{title:"It's that time again, Pulsar 1.103.0 is available now!",headers:[{level:3,title:"Pulsar",slug:"pulsar",link:"#pulsar",children:[]},{level:3,title:"Snippets",slug:"snippets",link:"#snippets",children:[]},{level:3,title:"Github",slug:"github",link:"#github",children:[]},{level:3,title:"PPM",slug:"ppm",link:"#ppm",children:[]}],path:"/blog/20230315-Daeraxa-v1.103.0.html",pathLocale:"/",extraFields:[]},{title:"How 'license: none' Deleted Packages",headers:[{level:2,title:"Licenses",slug:"licenses",link:"#licenses",children:[]},{level:2,title:"Packages on Pulsar",slug:"packages-on-pulsar",link:"#packages-on-pulsar",children:[]},{level:2,title:"Why This Matters",slug:"why-this-matters",link:"#why-this-matters",children:[]},{level:2,title:"How did we Handle This",slug:"how-did-we-handle-this",link:"#how-did-we-handle-this",children:[]},{level:2,title:"The Actual Hard Part",slug:"the-actual-hard-part",link:"#the-actual-hard-part",children:[]},{level:2,title:"Respecting A License",slug:"respecting-a-license",link:"#respecting-a-license",children:[]}],path:"/blog/20230319-confused-Techie-HowLicenseNoneDeletedPackages.html",pathLocale:"/",extraFields:[]},{title:"Survey Results: How did you hear about us?",headers:[{level:2,title:"How did you hear about us?",slug:"how-did-you-hear-about-us",link:"#how-did-you-hear-about-us",children:[]},{level:2,title:"Which community area do you check for updates?",slug:"which-community-area-do-you-check-for-updates",link:"#which-community-area-do-you-check-for-updates",children:[]},{level:2,title:"Other comments on social media presence",slug:"other-comments-on-social-media-presence",link:"#other-comments-on-social-media-presence",children:[]},{level:2,title:"Summary",slug:"summary",link:"#summary",children:[]}],path:"/blog/20230326-Daeraxa-Survey1-Results.html",pathLocale:"/",extraFields:[]},{title:"Community Update",headers:[{level:2,title:"i18n (internationalization) Efforts",slug:"i18n-internationalization-efforts",link:"#i18n-internationalization-efforts",children:[]},{level:2,title:"Tree-sitter Modernization",slug:"tree-sitter-modernization",link:"#tree-sitter-modernization",children:[]},{level:2,title:"TextMate Grammar Library & Superstring Migration to WASM",slug:"textmate-grammar-library-superstring-migration-to-wasm",link:"#textmate-grammar-library-superstring-migration-to-wasm",children:[]},{level:2,title:"Package Badges",slug:"package-badges",link:"#package-badges",children:[]},{level:2,title:"Package Service Filtering",slug:"package-service-filtering",link:"#package-service-filtering",children:[]},{level:2,title:"Bundling ppm Within Pulsar",slug:"bundling-ppm-within-pulsar",link:"#bundling-ppm-within-pulsar",children:[]},{level:2,title:"Chocolatey",slug:"chocolatey",link:"#chocolatey",children:[]}],path:"/blog/20230401-Daeraxa-AprUpdate.html",pathLocale:"/",extraFields:[]},{title:"Meet PON (Pulsar Object Notation)",headers:[],path:"/blog/20230401-confused-Techie-PON.html",pathLocale:"/",extraFields:[]},{title:"Get Ready for another fantastically essential release, Pulsar 1.104.0 is now available!",headers:[{level:3,title:"Pulsar",slug:"pulsar",link:"#pulsar",children:[]},{level:3,title:"second-mate",slug:"second-mate",link:"#second-mate",children:[]},{level:3,title:"autosave",slug:"autosave",link:"#autosave",children:[]},{level:3,title:"bracket-matcher",slug:"bracket-matcher",link:"#bracket-matcher",children:[]},{level:3,title:"timecop",slug:"timecop",link:"#timecop",children:[]},{level:3,title:"keybinding-resolver",slug:"keybinding-resolver",link:"#keybinding-resolver",children:[]}],path:"/blog/20230418-Daeraxa-v1.104.0.html",pathLocale:"/",extraFields:[]},{title:"Survey: Project Feedback",headers:[],path:"/blog/20230430-Daeraxa-Survey2.html",pathLocale:"/",extraFields:[]},{title:"Community Update",headers:[{level:2,title:"Octicons iconography update",slug:"octicons-iconography-update",link:"#octicons-iconography-update",children:[]},{level:2,title:"Symbols view improvements",slug:"symbols-view-improvements",link:"#symbols-view-improvements",children:[]},{level:2,title:"CI Testing speed upgrade",slug:"ci-testing-speed-upgrade",link:"#ci-testing-speed-upgrade",children:[]},{level:2,title:"Backend webhook",slug:"backend-webhook",link:"#backend-webhook",children:[]},{level:2,title:"Package website readme links overhaul",slug:"package-website-readme-links-overhaul",link:"#package-website-readme-links-overhaul",children:[]},{level:2,title:"Decoupling http handling for the package backend",slug:"decoupling-http-handling-for-the-package-backend",link:"#decoupling-http-handling-for-the-package-backend",children:[]}],path:"/blog/20230501-Daeraxa-MayUpdate.html",pathLocale:"/",extraFields:[]},{title:"Welcome to a rad new release, Pulsar 1.105.0 is available now!",headers:[{level:3,title:"Pulsar",slug:"pulsar",link:"#pulsar",children:[]},{level:3,title:"notifications",slug:"notifications",link:"#notifications",children:[]}],path:"/blog/20230516-Daeraxa-v1.105.0.html",pathLocale:"/",extraFields:[]},{title:"Survey Results: Project feedback",headers:[{level:2,title:"Iconography",slug:"iconography",link:"#iconography",children:[{level:3,title:"Comments",slug:"comments",link:"#comments",children:[]}]},{level:2,title:"Documentation Site Upgrade",slug:"documentation-site-upgrade",link:"#documentation-site-upgrade",children:[{level:3,title:"API Docs",slug:"api-docs",link:"#api-docs",children:[]},{level:3,title:"Layout",slug:"layout",link:"#layout",children:[]},{level:3,title:"Videos",slug:"videos",link:"#videos",children:[]}]},{level:2,title:"Community package publishing",slug:"community-package-publishing",link:"#community-package-publishing",children:[]},{level:2,title:"Summary",slug:"summary",link:"#summary",children:[]}],path:"/blog/20230525-Daeraxa-Survey2-Results.html",pathLocale:"/",extraFields:[]},{title:"Community Update",headers:[{level:2,title:"Community package feature detection",slug:"community-package-feature-detection",link:"#community-package-feature-detection",children:[]},{level:2,title:"Tree-sitter updates are live!",slug:"tree-sitter-updates-are-live",link:"#tree-sitter-updates-are-live",children:[]},{level:2,title:"GitHub Discussions reorganisation",slug:"github-discussions-reorganisation",link:"#github-discussions-reorganisation",children:[]},{level:2,title:"Community spotlight",slug:"community-spotlight",link:"#community-spotlight",children:[]}],path:"/blog/20230601-Daeraxa-JuneUpdate.html",pathLocale:"/",extraFields:[]},{title:"2k Stars!",headers:[],path:"/blog/20230610-Daeraxa-2kStars.html",pathLocale:"/",extraFields:[]},{title:"Pulsar v1.106.0: A Focus on Grammars",headers:[{level:2,title:"What is new in 1.106.0?",slug:"what-is-new-in-1-106-0",link:"#what-is-new-in-1-106-0",children:[{level:3,title:"Changelog",slug:"changelog",link:"#changelog",children:[]},{level:3,title:"Pulsar",slug:"pulsar",link:"#pulsar",children:[]},{level:3,title:"github",slug:"github",link:"#github",children:[]}]}],path:"/blog/20230616-Daeraxa-v1.106.0.html",pathLocale:"/",extraFields:[]},{title:"Community Update",headers:[{level:2,title:"ARM builds problem",slug:"arm-builds-problem",link:"#arm-builds-problem",children:[]},{level:2,title:"Subreddit closure/reopening and possible Lemmy community",slug:"subreddit-closure-reopening-and-possible-lemmy-community",link:"#subreddit-closure-reopening-and-possible-lemmy-community",children:[]},{level:2,title:"Antivirus es5-ext issue",slug:"antivirus-es5-ext-issue",link:"#antivirus-es5-ext-issue",children:[]},{level:2,title:"Pulsar available on deb-get",slug:"pulsar-available-on-deb-get",link:"#pulsar-available-on-deb-get",children:[]},{level:2,title:"Tree-sitter migration of TOML grammar",slug:"tree-sitter-migration-of-toml-grammar",link:"#tree-sitter-migration-of-toml-grammar",children:[]},{level:2,title:"less-cache package update",slug:"less-cache-package-update",link:"#less-cache-package-update",children:[]}],path:"/blog/20230701-Daeraxa-JulyUpdate.html",pathLocale:"/",extraFields:[]},{title:"Fresh off the CI press: Pulsar 1.107.0 is available now!",headers:[{level:2,title:"What is new in 1.107.0?",slug:"what-is-new-in-1-107-0",link:"#what-is-new-in-1-107-0",children:[{level:3,title:"Changelog",slug:"changelog",link:"#changelog",children:[]}]}],path:"/blog/20230715-DeeDeeG-v1.107.0.html",pathLocale:"/",extraFields:[]},{title:"Hotfix: Pulsar v1.107.1",headers:[{level:2,title:"What is new in 1.107.1?",slug:"what-is-new-in-1-107-1",link:"#what-is-new-in-1-107-1",children:[]}],path:"/blog/20230716-Daeraxa-v1.107.1.html",pathLocale:"/",extraFields:[]},{title:"Community Update",headers:[{level:2,title:"Update to markdown-preview language identifiers",slug:"update-to-markdown-preview-language-identifiers",link:"#update-to-markdown-preview-language-identifiers",children:[]},{level:2,title:"Moving PPM to our own NPM fork",slug:"moving-ppm-to-our-own-npm-fork",link:"#moving-ppm-to-our-own-npm-fork",children:[]},{level:2,title:"pulsar-updater package in the works",slug:"pulsar-updater-package-in-the-works",link:"#pulsar-updater-package-in-the-works",children:[]},{level:2,title:"Pulsar Package Repository feature detection is now live!",slug:"pulsar-package-repository-feature-detection-is-now-live",link:"#pulsar-package-repository-feature-detection-is-now-live",children:[]},{level:2,title:"Community spotlight",slug:"community-spotlight",link:"#community-spotlight",children:[]}],path:"/blog/20230801-Daeraxa-AugustUpdate.html",pathLocale:"/",extraFields:[]},{title:"Great Scott! A new Pulsar release. Pulsar 1.108.0 is available now!",headers:[{level:2,title:"What is new in 1.108.0?",slug:"what-is-new-in-1-108-0",link:"#what-is-new-in-1-108-0",children:[{level:3,title:"Changelog",slug:"changelog",link:"#changelog",children:[]}]}],path:"/blog/20230816-DeeDeeG-v1.108.0.html",pathLocale:"/",extraFields:[]},{title:"Chocolatey packages are up to date again!",headers:[],path:"/blog/20230825-Daeraxa-ChocolateyUpdate.html",pathLocale:"/",extraFields:[]},{title:"Pulsar's CI",headers:[{level:2,title:"How it was",slug:"how-it-was",link:"#how-it-was",children:[]},{level:2,title:"How it is, and how it got there",slug:"how-it-is-and-how-it-got-there",link:"#how-it-is-and-how-it-got-there",children:[]},{level:2,title:"Summary",slug:"summary",link:"#summary",children:[]}],path:"/blog/20230903-confused-Techie-pulsars-ci.html",pathLocale:"/",extraFields:[]},{title:"Community Update",headers:[{level:2,title:"Upheaving our CI setup",slug:"upheaving-our-ci-setup",link:"#upheaving-our-ci-setup",children:[]},{level:2,title:"Chocolately packages are up to date again!",slug:"chocolately-packages-are-up-to-date-again",link:"#chocolately-packages-are-up-to-date-again",children:[]},{level:2,title:"New title bar configuration option",slug:"new-title-bar-configuration-option",link:"#new-title-bar-configuration-option",children:[]},{level:2,title:"Unpublishing packages",slug:"unpublishing-packages",link:"#unpublishing-packages",children:[]},{level:2,title:"Pulsar cleanup",slug:"pulsar-cleanup",link:"#pulsar-cleanup",children:[]},{level:2,title:"Community spotlight",slug:"community-spotlight",link:"#community-spotlight",children:[]}],path:"/blog/20230904-Daeraxa-SeptemberUpdate.html",pathLocale:"/",extraFields:[]},{title:"Going the whole nine yards: Get Pulsar 1.109.0 now!",headers:[{level:2,title:"What do we have for you in Pulsar 1.109.0?",slug:"what-do-we-have-for-you-in-pulsar-1-109-0",link:"#what-do-we-have-for-you-in-pulsar-1-109-0",children:[{level:3,title:"Pulsar",slug:"pulsar",link:"#pulsar",children:[]}]}],path:"/blog/20230916-Daeraxa-v1.109.0.html",pathLocale:"/",extraFields:[]},{title:"Lemmy community now open!",headers:[{level:2,title:"Come and join our newly opened Lemmy community!",slug:"come-and-join-our-newly-opened-lemmy-community",link:"#come-and-join-our-newly-opened-lemmy-community",children:[]}],path:"/blog/20230917-Daeraxa-LemmyCommunity.html",pathLocale:"/",extraFields:[]},{title:"Modern Tree-sitter, part 1: the new old feature",headers:[{level:2,title:"What is Tree-sitter?",slug:"what-is-tree-sitter",link:"#what-is-tree-sitter",children:[]},{level:2,title:"What is the new Tree-sitter integration replacing?",slug:"what-is-the-new-tree-sitter-integration-replacing",link:"#what-is-the-new-tree-sitter-integration-replacing",children:[]},{level:2,title:"If Tree-sitter is already in Pulsar, why write a new implementation?",slug:"if-tree-sitter-is-already-in-pulsar-why-write-a-new-implementation",link:"#if-tree-sitter-is-already-in-pulsar-why-write-a-new-implementation",children:[]},{level:2,title:"Why is Tree-sitter better in general?",slug:"why-is-tree-sitter-better-in-general",link:"#why-is-tree-sitter-better-in-general",children:[]},{level:2,title:"Why is this new implementation better than the old one?",slug:"why-is-this-new-implementation-better-than-the-old-one",link:"#why-is-this-new-implementation-better-than-the-old-one",children:[]},{level:2,title:"I disabled Tree-sitter grammars at some point, and I don\u2019t feel like I\u2019ve missed anything. Why should I turn them back on?",slug:"i-disabled-tree-sitter-grammars-at-some-point-and-i-don-t-feel-like-i-ve-missed-anything-why-should-i-turn-them-back-on",link:"#i-disabled-tree-sitter-grammars-at-some-point-and-i-don-t-feel-like-i-ve-missed-anything-why-should-i-turn-them-back-on",children:[]},{level:2,title:"Can I use this new implementation now?",slug:"can-i-use-this-new-implementation-now",link:"#can-i-use-this-new-implementation-now",children:[]},{level:2,title:"Which Tree-sitter grammars come with Pulsar?",slug:"which-tree-sitter-grammars-come-with-pulsar",link:"#which-tree-sitter-grammars-come-with-pulsar",children:[]},{level:2,title:"The old grammar highlighted my code in a way that I liked. Now things are colored differently and it\u2019s driving me nuts. Should I turn off Tree-sitter?",slug:"the-old-grammar-highlighted-my-code-in-a-way-that-i-liked-now-things-are-colored-differently-and-it-s-driving-me-nuts-should-i-turn-off-tree-sitter",link:"#the-old-grammar-highlighted-my-code-in-a-way-that-i-liked-now-things-are-colored-differently-and-it-s-driving-me-nuts-should-i-turn-off-tree-sitter",children:[]},{level:2,title:"Why should I write a Tree-sitter grammar for Pulsar?",slug:"why-should-i-write-a-tree-sitter-grammar-for-pulsar",link:"#why-should-i-write-a-tree-sitter-grammar-for-pulsar",children:[]},{level:2,title:"Why is this interesting enough to write about?",slug:"why-is-this-interesting-enough-to-write-about",link:"#why-is-this-interesting-enough-to-write-about",children:[]}],path:"/blog/20230925-savetheclocktower-modern-tree-sitter-part-1.html",pathLocale:"/",extraFields:[]},{title:"Modern Tree-sitter, part 2: why scopes matter",headers:[{level:2,title:"How TextMate grammars work",slug:"how-textmate-grammars-work",link:"#how-textmate-grammars-work",children:[]},{level:2,title:"How TextMate uses scope names",slug:"how-textmate-uses-scope-names",link:"#how-textmate-uses-scope-names",children:[]},{level:2,title:"How Pulsar uses scope names",slug:"how-pulsar-uses-scope-names",link:"#how-pulsar-uses-scope-names",children:[]},{level:2,title:"What would break?",slug:"what-would-break",link:"#what-would-break",children:[]},{level:2,title:"The challenge",slug:"the-challenge",link:"#the-challenge",children:[]}],path:"/blog/20230927-savetheclocktower-modern-tree-sitter-part-2.html",pathLocale:"/",extraFields:[]},{title:"Community Update",headers:[{level:2,title:"Introducing Pulsar Cooperative!",slug:"introducing-pulsar-cooperative",link:"#introducing-pulsar-cooperative",children:[]},{level:2,title:"Modern Tree-sitter blog posts",slug:"modern-tree-sitter-blog-posts",link:"#modern-tree-sitter-blog-posts",children:[]},{level:2,title:"Converting PPM's code from callbacks to async",slug:"converting-ppm-s-code-from-callbacks-to-async",link:"#converting-ppm-s-code-from-callbacks-to-async",children:[]},{level:2,title:"macOS binary signing issues",slug:"macos-binary-signing-issues",link:"#macos-binary-signing-issues",children:[]},{level:2,title:"Shields.io badges",slug:"shields-io-badges",link:"#shields-io-badges",children:[]},{level:2,title:"Pulsar on GitHub Desktop",slug:"pulsar-on-github-desktop",link:"#pulsar-on-github-desktop",children:[]},{level:2,title:"Community spotlight",slug:"community-spotlight",link:"#community-spotlight",children:[]}],path:"/blog/20231004-Daeraxa-OctoberUpdate.html",pathLocale:"/",extraFields:[]},{title:"Modern Tree-sitter, part 3: syntax highlighting via queries",headers:[{level:2,title:"The problems",slug:"the-problems",link:"#the-problems",children:[]},{level:2,title:"The first challenge: robust querying",slug:"the-first-challenge-robust-querying",link:"#the-first-challenge-robust-querying",children:[{level:3,title:"How captures work",slug:"how-captures-work",link:"#how-captures-work",children:[]},{level:3,title:"Highlights",slug:"highlights",link:"#highlights",children:[]},{level:3,title:"Scope tests",slug:"scope-tests",link:"#scope-tests",children:[]}]},{level:2,title:"The second challenge: scoping arbitrary ranges",slug:"the-second-challenge-scoping-arbitrary-ranges",link:"#the-second-challenge-scoping-arbitrary-ranges",children:[{level:3,title:"Scope adjustments",slug:"scope-adjustments",link:"#scope-adjustments",children:[]}]},{level:2,title:"What does this get us?",slug:"what-does-this-get-us",link:"#what-does-this-get-us",children:[]},{level:2,title:"Next time",slug:"next-time",link:"#next-time",children:[]}],path:"/blog/20231013-savetheclocktower-modern-tree-sitter-part-3.html",pathLocale:"/",extraFields:[]},{title:"Armed with a big ol' can of Raid: Pulsar 1.110.0 is available now!",headers:[{level:2,title:"What do we have for you in Pulsar 1.110.0?",slug:"what-do-we-have-for-you-in-pulsar-1-110-0",link:"#what-do-we-have-for-you-in-pulsar-1-110-0",children:[{level:3,title:"Pulsar",slug:"pulsar",link:"#pulsar",children:[]},{level:3,title:"ppm",slug:"ppm",link:"#ppm",children:[]}]}],path:"/blog/20231016-Daeraxa-v1.110.0.html",pathLocale:"/",extraFields:[]},{title:"Modern Tree-sitter, part 4: indentation and code folding",headers:[{level:2,title:"Indentation",slug:"indentation",link:"#indentation",children:[{level:3,title:"How TextMate grammars do it",slug:"how-textmate-grammars-do-it",link:"#how-textmate-grammars-do-it",children:[]},{level:3,title:"How legacy Tree-sitter grammars do it",slug:"how-legacy-tree-sitter-grammars-do-it",link:"#how-legacy-tree-sitter-grammars-do-it",children:[]},{level:3,title:"Working within the system",slug:"working-within-the-system",link:"#working-within-the-system",children:[]},{level:3,title:"Getting creative",slug:"getting-creative",link:"#getting-creative",children:[]}]},{level:2,title:"Code folding",slug:"code-folding",link:"#code-folding",children:[{level:3,title:"How TextMate grammars do it",slug:"how-textmate-grammars-do-it-1",link:"#how-textmate-grammars-do-it-1",children:[]},{level:3,title:"How legacy Tree-sitter grammars do it",slug:"how-legacy-tree-sitter-grammars-do-it-1",link:"#how-legacy-tree-sitter-grammars-do-it-1",children:[]},{level:3,title:"Using queries to define fold ranges",slug:"using-queries-to-define-fold-ranges",link:"#using-queries-to-define-fold-ranges",children:[]}]},{level:2,title:"Why is this so complicated?",slug:"why-is-this-so-complicated",link:"#why-is-this-so-complicated",children:[]},{level:2,title:"Next time",slug:"next-time",link:"#next-time",children:[]}],path:"/blog/20231031-savetheclocktower-modern-tree-sitter-part-4.html",pathLocale:"/",extraFields:[]},{title:"Community Update",headers:[{level:2,title:"New UI API",slug:"new-ui-api",link:"#new-ui-api",children:[]},{level:2,title:"Exposing a fuzzy-native API",slug:"exposing-a-fuzzy-native-api",link:"#exposing-a-fuzzy-native-api",children:[]},{level:2,title:"A new code runner package",slug:"a-new-code-runner-package",link:"#a-new-code-runner-package",children:[]},{level:2,title:"Further installments in the modern Tree-sitter blog post series",slug:"further-installments-in-the-modern-tree-sitter-blog-post-series",link:"#further-installments-in-the-modern-tree-sitter-blog-post-series",children:[]},{level:2,title:"Community spotlight",slug:"community-spotlight",link:"#community-spotlight",children:[]}],path:"/blog/20231109-Daeraxa-NovemberUpdate.html",pathLocale:"/",extraFields:[]},{title:"Modern Tree-sitter, part 5: injections",headers:[{level:2,title:"A mental model for injections",slug:"a-mental-model-for-injections",link:"#a-mental-model-for-injections",children:[{level:3,title:"Language layers",slug:"language-layers",link:"#language-layers",children:[]}]},{level:2,title:"How Tree-sitter envisions injections",slug:"how-tree-sitter-envisions-injections",link:"#how-tree-sitter-envisions-injections",children:[]},{level:2,title:"How Pulsar implements injections",slug:"how-pulsar-implements-injections",link:"#how-pulsar-implements-injections",children:[{level:3,title:"How does it know which language to use?",slug:"how-does-it-know-which-language-to-use",link:"#how-does-it-know-which-language-to-use",children:[]}]},{level:2,title:"Architecture",slug:"architecture",link:"#architecture",children:[{level:3,title:"Challenges",slug:"challenges",link:"#challenges",children:[]}]},{level:2,title:"Strange injection tricks",slug:"strange-injection-tricks",link:"#strange-injection-tricks",children:[{level:3,title:"\u201CRedaction\u201D in injections",slug:"redaction-in-injections",link:"#redaction-in-injections",children:[]},{level:3,title:"Macros in Rust/C/C++",slug:"macros-in-rust-c-c",link:"#macros-in-rust-c-c",children:[]},{level:3,title:"Injecting highlighting for URLs and TODOs",slug:"injecting-highlighting-for-urls-and-todos",link:"#injecting-highlighting-for-urls-and-todos",children:[]},{level:3,title:"Markdown and front matter",slug:"markdown-and-front-matter",link:"#markdown-and-front-matter",children:[]}]},{level:2,title:"Next time",slug:"next-time",link:"#next-time",children:[]}],path:"/blog/20231110-savetheclocktower-modern-tree-sitter-part-5.html",pathLocale:"/",extraFields:[]},{title:"If you're API and you know it, clap your hands!",headers:[{level:2,title:"What do we have for you in Pulsar 1.111.0?",slug:"what-do-we-have-for-you-in-pulsar-1-111-0",link:"#what-do-we-have-for-you-in-pulsar-1-111-0",children:[{level:3,title:"Pulsar",slug:"pulsar",link:"#pulsar",children:[]},{level:3,title:"github",slug:"github",link:"#github",children:[]},{level:3,title:"whats-my-line",slug:"whats-my-line",link:"#whats-my-line",children:[]}]}],path:"/blog/20231116-Daeraxa-v1.111.0.html",pathLocale:"/",extraFields:[]},{title:"Community Update",headers:[{level:2,title:"Update on Electron updates",slug:"update-on-electron-updates",link:"#update-on-electron-updates",children:[{level:3,title:"Tree-sitter",slug:"tree-sitter",link:"#tree-sitter",children:[]},{level:3,title:"Making it available",slug:"making-it-available",link:"#making-it-available",children:[]},{level:3,title:"Package compatibility",slug:"package-compatibility",link:"#package-compatibility",children:[]}]},{level:2,title:"PPR website issues",slug:"ppr-website-issues",link:"#ppr-website-issues",children:[]},{level:2,title:"Community Spotlight",slug:"community-spotlight",link:"#community-spotlight",children:[{level:3,title:"Default file icons",slug:"default-file-icons",link:"#default-file-icons",children:[]}]}],path:"/blog/20231212-Daeraxa-DecemberUpdate.html",pathLocale:"/",extraFields:[]},{title:"Christmas has come early",headers:[{level:3,title:"Pulsar",slug:"pulsar",link:"#pulsar",children:[]},{level:3,title:"PPM",slug:"ppm",link:"#ppm",children:[]},{level:3,title:"github",slug:"github",link:"#github",children:[]}],path:"/blog/20231216-Daeraxa-v1.112.0.html",pathLocale:"/",extraFields:[]},{title:"Hotfix: Pulsar v1.112.1",headers:[{level:2,title:"What is new in 1.112.1?",slug:"what-is-new-in-1-112-1",link:"#what-is-new-in-1-112-1",children:[]}],path:"/blog/20231219-DeeDeeG-v1.112.1.html",pathLocale:"/",extraFields:[]},{title:"Community Update",headers:[{level:2,title:"New tree-sitter becoming default",slug:"new-tree-sitter-becoming-default",link:"#new-tree-sitter-becoming-default",children:[]},{level:2,title:"Annoying website bug found and zapped!",slug:"annoying-website-bug-found-and-zapped",link:"#annoying-website-bug-found-and-zapped",children:[]},{level:2,title:"New PPR owners/:ownerName API endpoint",slug:"new-ppr-owners-ownername-api-endpoint",link:"#new-ppr-owners-ownername-api-endpoint",children:[]},{level:2,title:"Long-term projects",slug:"long-term-projects",link:"#long-term-projects",children:[]},{level:2,title:"Community Spotlight",slug:"community-spotlight",link:"#community-spotlight",children:[]}],path:"/blog/20240112-Daeraxa-JanuaryUpdate.html",pathLocale:"/",extraFields:[]},{title:"Unlucky for some, but not us. Our 13th release, Pulsar 1.113.0, is available now!",headers:[{level:3,title:"Pulsar",slug:"pulsar",link:"#pulsar",children:[]},{level:3,title:"find-and-replace",slug:"find-and-replace",link:"#find-and-replace",children:[]},{level:3,title:"symbols-view",slug:"symbols-view",link:"#symbols-view",children:[]}],path:"/blog/20240115-Daeraxa-v1.113.0.html",pathLocale:"/",extraFields:[]},{title:"Modern Tree-sitter, part 6: reinventing symbols-view",headers:[{level:2,title:"Background",slug:"background",link:"#background",children:[]},{level:2,title:"Refactoring symbols-view",slug:"refactoring-symbols-view",link:"#refactoring-symbols-view",children:[{level:3,title:"A crash course in services",slug:"a-crash-course-in-services",link:"#a-crash-course-in-services",children:[]},{level:3,title:"A built-in example",slug:"a-built-in-example",link:"#a-built-in-example",children:[]},{level:3,title:"How will it work?",slug:"how-will-it-work",link:"#how-will-it-work",children:[]},{level:3,title:"Project symbols",slug:"project-symbols",link:"#project-symbols",children:[]},{level:3,title:"Go to declaration",slug:"go-to-declaration",link:"#go-to-declaration",children:[]},{level:3,title:"Language servers",slug:"language-servers",link:"#language-servers",children:[]},{level:3,title:"Anyway, back to symbols-view",slug:"anyway-back-to-symbols-view",link:"#anyway-back-to-symbols-view",children:[]}]},{level:2,title:"Shipping now",slug:"shipping-now",link:"#shipping-now",children:[]},{level:2,title:"Conclusion",slug:"conclusion",link:"#conclusion",children:[]}],path:"/blog/20240122-savetheclocktower-modern-tree-sitter-part-6.html",pathLocale:"/",extraFields:[]},{title:"The quest for Electron LTS",headers:[{level:2,title:"A little bit of history",slug:"a-little-bit-of-history",link:"#a-little-bit-of-history",children:[]},{level:2,title:"The quest for Electron 13",slug:"the-quest-for-electron-13",link:"#the-quest-for-electron-13",children:[]},{level:2,title:"Superstring, and Memory Manipulation",slug:"superstring-and-memory-manipulation",link:"#superstring-and-memory-manipulation",children:[]},{level:2,title:"More things break",slug:"more-things-break",link:"#more-things-break",children:[]},{level:2,title:"Things work fine! (juuuust kidding!)",slug:"things-work-fine-juuuust-kidding",link:"#things-work-fine-juuuust-kidding",children:[]},{level:2,title:"The quest is (almost) over",slug:"the-quest-is-almost-over",link:"#the-quest-is-almost-over",children:[]}],path:"/blog/20240124-mauricioszabo-the-quest-for-electron-lts.html",pathLocale:"/",extraFields:[]},{title:"Community Update",headers:[{level:2,title:"Update on the new Tree-sitter implementation",slug:"update-on-the-new-tree-sitter-implementation",link:"#update-on-the-new-tree-sitter-implementation",children:[]},{level:2,title:"New blog post about our quest for Electron stable",slug:"new-blog-post-about-our-quest-for-electron-stable",link:"#new-blog-post-about-our-quest-for-electron-stable",children:[]},{level:2,title:"New PPR filter options",slug:"new-ppr-filter-options",link:"#new-ppr-filter-options",children:[]},{level:2,title:"Major update to our OpenAPI documentation for the PPR",slug:"major-update-to-our-openapi-documentation-for-the-ppr",link:"#major-update-to-our-openapi-documentation-for-the-ppr",children:[]},{level:2,title:"Community Spotlight",slug:"community-spotlight",link:"#community-spotlight",children:[]}],path:"/blog/20240201-Daeraxa-FebruaryUpdate.html",pathLocale:"/",extraFields:[]},{title:"A Valentine's release bursting with love, Pulsar 1.114.0 is available now!",headers:[{level:2,title:"A Valentine's release bursting with love, Pulsar 1.114.0 is available now!",slug:"a-valentine-s-release-bursting-with-love-pulsar-1-114-0-is-available-now",link:"#a-valentine-s-release-bursting-with-love-pulsar-1-114-0-is-available-now",children:[{level:3,title:"Pulsar",slug:"pulsar",link:"#pulsar",children:[]},{level:3,title:"PPM",slug:"ppm",link:"#ppm",children:[]},{level:3,title:"github",slug:"github",link:"#github",children:[]}]}],path:"/blog/20240215-Daeraxa-v1.114.0.html",pathLocale:"/",extraFields:[]},{title:"A week later than you\u2019re accustomed to \u2014 but worth the wait! Pulsar 1.115.0 is available now!",headers:[{level:2,title:"A week later than you\u2019re accustomed to \u2014 but worth the wait! Pulsar 1.115.0 is available now!",slug:"a-week-later-than-you-re-accustomed-to-\u2014-but-worth-the-wait-pulsar-1-115-0-is-available-now",link:"#a-week-later-than-you-re-accustomed-to-\u2014-but-worth-the-wait-pulsar-1-115-0-is-available-now",children:[{level:3,title:"Pulsar",slug:"pulsar",link:"#pulsar",children:[]}]}],path:"/blog/20240323-savetheclocktower-v1.115.0.html",pathLocale:"/",extraFields:[]},{title:"For all the code ninjas out there, Pulsar 1.116.0 is available now!",headers:[{level:2,title:"Pulsar 1.116.0: Ready for all the code ninjas out there!",slug:"pulsar-1-116-0-ready-for-all-the-code-ninjas-out-there",link:"#pulsar-1-116-0-ready-for-all-the-code-ninjas-out-there",children:[{level:3,title:"Pulsar",slug:"pulsar",link:"#pulsar",children:[]},{level:3,title:"snippets",slug:"snippets",link:"#snippets",children:[]}]}],path:"/blog/20240417-confused-Techie-v1.116.0.html",pathLocale:"/",extraFields:[]},{title:"With special love for Markdown and Tree-sitter, Pulsar 1.117.0 is available now!",headers:[{level:2,title:"Pulsar 1.117.0: With special love for Markdown and Tree-sitter",slug:"pulsar-1-117-0-with-special-love-for-markdown-and-tree-sitter",link:"#pulsar-1-117-0-with-special-love-for-markdown-and-tree-sitter",children:[{level:3,title:"Pulsar",slug:"pulsar",link:"#pulsar",children:[]}]}],path:"/blog/20240520-confused-Techie-v1.117.0.html",pathLocale:"/",extraFields:[]},{title:"Hot dog, it's another Pulsar release!",headers:[{level:2,title:"Hot dog, it's another Pulsar release!",slug:"hot-dog-it-s-another-pulsar-release",link:"#hot-dog-it-s-another-pulsar-release",children:[{level:3,title:"Pulsar",slug:"pulsar",link:"#pulsar",children:[]}]}],path:"/blog/20240616-confused-Techie-v1.118.0.html",pathLocale:"/",extraFields:[]},{title:"Pulsar v1.119.0 is Live!",headers:[{level:2,title:"Pulsar v1.119.0 is live!",slug:"pulsar-v1-119-0-is-live",link:"#pulsar-v1-119-0-is-live",children:[{level:3,title:"Pulsar",slug:"pulsar",link:"#pulsar",children:[]},{level:3,title:"superstring",slug:"superstring",link:"#superstring",children:[]}]}],path:"/blog/20240717-confused-Techie-v1.119.0.html",pathLocale:"/",extraFields:[]},{title:"Documentation Home",headers:[{level:2,title:"Welcome to Pulsar",slug:"welcome-to-pulsar",link:"#welcome-to-pulsar",children:[{level:3,title:"Launch Manual",slug:"launch-manual",link:"#launch-manual",children:[]},{level:3,title:"Packages",slug:"packages",link:"#packages",children:[]},{level:3,title:"Resources",slug:"resources",link:"#resources",children:[]},{level:3,title:"Atom Archive",slug:"atom-archive",link:"#atom-archive",children:[]}]}],path:"/docs/",pathLocale:"/",extraFields:[]},{title:"Atom Documentation Archive",headers:[{level:2,title:"Chapters",slug:"chapters",link:"#chapters",children:[]}],path:"/docs/atom-archive/",pathLocale:"/",extraFields:[]},{title:"Launch Manual",headers:[{level:2,title:"Getting Started",slug:"getting-started",link:"#getting-started",children:[]},{level:2,title:"Using Pulsar",slug:"using-pulsar",link:"#using-pulsar",children:[]},{level:2,title:"Hacking the Core",slug:"hacking-the-core",link:"#hacking-the-core",children:[]},{level:2,title:"Behind Pulsar",slug:"behind-pulsar",link:"#behind-pulsar",children:[]},{level:2,title:"FAQ",slug:"faq",link:"#faq",children:[]}],path:"/docs/launch-manual/",pathLocale:"/",extraFields:[]},{title:"Packages",headers:[{level:2,title:"Core Packages",slug:"core-packages",link:"#core-packages",children:[{level:3,title:"Updated",slug:"updated",link:"#updated",children:[]},{level:3,title:"Archived Documentation",slug:"archived-documentation",link:"#archived-documentation",children:[]}]},{level:2,title:"Community Packages",slug:"community-packages",link:"#community-packages",children:[]}],path:"/docs/packages/",pathLocale:"/",extraFields:[]},{title:"Resources",headers:[{level:2,title:"Glossary",slug:"glossary",link:"#glossary",children:[]},{level:2,title:"Pulsar API",slug:"pulsar-api",link:"#pulsar-api",children:[]},{level:2,title:"Project tooling",slug:"project-tooling",link:"#project-tooling",children:[]}],path:"/docs/resources/",pathLocale:"/",extraFields:[]},{title:"Reference : API",headers:[],path:"/docs/atom-archive/api/",pathLocale:"/",extraFields:[]},{title:"Appendix E : Atom server-side APIs",headers:[{level:2,title:"Atom server-side APIs",slug:"atom-server-side-apis",link:"#atom-server-side-apis",children:[{level:3,title:"Atom package server API",slug:"atom-package-server-api",link:"#atom-package-server-api",children:[]},{level:3,title:"Atom update server API",slug:"atom-update-server-api",link:"#atom-update-server-api",children:[]}]}],path:"/docs/atom-archive/atom-server-side-apis/",pathLocale:"/",extraFields:[]},{title:"Chapter 4 : Behind Atom",headers:[{level:2,title:"Behind Atom",slug:"behind-atom",link:"#behind-atom",children:[{level:3,title:"Configuration API",slug:"configuration-api",link:"#configuration-api",children:[]},{level:3,title:"Keymaps In-Depth",slug:"keymaps-in-depth",link:"#keymaps-in-depth",children:[]},{level:3,title:"Scoped Settings, Scopes and Scope Descriptors",slug:"scoped-settings-scopes-and-scope-descriptors",link:"#scoped-settings-scopes-and-scope-descriptors",children:[]},{level:3,title:"Serialization in Atom",slug:"serialization-in-atom",link:"#serialization-in-atom",children:[]},{level:3,title:"Developing Node Modules",slug:"developing-node-modules",link:"#developing-node-modules",children:[]},{level:3,title:"Interacting With Other Packages Via Services",slug:"interacting-with-other-packages-via-services",link:"#interacting-with-other-packages-via-services",children:[]},{level:3,title:"Maintaining Your Packages",slug:"maintaining-your-packages",link:"#maintaining-your-packages",children:[]},{level:3,title:"How Atom Uses Chromium Snapshots",slug:"how-atom-uses-chromium-snapshots",link:"#how-atom-uses-chromium-snapshots",children:[]},{level:3,title:"Summary",slug:"summary",link:"#summary",children:[]}]}],path:"/docs/atom-archive/behind-atom/",pathLocale:"/",extraFields:[]},{title:"Appendix B : FAQ",headers:[{level:2,title:"FAQ",slug:"faq",link:"#faq",children:[{level:3,title:"Is Atom open source?",slug:"is-atom-open-source",link:"#is-atom-open-source",children:[]},{level:3,title:"What does Atom cost?",slug:"what-does-atom-cost",link:"#what-does-atom-cost",children:[]},{level:3,title:"What platforms does Atom run on?",slug:"what-platforms-does-atom-run-on",link:"#what-platforms-does-atom-run-on",children:[]},{level:3,title:"How can I contribute to Atom?",slug:"how-can-i-contribute-to-atom",link:"#how-can-i-contribute-to-atom",children:[]},{level:3,title:"Why does Atom collect usage data?",slug:"why-does-atom-collect-usage-data",link:"#why-does-atom-collect-usage-data",children:[]},{level:3,title:"Atom in the cloud?",slug:"atom-in-the-cloud",link:"#atom-in-the-cloud",children:[]},{level:3,title:"What's the difference between an IDE and an editor?",slug:"what-s-the-difference-between-an-ide-and-an-editor",link:"#what-s-the-difference-between-an-ide-and-an-editor",children:[]},{level:3,title:"How can I tell if subpixel antialiasing is working?",slug:"how-can-i-tell-if-subpixel-antialiasing-is-working",link:"#how-can-i-tell-if-subpixel-antialiasing-is-working",children:[]},{level:3,title:"Why is Atom deleting trailing whitespace? Why is there a newline at the end of the file?",slug:"why-is-atom-deleting-trailing-whitespace-why-is-there-a-newline-at-the-end-of-the-file",link:"#why-is-atom-deleting-trailing-whitespace-why-is-there-a-newline-at-the-end-of-the-file",children:[]},{level:3,title:"What does Safe Mode do?",slug:"what-does-safe-mode-do",link:"#what-does-safe-mode-do",children:[]},{level:3,title:"I have a question about a specific Atom community package. Where is the best place to ask it?",slug:"i-have-a-question-about-a-specific-atom-community-package-where-is-the-best-place-to-ask-it",link:"#i-have-a-question-about-a-specific-atom-community-package-where-is-the-best-place-to-ask-it",children:[]},{level:3,title:"I\u2019m using an international keyboard and keys that use AltGr or Ctrl+Alt aren\u2019t working",slug:"i-m-using-an-international-keyboard-and-keys-that-use-altgr-or-ctrl-alt-aren-t-working",link:"#i-m-using-an-international-keyboard-and-keys-that-use-altgr-or-ctrl-alt-aren-t-working",children:[]},{level:3,title:"I\u2019m having a problem with Julia! What do I do?",slug:"i-m-having-a-problem-with-julia-what-do-i-do",link:"#i-m-having-a-problem-with-julia-what-do-i-do",children:[]},{level:3,title:"I\u2019m getting an error about a \u201Cself-signed certificate\u201D. What do I do?",slug:"i-m-getting-an-error-about-a-self-signed-certificate-what-do-i-do",link:"#i-m-getting-an-error-about-a-self-signed-certificate-what-do-i-do",children:[]},{level:3,title:"I\u2019m having a problem with PlatformIO! What do I do?",slug:"i-m-having-a-problem-with-platformio-what-do-i-do",link:"#i-m-having-a-problem-with-platformio-what-do-i-do",children:[]},{level:3,title:"How do I make Atom recognize a file with extension X as language Y?",slug:"how-do-i-make-atom-recognize-a-file-with-extension-x-as-language-y",link:"#how-do-i-make-atom-recognize-a-file-with-extension-x-as-language-y",children:[]},{level:3,title:"How do I make the Welcome screen stop showing up?",slug:"how-do-i-make-the-welcome-screen-stop-showing-up",link:"#how-do-i-make-the-welcome-screen-stop-showing-up",children:[]},{level:3,title:"How do I preview web page changes automatically?",slug:"how-do-i-preview-web-page-changes-automatically",link:"#how-do-i-preview-web-page-changes-automatically",children:[]},{level:3,title:"How do I accept input from my program or script when using the script package?",slug:"how-do-i-accept-input-from-my-program-or-script-when-using-the-script-package",link:"#how-do-i-accept-input-from-my-program-or-script-when-using-the-script-package",children:[]},{level:3,title:"I am unable to update to the latest version of Atom on macOS. How do I fix this?",slug:"i-am-unable-to-update-to-the-latest-version-of-atom-on-macos-how-do-i-fix-this",link:"#i-am-unable-to-update-to-the-latest-version-of-atom-on-macos-how-do-i-fix-this",children:[]},{level:3,title:"I\u2019m trying to change my syntax colors from styles.less, but it isn\u2019t working!",slug:"i-m-trying-to-change-my-syntax-colors-from-styles-less-but-it-isn-t-working",link:"#i-m-trying-to-change-my-syntax-colors-from-styles-less-but-it-isn-t-working",children:[]},{level:3,title:"How do I build or execute code I've written in Atom?",slug:"how-do-i-build-or-execute-code-i-ve-written-in-atom",link:"#how-do-i-build-or-execute-code-i-ve-written-in-atom",children:[]},{level:3,title:"How do I uninstall Atom on macOS?",slug:"how-do-i-uninstall-atom-on-macos",link:"#how-do-i-uninstall-atom-on-macos",children:[]},{level:3,title:"MacOS Mojave font rendering change",slug:"macos-mojave-font-rendering-change",link:"#macos-mojave-font-rendering-change",children:[]},{level:3,title:"Why does macOS say that Atom wants to access my calendar, contacts, photos, etc.?",slug:"why-does-macos-say-that-atom-wants-to-access-my-calendar-contacts-photos-etc",link:"#why-does-macos-say-that-atom-wants-to-access-my-calendar-contacts-photos-etc",children:[]},{level:3,title:"How do I turn on line wrap?",slug:"how-do-i-turn-on-line-wrap",link:"#how-do-i-turn-on-line-wrap",children:[]},{level:3,title:"The menu bar disappeared, how do I get it back?",slug:"the-menu-bar-disappeared-how-do-i-get-it-back",link:"#the-menu-bar-disappeared-how-do-i-get-it-back",children:[]},{level:3,title:"How do I use a newline in the result of find and replace?",slug:"how-do-i-use-a-newline-in-the-result-of-find-and-replace",link:"#how-do-i-use-a-newline-in-the-result-of-find-and-replace",children:[]},{level:3,title:"What is this line on the right in the editor view?",slug:"what-is-this-line-on-the-right-in-the-editor-view",link:"#what-is-this-line-on-the-right-in-the-editor-view",children:[]}]}],path:"/docs/atom-archive/faq/",pathLocale:"/",extraFields:[]},{title:"Chapter 1 : Getting started",headers:[{level:2,title:"Getting Started",slug:"getting-started",link:"#getting-started",children:[{level:3,title:"Why Atom?",slug:"why-atom",link:"#why-atom",children:[]},{level:3,title:"Installing Atom",slug:"installing-atom",link:"#installing-atom",children:[]},{level:3,title:"Summary",slug:"summary",link:"#summary",children:[]}]}],path:"/docs/atom-archive/getting-started/",pathLocale:"/",extraFields:[]},{title:"Chapter 3 : Hacking Atom",headers:[{level:2,title:"Hacking Atom",slug:"hacking-atom",link:"#hacking-atom",children:[{level:3,title:"Tools of the Trade",slug:"tools-of-the-trade",link:"#tools-of-the-trade",children:[]},{level:3,title:"The Init File",slug:"the-init-file",link:"#the-init-file",children:[]},{level:3,title:"Package: Word Count",slug:"package-word-count",link:"#package-word-count",children:[]},{level:3,title:"Package: Modifying Text",slug:"package-modifying-text",link:"#package-modifying-text",children:[]},{level:3,title:"Package: Active Editor Info",slug:"package-active-editor-info",link:"#package-active-editor-info",children:[]},{level:3,title:"Creating a Theme",slug:"creating-a-theme",link:"#creating-a-theme",children:[]},{level:3,title:"Creating a Grammar",slug:"creating-a-grammar",link:"#creating-a-grammar",children:[]},{level:3,title:"Creating a Legacy TextMate Grammar",slug:"creating-a-legacy-textmate-grammar",link:"#creating-a-legacy-textmate-grammar",children:[]},{level:3,title:"Publishing",slug:"publishing",link:"#publishing",children:[]},{level:3,title:"Iconography",slug:"iconography",link:"#iconography",children:[]},{level:3,title:"Debugging",slug:"debugging",link:"#debugging",children:[]},{level:3,title:"Writing Specs",slug:"writing-specs",link:"#writing-specs",children:[]},{level:3,title:"Handling URIs",slug:"handling-uris",link:"#handling-uris",children:[]},{level:3,title:"Core URIs",slug:"core-uris",link:"#core-uris",children:[]},{level:3,title:"Cross-Platform Compatibility",slug:"cross-platform-compatibility",link:"#cross-platform-compatibility",children:[]},{level:3,title:"Converting from TextMate",slug:"converting-from-textmate",link:"#converting-from-textmate",children:[]},{level:3,title:"Hacking on Atom Core",slug:"hacking-on-atom-core",link:"#hacking-on-atom-core",children:[]},{level:3,title:"Contributing to Official Atom Packages",slug:"contributing-to-official-atom-packages",link:"#contributing-to-official-atom-packages",children:[]}]},{level:2,title:"Creating a Fork of a Core Package in atom/atom",slug:"creating-a-fork-of-a-core-package-in-atom-atom",link:"#creating-a-fork-of-a-core-package-in-atom-atom",children:[{level:3,title:"Creating Your New Package",slug:"creating-your-new-package",link:"#creating-your-new-package",children:[]},{level:3,title:"Merging Upstream Changes into Your Package",slug:"merging-upstream-changes-into-your-package",link:"#merging-upstream-changes-into-your-package",children:[]},{level:3,title:"Maintaining a Fork of a Core Package in atom/atom",slug:"maintaining-a-fork-of-a-core-package-in-atom-atom",link:"#maintaining-a-fork-of-a-core-package-in-atom-atom",children:[]},{level:3,title:"Summary",slug:"summary-3",link:"#summary-3",children:[]}]}],path:"/docs/atom-archive/hacking-atom/",pathLocale:"/",extraFields:[]},{title:"Appendix A : Resources",headers:[{level:2,title:"Resources",slug:"resources",link:"#resources",children:[{level:3,title:"Glossary",slug:"glossary",link:"#glossary",children:[]}]}],path:"/docs/atom-archive/resources/",pathLocale:"/",extraFields:[]},{title:"Appendix C : Shadow DOM",headers:[{level:2,title:"Shadow DOM",slug:"shadow-dom",link:"#shadow-dom",children:[{level:3,title:"Removing Shadow DOM styles",slug:"removing-shadow-dom-styles",link:"#removing-shadow-dom-styles",children:[]}]}],path:"/docs/atom-archive/shadow-dom/",pathLocale:"/",extraFields:[]},{title:"Appendix D : Upgrading to 1.0 APIs",headers:[{level:2,title:"Upgrading to 1.0 APIs",slug:"upgrading-to-1-0-apis",link:"#upgrading-to-1-0-apis",children:[{level:3,title:"Upgrading Your Package",slug:"upgrading-your-package",link:"#upgrading-your-package",children:[]},{level:3,title:"Upgrading Your UI Theme Or Package Selectors",slug:"upgrading-your-ui-theme-or-package-selectors",link:"#upgrading-your-ui-theme-or-package-selectors",children:[]},{level:3,title:"Upgrading Your Syntax Theme",slug:"upgrading-your-syntax-theme",link:"#upgrading-your-syntax-theme",children:[]}]}],path:"/docs/atom-archive/upgrading-to-1-0-apis/",pathLocale:"/",extraFields:[]},{title:"Chapter 2 : Using Atom",headers:[{level:2,title:"Using Atom",slug:"using-atom",link:"#using-atom",children:[{level:3,title:"Atom Packages",slug:"atom-packages",link:"#atom-packages",children:[]},{level:3,title:"Moving in Atom",slug:"moving-in-atom",link:"#moving-in-atom",children:[]},{level:3,title:"Atom Selections",slug:"atom-selections",link:"#atom-selections",children:[]},{level:3,title:"Editing and Deleting Text",slug:"editing-and-deleting-text",link:"#editing-and-deleting-text",children:[]},{level:3,title:"Find and Replace",slug:"find-and-replace",link:"#find-and-replace",children:[]},{level:3,title:"Snippets",slug:"snippets",link:"#snippets",children:[]},{level:3,title:"Autocomplete",slug:"autocomplete",link:"#autocomplete",children:[]},{level:3,title:"Folding",slug:"folding",link:"#folding",children:[]},{level:3,title:"Panes",slug:"panes",link:"#panes",children:[]},{level:3,title:"Pending Pane Items",slug:"pending-pane-items",link:"#pending-pane-items",children:[]},{level:3,title:"Grammar",slug:"grammar",link:"#grammar",children:[]},{level:3,title:"Version Control in Atom",slug:"version-control-in-atom",link:"#version-control-in-atom",children:[]},{level:3,title:"GitHub package",slug:"github-package",link:"#github-package",children:[]},{level:3,title:"Writing in Atom",slug:"writing-in-atom",link:"#writing-in-atom",children:[]},{level:3,title:"Basic Customization",slug:"basic-customization",link:"#basic-customization",children:[]},{level:3,title:"Summary",slug:"summary",link:"#summary",children:[]}]}],path:"/docs/atom-archive/using-atom/",pathLocale:"/",extraFields:[]},{title:"Core Packages Documentation",headers:[{level:2,title:"Archived Wikis",slug:"archived-wikis",link:"#archived-wikis",children:[]}],path:"/docs/packages/core/",pathLocale:"/",extraFields:[]},{title:"Code of Conduct",headers:[{level:2,title:"Our Pledge",slug:"our-pledge",link:"#our-pledge",children:[]},{level:2,title:"Our Standards",slug:"our-standards",link:"#our-standards",children:[]},{level:2,title:"Our Responsibilities",slug:"our-responsibilities",link:"#our-responsibilities",children:[]},{level:2,title:"Scope",slug:"scope",link:"#scope",children:[]},{level:2,title:"Enforcement",slug:"enforcement",link:"#enforcement",children:[]},{level:2,title:"Attribution",slug:"attribution",link:"#attribution",children:[]}],path:"/docs/resources/conduct/",pathLocale:"/",extraFields:[]},{title:"Glossary",headers:[],path:"/docs/resources/glossary/",pathLocale:"/",extraFields:[]},{title:"Privacy Policy",headers:[{level:2,title:"Pulsar Backend (https://api.pulsar-edit.dev)",slug:"pulsar-backend-https-api-pulsar-edit-dev",link:"#pulsar-backend-https-api-pulsar-edit-dev",children:[{level:3,title:"What Information is Collected",slug:"what-information-is-collected",link:"#what-information-is-collected",children:[]},{level:3,title:"How is this Information Collected",slug:"how-is-this-information-collected",link:"#how-is-this-information-collected",children:[]},{level:3,title:"What is Done with this Information",slug:"what-is-done-with-this-information",link:"#what-is-done-with-this-information",children:[]},{level:3,title:"Who has Access to this Information",slug:"who-has-access-to-this-information",link:"#who-has-access-to-this-information",children:[]},{level:3,title:"How Long is this information Kept",slug:"how-long-is-this-information-kept",link:"#how-long-is-this-information-kept",children:[]},{level:3,title:"TLDR",slug:"tldr",link:"#tldr",children:[]}]},{level:2,title:"Pulsar User Account",slug:"pulsar-user-account",link:"#pulsar-user-account",children:[{level:3,title:"What Information is Collected",slug:"what-information-is-collected-1",link:"#what-information-is-collected-1",children:[]},{level:3,title:"How is this Information Collected",slug:"how-is-this-information-collected-1",link:"#how-is-this-information-collected-1",children:[]},{level:3,title:"What is Done with this Information",slug:"what-is-done-with-this-information-1",link:"#what-is-done-with-this-information-1",children:[]},{level:3,title:"Who has Access to this Information",slug:"who-has-access-to-this-information-1",link:"#who-has-access-to-this-information-1",children:[]},{level:3,title:"How Long is this Information Kept",slug:"how-long-is-this-information-kept-1",link:"#how-long-is-this-information-kept-1",children:[]},{level:3,title:"How do I have this Information Deleted",slug:"how-do-i-have-this-information-deleted",link:"#how-do-i-have-this-information-deleted",children:[]},{level:3,title:"TLDR",slug:"tldr-1",link:"#tldr-1",children:[]}]},{level:2,title:"Pulsar Website (https://pulsar-edit.dev)",slug:"pulsar-website-https-pulsar-edit-dev",link:"#pulsar-website-https-pulsar-edit-dev",children:[{level:3,title:"TLDR",slug:"tldr-2",link:"#tldr-2",children:[]}]},{level:2,title:"Pulsar Editor",slug:"pulsar-editor",link:"#pulsar-editor",children:[{level:3,title:"TLDR",slug:"tldr-3",link:"#tldr-3",children:[]}]}],path:"/docs/resources/privacy/",pathLocale:"/",extraFields:[]},{title:"Pulsar API",headers:[],path:"/docs/resources/pulsar-api/",pathLocale:"/",extraFields:[]},{title:"Tooling",headers:[{level:2,title:"Continuous Integration",slug:"continuous-integration",link:"#continuous-integration",children:[{level:3,title:"Cirrus CI",slug:"cirrus-ci",link:"#cirrus-ci",children:[]},{level:3,title:"Codacy",slug:"codacy",link:"#codacy",children:[]}]},{level:2,title:"i18n (Internationalization)",slug:"i18n-internationalization",link:"#i18n-internationalization",children:[{level:3,title:"Crowdin",slug:"crowdin",link:"#crowdin",children:[]}]},{level:2,title:"Package Managers",slug:"package-managers",link:"#package-managers",children:[]},{level:2,title:"Cloud Database",slug:"cloud-database",link:"#cloud-database",children:[]},{level:2,title:"Cloud Compute",slug:"cloud-compute",link:"#cloud-compute",children:[]},{level:2,title:"Additional Testing tools",slug:"additional-testing-tools",link:"#additional-testing-tools",children:[{level:3,title:"Action Pulsar Dependency Tester",slug:"action-pulsar-dependency-tester",link:"#action-pulsar-dependency-tester",children:[]}]}],path:"/docs/resources/tooling/",pathLocale:"/",extraFields:[]},{title:"Website",headers:[{level:2,title:"Building the website",slug:"building-the-website",link:"#building-the-website",children:[{level:3,title:"Prerequisites",slug:"prerequisites",link:"#prerequisites",children:[]},{level:3,title:"Clone the repository and install",slug:"clone-the-repository-and-install",link:"#clone-the-repository-and-install",children:[]},{level:3,title:"Running the website",slug:"running-the-website",link:"#running-the-website",children:[]},{level:3,title:"Notes",slug:"notes",link:"#notes",children:[]}]},{level:2,title:"Configuration files",slug:"configuration-files",link:"#configuration-files",children:[{level:3,title:"config.js",slug:"config-js",link:"#config-js",children:[]},{level:3,title:"navbar.js",slug:"navbar-js",link:"#navbar-js",children:[]},{level:3,title:"sidebar.js",slug:"sidebar-js",link:"#sidebar-js",children:[]},{level:3,title:"Theme",slug:"theme",link:"#theme",children:[]}]},{level:2,title:"File Organization",slug:"file-organization",link:"#file-organization",children:[{level:3,title:"index.md",slug:"index-md",link:"#index-md",children:[]},{level:3,title:"Sections",slug:"sections",link:"#sections",children:[]},{level:3,title:"Assets",slug:"assets",link:"#assets",children:[]}]},{level:2,title:"Documentation style & reusable components",slug:"documentation-style-reusable-components",link:"#documentation-style-reusable-components",children:[{level:3,title:"Structure",slug:"structure",link:"#structure",children:[]},{level:3,title:"Links",slug:"links",link:"#links",children:[]},{level:3,title:"Naming",slug:"naming",link:"#naming",children:[]},{level:3,title:"Containers",slug:"containers",link:"#containers",children:[]},{level:3,title:"Writing style",slug:"writing-style",link:"#writing-style",children:[]}]},{level:2,title:"Blog guide",slug:"blog-guide",link:"#blog-guide",children:[{level:3,title:"Writing a new post",slug:"writing-a-new-post",link:"#writing-a-new-post",children:[]},{level:3,title:"Testing locally",slug:"testing-locally",link:"#testing-locally",children:[]},{level:3,title:'Website "Blog" page(s)',slug:"website-blog-page-s",link:"#website-blog-page-s",children:[]},{level:3,title:"Questions? Suggestions",slug:"questions-suggestions",link:"#questions-suggestions",children:[]}]}],path:"/docs/resources/website/",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"Atom package server API",slug:"atom-package-server-api",link:"#atom-package-server-api",children:[]}],path:"/docs/atom-archive/atom-server-side-apis/sections/atom-package-server-api.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"Atom update server API",slug:"atom-update-server-api",link:"#atom-update-server-api",children:[]}],path:"/docs/atom-archive/atom-server-side-apis/sections/atom-update-server-api.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"Configuration API",slug:"configuration-api",link:"#configuration-api",children:[]}],path:"/docs/atom-archive/behind-atom/sections/configuration-api.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"Developing Node Modules",slug:"developing-node-modules",link:"#developing-node-modules",children:[]}],path:"/docs/atom-archive/behind-atom/sections/developing-node-modules.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"How Atom Uses Chromium Snapshots",slug:"how-atom-uses-chromium-snapshots",link:"#how-atom-uses-chromium-snapshots",children:[]}],path:"/docs/atom-archive/behind-atom/sections/how-atom-uses-chromium-snapshots.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"Interacting With Other Packages Via Services",slug:"interacting-with-other-packages-via-services",link:"#interacting-with-other-packages-via-services",children:[]}],path:"/docs/atom-archive/behind-atom/sections/interacting-with-other-packages-via-services.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"Keymaps In-Depth",slug:"keymaps-in-depth",link:"#keymaps-in-depth",children:[]}],path:"/docs/atom-archive/behind-atom/sections/keymaps-in-depth.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"Maintaining Your Packages",slug:"maintaining-your-packages",link:"#maintaining-your-packages",children:[]}],path:"/docs/atom-archive/behind-atom/sections/maintaining-your-packages.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"Scoped Settings, Scopes and Scope Descriptors",slug:"scoped-settings-scopes-and-scope-descriptors",link:"#scoped-settings-scopes-and-scope-descriptors",children:[]}],path:"/docs/atom-archive/behind-atom/sections/scoped-settings-scopes-and-scope-descriptors.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"Serialization in Atom",slug:"serialization-in-atom",link:"#serialization-in-atom",children:[]}],path:"/docs/atom-archive/behind-atom/sections/serialization-in-atom.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"Summary",slug:"summary",link:"#summary",children:[]}],path:"/docs/atom-archive/behind-atom/sections/summary.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"Atom in the cloud?",slug:"atom-in-the-cloud",link:"#atom-in-the-cloud",children:[]}],path:"/docs/atom-archive/faq/sections/atom-in-the-cloud.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"How can I contribute to Atom?",slug:"how-can-i-contribute-to-atom",link:"#how-can-i-contribute-to-atom",children:[]}],path:"/docs/atom-archive/faq/sections/how-can-i-contribute-to-atom.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"How can I tell if subpixel antialiasing is working?",slug:"how-can-i-tell-if-subpixel-antialiasing-is-working",link:"#how-can-i-tell-if-subpixel-antialiasing-is-working",children:[]}],path:"/docs/atom-archive/faq/sections/how-can-i-tell-if-subpixel-antialiasing-is-working.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"How do I accept input from my program or script when using the script package?",slug:"how-do-i-accept-input-from-my-program-or-script-when-using-the-script-package",link:"#how-do-i-accept-input-from-my-program-or-script-when-using-the-script-package",children:[]}],path:"/docs/atom-archive/faq/sections/how-do-i-accept-input-from-my-program-or-script-when-using-the-script-package.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"How do I build or execute code I've written in Atom?",slug:"how-do-i-build-or-execute-code-i-ve-written-in-atom",link:"#how-do-i-build-or-execute-code-i-ve-written-in-atom",children:[]}],path:"/docs/atom-archive/faq/sections/how-do-i-build-or-execute-code-i-ve-written-in-atom.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"How do I make Atom recognize a file with extension X as language Y?",slug:"how-do-i-make-atom-recognize-a-file-with-extension-x-as-language-y",link:"#how-do-i-make-atom-recognize-a-file-with-extension-x-as-language-y",children:[]}],path:"/docs/atom-archive/faq/sections/how-do-i-make-atom-recognize-a-file-with-extension-x-as-language-y.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"How do I make the Welcome screen stop showing up?",slug:"how-do-i-make-the-welcome-screen-stop-showing-up",link:"#how-do-i-make-the-welcome-screen-stop-showing-up",children:[]}],path:"/docs/atom-archive/faq/sections/how-do-i-make-the-welcome-screen-stop-showing-up.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"How do I preview web page changes automatically?",slug:"how-do-i-preview-web-page-changes-automatically",link:"#how-do-i-preview-web-page-changes-automatically",children:[]}],path:"/docs/atom-archive/faq/sections/how-do-i-preview-web-page-changes-automatically.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"How do I turn on line wrap?",slug:"how-do-i-turn-on-line-wrap",link:"#how-do-i-turn-on-line-wrap",children:[]}],path:"/docs/atom-archive/faq/sections/how-do-i-turn-on-line-wrap.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"How do I uninstall Atom on macOS?",slug:"how-do-i-uninstall-atom-on-macos",link:"#how-do-i-uninstall-atom-on-macos",children:[]}],path:"/docs/atom-archive/faq/sections/how-do-i-uninstall-atom-on-macos.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"How do I use a newline in the result of find and replace?",slug:"how-do-i-use-a-newline-in-the-result-of-find-and-replace",link:"#how-do-i-use-a-newline-in-the-result-of-find-and-replace",children:[]}],path:"/docs/atom-archive/faq/sections/how-do-i-use-a-newline-in-the-result-of-find-and-replace.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"I am unable to update to the latest version of Atom on macOS. How do I fix this?",slug:"i-am-unable-to-update-to-the-latest-version-of-atom-on-macos-how-do-i-fix-this",link:"#i-am-unable-to-update-to-the-latest-version-of-atom-on-macos-how-do-i-fix-this",children:[]}],path:"/docs/atom-archive/faq/sections/i-am-unable-to-update-to-the-latest-version-of-atom-on-macos-how-do-i-fix-this.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"I have a question about a specific Atom community package. Where is the best place to ask it?",slug:"i-have-a-question-about-a-specific-atom-community-package-where-is-the-best-place-to-ask-it",link:"#i-have-a-question-about-a-specific-atom-community-package-where-is-the-best-place-to-ask-it",children:[]}],path:"/docs/atom-archive/faq/sections/i-have-a-question-about-a-specific-atom-community-package-where-is-the-best-place-to-ask-it.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"I\u2019m getting an error about a \u201Cself-signed certificate\u201D. What do I do?",slug:"i-m-getting-an-error-about-a-self-signed-certificate-what-do-i-do",link:"#i-m-getting-an-error-about-a-self-signed-certificate-what-do-i-do",children:[]}],path:"/docs/atom-archive/faq/sections/i-m-getting-an-error-about-a-self-signed-certificate-what-do-i-do.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"I\u2019m having a problem with Julia! What do I do?",slug:"i-m-having-a-problem-with-julia-what-do-i-do",link:"#i-m-having-a-problem-with-julia-what-do-i-do",children:[]}],path:"/docs/atom-archive/faq/sections/i-m-having-a-problem-with-julia-what-do-i-do.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"I\u2019m having a problem with PlatformIO! What do I do?",slug:"i-m-having-a-problem-with-platformio-what-do-i-do",link:"#i-m-having-a-problem-with-platformio-what-do-i-do",children:[]}],path:"/docs/atom-archive/faq/sections/i-m-having-a-problem-with-platformio-what-do-i-do.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"I\u2019m trying to change my syntax colors from styles.less, but it isn\u2019t working!",slug:"i-m-trying-to-change-my-syntax-colors-from-styles-less-but-it-isn-t-working",link:"#i-m-trying-to-change-my-syntax-colors-from-styles-less-but-it-isn-t-working",children:[]}],path:"/docs/atom-archive/faq/sections/i-m-trying-to-change-my-syntax-colors-from-styles-less-but-it-isn-t-working.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"I\u2019m using an international keyboard and keys that use AltGr or Ctrl+Alt aren\u2019t working",slug:"i-m-using-an-international-keyboard-and-keys-that-use-altgr-or-ctrl-alt-aren-t-working",link:"#i-m-using-an-international-keyboard-and-keys-that-use-altgr-or-ctrl-alt-aren-t-working",children:[]}],path:"/docs/atom-archive/faq/sections/i-m-using-an-international-keyboard-and-keys-that-use-altgr-or-ctrl-alt-aren-t-working.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"Is Atom open source?",slug:"is-atom-open-source",link:"#is-atom-open-source",children:[]}],path:"/docs/atom-archive/faq/sections/is-atom-open-source.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"MacOS Mojave font rendering change",slug:"macos-mojave-font-rendering-change",link:"#macos-mojave-font-rendering-change",children:[]}],path:"/docs/atom-archive/faq/sections/macos-mojave-font-rendering-change.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"The menu bar disappeared, how do I get it back?",slug:"the-menu-bar-disappeared-how-do-i-get-it-back",link:"#the-menu-bar-disappeared-how-do-i-get-it-back",children:[]}],path:"/docs/atom-archive/faq/sections/the-menu-bar-disappeared-how-do-i-get-it-back.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"What does Atom cost?",slug:"what-does-atom-cost",link:"#what-does-atom-cost",children:[]}],path:"/docs/atom-archive/faq/sections/what-does-atom-cost.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"What does Safe Mode do?",slug:"what-does-safe-mode-do",link:"#what-does-safe-mode-do",children:[]}],path:"/docs/atom-archive/faq/sections/what-does-safe-mode-do.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"What is this line on the right in the editor view?",slug:"what-is-this-line-on-the-right-in-the-editor-view",link:"#what-is-this-line-on-the-right-in-the-editor-view",children:[]}],path:"/docs/atom-archive/faq/sections/what-is-this-line-on-the-right-in-the-editor-view.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"What platforms does Atom run on?",slug:"what-platforms-does-atom-run-on",link:"#what-platforms-does-atom-run-on",children:[]}],path:"/docs/atom-archive/faq/sections/what-platforms-does-atom-run-on.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"What's the difference between an IDE and an editor?",slug:"what-s-the-difference-between-an-ide-and-an-editor",link:"#what-s-the-difference-between-an-ide-and-an-editor",children:[]}],path:"/docs/atom-archive/faq/sections/what-s-the-difference-between-an-ide-and-an-editor.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"Why does Atom collect usage data?",slug:"why-does-atom-collect-usage-data",link:"#why-does-atom-collect-usage-data",children:[]}],path:"/docs/atom-archive/faq/sections/why-does-atom-collect-usage-data.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"Why does macOS say that Atom wants to access my calendar, contacts, photos, etc.?",slug:"why-does-macos-say-that-atom-wants-to-access-my-calendar-contacts-photos-etc",link:"#why-does-macos-say-that-atom-wants-to-access-my-calendar-contacts-photos-etc",children:[]}],path:"/docs/atom-archive/faq/sections/why-does-macos-say-that-atom-wants-to-access-my-calendar-contacts-photos-etc.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"Why is Atom deleting trailing whitespace? Why is there a newline at the end of the file?",slug:"why-is-atom-deleting-trailing-whitespace-why-is-there-a-newline-at-the-end-of-the-file",link:"#why-is-atom-deleting-trailing-whitespace-why-is-there-a-newline-at-the-end-of-the-file",children:[]}],path:"/docs/atom-archive/faq/sections/why-is-atom-deleting-trailing-whitespace-why-is-there-a-newline-at-the-end-of-the-file.html",pathLocale:"/",extraFields:[]},{title:"",headers:[],path:"/docs/atom-archive/getting-started/sections/atom-basics.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"Installing Atom",slug:"installing-atom",link:"#installing-atom",children:[]}],path:"/docs/atom-archive/getting-started/sections/installing-atom.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"Summary",slug:"summary",link:"#summary",children:[]}],path:"/docs/atom-archive/getting-started/sections/summary.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"Why Atom?",slug:"why-atom",link:"#why-atom",children:[]}],path:"/docs/atom-archive/getting-started/sections/why-atom.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"Contributing to Official Atom Packages",slug:"contributing-to-official-atom-packages",link:"#contributing-to-official-atom-packages",children:[]}],path:"/docs/atom-archive/hacking-atom/sections/contributing-to-official-atom-packages.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"Converting from TextMate",slug:"converting-from-textmate",link:"#converting-from-textmate",children:[]}],path:"/docs/atom-archive/hacking-atom/sections/converting-from-textmate.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:2,title:"Creating a Fork of a Core Package in atom/atom",slug:"creating-a-fork-of-a-core-package-in-atom-atom",link:"#creating-a-fork-of-a-core-package-in-atom-atom",children:[{level:3,title:"Creating Your New Package",slug:"creating-your-new-package",link:"#creating-your-new-package",children:[]},{level:3,title:"Merging Upstream Changes into Your Package",slug:"merging-upstream-changes-into-your-package",link:"#merging-upstream-changes-into-your-package",children:[]}]}],path:"/docs/atom-archive/hacking-atom/sections/creating-a-fork-of-a-core-package-in-atom-atom.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"Creating a Grammar",slug:"creating-a-grammar",link:"#creating-a-grammar",children:[]}],path:"/docs/atom-archive/hacking-atom/sections/creating-a-grammar.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"Creating a Legacy TextMate Grammar",slug:"creating-a-legacy-textmate-grammar",link:"#creating-a-legacy-textmate-grammar",children:[]}],path:"/docs/atom-archive/hacking-atom/sections/creating-a-legacy-textmate-grammar.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"Creating a Theme",slug:"creating-a-theme",link:"#creating-a-theme",children:[]}],path:"/docs/atom-archive/hacking-atom/sections/creating-a-theme.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"Cross-Platform Compatibility",slug:"cross-platform-compatibility",link:"#cross-platform-compatibility",children:[]}],path:"/docs/atom-archive/hacking-atom/sections/cross-platform-compatibility.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"Debugging",slug:"debugging",link:"#debugging",children:[]}],path:"/docs/atom-archive/hacking-atom/sections/debugging.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"Hacking on Atom Core",slug:"hacking-on-atom-core",link:"#hacking-on-atom-core",children:[]}],path:"/docs/atom-archive/hacking-atom/sections/hacking-on-atom-core.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"Handling URIs",slug:"handling-uris",link:"#handling-uris",children:[]},{level:3,title:"Core URIs",slug:"core-uris",link:"#core-uris",children:[]}],path:"/docs/atom-archive/hacking-atom/sections/handling-uris.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"Iconography",slug:"iconography",link:"#iconography",children:[]}],path:"/docs/atom-archive/hacking-atom/sections/iconography.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"Maintaining a Fork of a Core Package in atom/atom",slug:"maintaining-a-fork-of-a-core-package-in-atom-atom",link:"#maintaining-a-fork-of-a-core-package-in-atom-atom",children:[]}],path:"/docs/atom-archive/hacking-atom/sections/maintaining-a-fork-of-a-core-package-in-atom-atom.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"Package: Active Editor Info",slug:"package-active-editor-info",link:"#package-active-editor-info",children:[]}],path:"/docs/atom-archive/hacking-atom/sections/package-active-editor-info.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"Package: Modifying Text",slug:"package-modifying-text",link:"#package-modifying-text",children:[]}],path:"/docs/atom-archive/hacking-atom/sections/package-modifying-text.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"Package: Word Count",slug:"package-word-count",link:"#package-word-count",children:[]}],path:"/docs/atom-archive/hacking-atom/sections/package-word-count.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"Publishing",slug:"publishing",link:"#publishing",children:[]}],path:"/docs/atom-archive/hacking-atom/sections/publishing.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"Summary",slug:"summary",link:"#summary",children:[]}],path:"/docs/atom-archive/hacking-atom/sections/summary.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"The Init File",slug:"the-init-file",link:"#the-init-file",children:[]}],path:"/docs/atom-archive/hacking-atom/sections/the-init-file.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"Tools of the Trade",slug:"tools-of-the-trade",link:"#tools-of-the-trade",children:[]}],path:"/docs/atom-archive/hacking-atom/sections/tools-of-the-trade.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"Writing Specs",slug:"writing-specs",link:"#writing-specs",children:[]}],path:"/docs/atom-archive/hacking-atom/sections/writing-specs.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"Glossary",slug:"glossary",link:"#glossary",children:[]}],path:"/docs/atom-archive/resources/sections/glossary.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"Removing Shadow DOM styles",slug:"removing-shadow-dom-styles",link:"#removing-shadow-dom-styles",children:[]}],path:"/docs/atom-archive/shadow-dom/sections/removing-shadow-dom-styles.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"Upgrading Your Package",slug:"upgrading-your-package",link:"#upgrading-your-package",children:[]}],path:"/docs/atom-archive/upgrading-to-1-0-apis/sections/upgrading-your-package.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"Upgrading Your Syntax Theme",slug:"upgrading-your-syntax-theme",link:"#upgrading-your-syntax-theme",children:[]}],path:"/docs/atom-archive/upgrading-to-1-0-apis/sections/upgrading-your-syntax-theme.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"Upgrading Your UI Theme Or Package Selectors",slug:"upgrading-your-ui-theme-or-package-selectors",link:"#upgrading-your-ui-theme-or-package-selectors",children:[]}],path:"/docs/atom-archive/upgrading-to-1-0-apis/sections/upgrading-your-ui-theme-or-package-selectors.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"Atom Packages",slug:"atom-packages",link:"#atom-packages",children:[]}],path:"/docs/atom-archive/using-atom/sections/atom-packages.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"Atom Selections",slug:"atom-selections",link:"#atom-selections",children:[]}],path:"/docs/atom-archive/using-atom/sections/atom-selections.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"Autocomplete",slug:"autocomplete",link:"#autocomplete",children:[]}],path:"/docs/atom-archive/using-atom/sections/autocomplete.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"Basic Customization",slug:"basic-customization",link:"#basic-customization",children:[]}],path:"/docs/atom-archive/using-atom/sections/basic-customization.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"Editing and Deleting Text",slug:"editing-and-deleting-text",link:"#editing-and-deleting-text",children:[]}],path:"/docs/atom-archive/using-atom/sections/editing-and-deleting-text.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"Find and Replace",slug:"find-and-replace",link:"#find-and-replace",children:[]}],path:"/docs/atom-archive/using-atom/sections/find-and-replace.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"Folding",slug:"folding",link:"#folding",children:[]}],path:"/docs/atom-archive/using-atom/sections/folding.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"GitHub package",slug:"github-package",link:"#github-package",children:[]}],path:"/docs/atom-archive/using-atom/sections/github-package.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"Grammar",slug:"grammar",link:"#grammar",children:[]}],path:"/docs/atom-archive/using-atom/sections/grammar.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"Moving in Atom",slug:"moving-in-atom",link:"#moving-in-atom",children:[]}],path:"/docs/atom-archive/using-atom/sections/moving-in-atom.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"Panes",slug:"panes",link:"#panes",children:[]}],path:"/docs/atom-archive/using-atom/sections/panes.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"Pending Pane Items",slug:"pending-pane-items",link:"#pending-pane-items",children:[]}],path:"/docs/atom-archive/using-atom/sections/pending-pane-items.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"Snippets",slug:"snippets",link:"#snippets",children:[]}],path:"/docs/atom-archive/using-atom/sections/snippets.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"Summary",slug:"summary",link:"#summary",children:[]}],path:"/docs/atom-archive/using-atom/sections/summary.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"Version Control in Atom",slug:"version-control-in-atom",link:"#version-control-in-atom",children:[]}],path:"/docs/atom-archive/using-atom/sections/version-control-in-atom.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"Writing in Atom",slug:"writing-in-atom",link:"#writing-in-atom",children:[]}],path:"/docs/atom-archive/using-atom/sections/writing-in-atom.html",pathLocale:"/",extraFields:[]},{title:"Behind Pulsar",headers:[{level:2,title:"Configuration API",slug:"configuration-api",link:"#configuration-api",children:[{level:3,title:"Reading Config Settings",slug:"reading-config-settings",link:"#reading-config-settings",children:[]},{level:3,title:"Writing Config Settings",slug:"writing-config-settings",link:"#writing-config-settings",children:[]}]},{level:2,title:"Keymaps In-Depth",slug:"keymaps-in-depth",link:"#keymaps-in-depth",children:[{level:3,title:"Structure of a Keymap File",slug:"structure-of-a-keymap-file",link:"#structure-of-a-keymap-file",children:[]},{level:3,title:"Removing Bindings",slug:"removing-bindings",link:"#removing-bindings",children:[]},{level:3,title:"Forcing Chromium's Native Keystroke Handling",slug:"forcing-chromium-s-native-keystroke-handling",link:"#forcing-chromium-s-native-keystroke-handling",children:[]},{level:3,title:"Overloading Key Bindings",slug:"overloading-key-bindings",link:"#overloading-key-bindings",children:[]},{level:3,title:"Step-by-Step: How Keydown Events are Mapped to Commands",slug:"step-by-step-how-keydown-events-are-mapped-to-commands",link:"#step-by-step-how-keydown-events-are-mapped-to-commands",children:[]},{level:3,title:"Overriding Pulsar's Keyboard Layout Recognition",slug:"overriding-pulsar-s-keyboard-layout-recognition",link:"#overriding-pulsar-s-keyboard-layout-recognition",children:[]}]},{level:2,title:"Scoped Settings, Scopes and Scope Descriptors",slug:"scoped-settings-scopes-and-scope-descriptors",link:"#scoped-settings-scopes-and-scope-descriptors",children:[{level:3,title:"Scope Names in Syntax Tokens",slug:"scope-names-in-syntax-tokens",link:"#scope-names-in-syntax-tokens",children:[]},{level:3,title:"Scope Selectors",slug:"scope-selectors",link:"#scope-selectors",children:[]},{level:3,title:"Scope Descriptors",slug:"scope-descriptors",link:"#scope-descriptors",children:[]}]},{level:2,title:"Serialization in Pulsar",slug:"serialization-in-pulsar",link:"#serialization-in-pulsar",children:[{level:3,title:"Package Serialization Hook",slug:"package-serialization-hook",link:"#package-serialization-hook",children:[]},{level:3,title:"Serialization Methods",slug:"serialization-methods",link:"#serialization-methods",children:[]},{level:3,title:"Versioning",slug:"versioning",link:"#versioning",children:[]}]},{level:2,title:"Developing Node Modules",slug:"developing-node-modules",link:"#developing-node-modules",children:[{level:3,title:"Linking a Node Module Into Your Pulsar Dev Environment",slug:"linking-a-node-module-into-your-pulsar-dev-environment",link:"#linking-a-node-module-into-your-pulsar-dev-environment",children:[]}]},{level:2,title:"Interacting With Other Packages Via Services",slug:"interacting-with-other-packages-via-services",link:"#interacting-with-other-packages-via-services",children:[]},{level:2,title:"Maintaining Your Packages",slug:"maintaining-your-packages",link:"#maintaining-your-packages",children:[{level:3,title:"Publishing a Package Manually",slug:"publishing-a-package-manually",link:"#publishing-a-package-manually",children:[]},{level:3,title:"Adding a Collaborator",slug:"adding-a-collaborator",link:"#adding-a-collaborator",children:[]},{level:3,title:"Transferring Ownership",slug:"transferring-ownership",link:"#transferring-ownership",children:[]},{level:3,title:"Unpublish Your Package",slug:"unpublish-your-package",link:"#unpublish-your-package",children:[]},{level:3,title:"Rename Your Package",slug:"rename-your-package",link:"#rename-your-package",children:[]}]},{level:2,title:"Summary",slug:"summary",link:"#summary",children:[]}],path:"/docs/launch-manual/sections/behind-pulsar/",pathLocale:"/",extraFields:[]},{title:"Hacking the Core",headers:[{level:2,title:"Building Pulsar",slug:"building-pulsar",link:"#building-pulsar",children:[{level:3,title:"Requirements and dependencies",slug:"requirements-and-dependencies",link:"#requirements-and-dependencies",children:[]},{level:3,title:"Building and running the application",slug:"building-and-running-the-application",link:"#building-and-running-the-application",children:[]},{level:3,title:"Building binaries",slug:"building-binaries",link:"#building-binaries",children:[]}]},{level:2,title:"Using ppm (Pulsar Package Manager)",slug:"using-ppm-pulsar-package-manager",link:"#using-ppm-pulsar-package-manager",children:[]},{level:2,title:"Hacking on the Core",slug:"hacking-on-the-core",link:"#hacking-on-the-core",children:[{level:3,title:"Running in Development Mode",slug:"running-in-development-mode",link:"#running-in-development-mode",children:[]},{level:3,title:"Running Pulsar Core Tests Locally",slug:"running-pulsar-core-tests-locally",link:"#running-pulsar-core-tests-locally",children:[]}]},{level:2,title:"Tools of the Trade",slug:"tools-of-the-trade",link:"#tools-of-the-trade",children:[]},{level:2,title:"The Init File",slug:"the-init-file",link:"#the-init-file",children:[]},{level:2,title:"Package: Word Count",slug:"package-word-count",link:"#package-word-count",children:[{level:3,title:"Package Generator",slug:"package-generator",link:"#package-generator",children:[]},{level:3,title:"Developing Our Package",slug:"developing-our-package",link:"#developing-our-package",children:[]},{level:3,title:"Basic Debugging",slug:"basic-debugging",link:"#basic-debugging",children:[]},{level:3,title:"Testing",slug:"testing",link:"#testing",children:[]},{level:3,title:"Summary",slug:"summary",link:"#summary",children:[]}]},{level:2,title:"Package: Modifying Text",slug:"package-modifying-text",link:"#package-modifying-text",children:[{level:3,title:"Basic Text Insertion",slug:"basic-text-insertion",link:"#basic-text-insertion",children:[]},{level:3,title:"Add the ASCII Art",slug:"add-the-ascii-art",link:"#add-the-ascii-art",children:[]},{level:3,title:"Summary",slug:"summary-1",link:"#summary-1",children:[]}]},{level:2,title:"Package: Active Editor Info",slug:"package-active-editor-info",link:"#package-active-editor-info",children:[{level:3,title:"Create the Package",slug:"create-the-package",link:"#create-the-package",children:[]},{level:3,title:"Add an Opener",slug:"add-an-opener",link:"#add-an-opener",children:[]},{level:3,title:"Updating the View",slug:"updating-the-view",link:"#updating-the-view",children:[]},{level:3,title:"Constraining Our Item's Locations",slug:"constraining-our-item-s-locations",link:"#constraining-our-item-s-locations",children:[]},{level:3,title:"Show Active Editor Info",slug:"show-active-editor-info",link:"#show-active-editor-info",children:[]},{level:3,title:"Serialization",slug:"serialization",link:"#serialization",children:[]},{level:3,title:"Summary",slug:"summary-2",link:"#summary-2",children:[]}]},{level:2,title:"Creating a Theme",slug:"creating-a-theme",link:"#creating-a-theme",children:[{level:3,title:"Getting Started",slug:"getting-started",link:"#getting-started",children:[]},{level:3,title:"Creating a Syntax Theme",slug:"creating-a-syntax-theme",link:"#creating-a-syntax-theme",children:[]},{level:3,title:"Creating a UI Theme",slug:"creating-a-ui-theme",link:"#creating-a-ui-theme",children:[]},{level:3,title:"Theme Variables",slug:"theme-variables",link:"#theme-variables",children:[]},{level:3,title:"Development workflow",slug:"development-workflow",link:"#development-workflow",children:[]},{level:3,title:"Publish your theme",slug:"publish-your-theme",link:"#publish-your-theme",children:[]}]},{level:2,title:"Creating a Grammar",slug:"creating-a-grammar",link:"#creating-a-grammar",children:[{level:3,title:"Getting Started",slug:"getting-started-1",link:"#getting-started-1",children:[]},{level:3,title:"The Parser",slug:"the-parser",link:"#the-parser",children:[]},{level:3,title:"The Package",slug:"the-package",link:"#the-package",children:[]},{level:3,title:"The Grammar File",slug:"the-grammar-file",link:"#the-grammar-file",children:[]},{level:3,title:"Basic Fields",slug:"basic-fields",link:"#basic-fields",children:[]},{level:3,title:"Language Recognition",slug:"language-recognition",link:"#language-recognition",children:[]},{level:3,title:"Syntax Highlighting",slug:"syntax-highlighting",link:"#syntax-highlighting",children:[]},{level:3,title:"Language Injection",slug:"language-injection",link:"#language-injection",children:[]},{level:3,title:"Code Folding",slug:"code-folding",link:"#code-folding",children:[]},{level:3,title:"Comments",slug:"comments",link:"#comments",children:[]},{level:3,title:"Example Packages",slug:"example-packages",link:"#example-packages",children:[]}]},{level:2,title:"Creating a Legacy TextMate Grammar",slug:"creating-a-legacy-textmate-grammar",link:"#creating-a-legacy-textmate-grammar",children:[{level:3,title:"Getting Started",slug:"getting-started-2",link:"#getting-started-2",children:[]},{level:3,title:"Create the Package",slug:"create-the-package-1",link:"#create-the-package-1",children:[]},{level:3,title:"Adding Patterns",slug:"adding-patterns",link:"#adding-patterns",children:[]},{level:3,title:"Begin/End Patterns",slug:"begin-end-patterns",link:"#begin-end-patterns",children:[]},{level:3,title:"Repositories and the Include keyword, or how to avoid duplication",slug:"repositories-and-the-include-keyword-or-how-to-avoid-duplication",link:"#repositories-and-the-include-keyword-or-how-to-avoid-duplication",children:[]},{level:3,title:"Where to Go from Here",slug:"where-to-go-from-here",link:"#where-to-go-from-here",children:[]}]},{level:2,title:"Converting from TextMate",slug:"converting-from-textmate",link:"#converting-from-textmate",children:[{level:3,title:"Converting a TextMate Grammar Bundle",slug:"converting-a-textmate-grammar-bundle",link:"#converting-a-textmate-grammar-bundle",children:[]},{level:3,title:"Converting a TextMate Syntax Theme",slug:"converting-a-textmate-syntax-theme",link:"#converting-a-textmate-syntax-theme",children:[]}]},{level:2,title:"Publishing",slug:"publishing",link:"#publishing",children:[{level:3,title:"Prepare Your Package",slug:"prepare-your-package",link:"#prepare-your-package",children:[]},{level:3,title:"Publish Your Package",slug:"publish-your-package",link:"#publish-your-package",children:[]}]},{level:2,title:"Iconography",slug:"iconography",link:"#iconography",children:[{level:3,title:"Overview",slug:"overview",link:"#overview",children:[]},{level:3,title:"Usage",slug:"usage",link:"#usage",children:[]},{level:3,title:"Size",slug:"size",link:"#size",children:[]},{level:3,title:"Usability",slug:"usability",link:"#usability",children:[]}]},{level:2,title:"Debugging",slug:"debugging",link:"#debugging",children:[{level:3,title:"Update to the Latest Version",slug:"update-to-the-latest-version",link:"#update-to-the-latest-version",children:[]},{level:3,title:"Using Safe Mode",slug:"using-safe-mode",link:"#using-safe-mode",children:[]},{level:3,title:"Clearing Saved State",slug:"clearing-saved-state",link:"#clearing-saved-state",children:[]},{level:3,title:"Reset to Factory Defaults",slug:"reset-to-factory-defaults",link:"#reset-to-factory-defaults",children:[]},{level:3,title:"Check for Linked Packages",slug:"check-for-linked-packages",link:"#check-for-linked-packages",children:[]},{level:3,title:"Check for Incompatible Packages",slug:"check-for-incompatible-packages",link:"#check-for-incompatible-packages",children:[]},{level:3,title:"Check Pulsar and Package Settings",slug:"check-pulsar-and-package-settings",link:"#check-pulsar-and-package-settings",children:[]},{level:3,title:"Check Your Configuration",slug:"check-your-configuration",link:"#check-your-configuration",children:[]},{level:3,title:"Check Your Keybindings",slug:"check-your-keybindings",link:"#check-your-keybindings",children:[]},{level:3,title:"Check Font Rendering Issues",slug:"check-font-rendering-issues",link:"#check-font-rendering-issues",children:[]},{level:3,title:"Check for Errors in the Developer Tools",slug:"check-for-errors-in-the-developer-tools",link:"#check-for-errors-in-the-developer-tools",children:[]},{level:3,title:"Find Crash Logs",slug:"find-crash-logs",link:"#find-crash-logs",children:[]},{level:3,title:"Diagnose Startup Performance",slug:"diagnose-startup-performance",link:"#diagnose-startup-performance",children:[]},{level:3,title:"Diagnose Runtime Performance",slug:"diagnose-runtime-performance",link:"#diagnose-runtime-performance",children:[]},{level:3,title:"Profiling Startup Performance",slug:"profiling-startup-performance",link:"#profiling-startup-performance",children:[]},{level:3,title:"Check Your Build Tools",slug:"check-your-build-tools",link:"#check-your-build-tools",children:[]},{level:3,title:"Check if your GPU is causing the problem",slug:"check-if-your-gpu-is-causing-the-problem",link:"#check-if-your-gpu-is-causing-the-problem",children:[]}]},{level:2,title:"Writing Specs",slug:"writing-specs",link:"#writing-specs",children:[{level:3,title:"Create a New Spec",slug:"create-a-new-spec",link:"#create-a-new-spec",children:[]},{level:3,title:"Asynchronous Specs",slug:"asynchronous-specs",link:"#asynchronous-specs",children:[]},{level:3,title:"Running Specs",slug:"running-specs",link:"#running-specs",children:[]},{level:3,title:"Customizing your test runner",slug:"customizing-your-test-runner",link:"#customizing-your-test-runner",children:[]}]},{level:2,title:"Handling URIs",slug:"handling-uris",link:"#handling-uris",children:[{level:3,title:"Modifying your package.json",slug:"modifying-your-package-json",link:"#modifying-your-package-json",children:[]},{level:3,title:"Modifying your Main Module",slug:"modifying-your-main-module",link:"#modifying-your-main-module",children:[]},{level:3,title:"Controlling Activation Deferral",slug:"controlling-activation-deferral",link:"#controlling-activation-deferral",children:[]},{level:3,title:"Linux Support",slug:"linux-support",link:"#linux-support",children:[]}]},{level:2,title:"Core URIs",slug:"core-uris",link:"#core-uris",children:[{level:3,title:"Cross-Platform Compatibility",slug:"cross-platform-compatibility",link:"#cross-platform-compatibility",children:[]}]},{level:2,title:"Contributing to Official Pulsar Packages",slug:"contributing-to-official-pulsar-packages",link:"#contributing-to-official-pulsar-packages",children:[{level:3,title:"Hacking on Packages",slug:"hacking-on-packages",link:"#hacking-on-packages",children:[]}]},{level:2,title:"Creating a Fork of a Core Package",slug:"creating-a-fork-of-a-core-package",link:"#creating-a-fork-of-a-core-package",children:[{level:3,title:"Creating Your New Package",slug:"creating-your-new-package",link:"#creating-your-new-package",children:[]},{level:3,title:"Merging Upstream Changes into Your Package",slug:"merging-upstream-changes-into-your-package",link:"#merging-upstream-changes-into-your-package",children:[]}]},{level:2,title:"Maintaining a Fork of a Core Package",slug:"maintaining-a-fork-of-a-core-package",link:"#maintaining-a-fork-of-a-core-package",children:[{level:3,title:"Step-by-step guide",slug:"step-by-step-guide",link:"#step-by-step-guide",children:[]}]},{level:2,title:"Summary",slug:"summary-3",link:"#summary-3",children:[]},{level:2,title:"Having trouble?",slug:"having-trouble",link:"#having-trouble",children:[]}],path:"/docs/launch-manual/sections/core-hacking/",pathLocale:"/",extraFields:[]},{title:"FAQ",headers:[{level:2,title:"Having trouble?",slug:"having-trouble",link:"#having-trouble",children:[]},{level:2,title:"Common Issues",slug:"common-issues",link:"#common-issues",children:[{level:3,title:'macOS error "App is damaged and can\u2019t be opened"',slug:"macos-error-app-is-damaged-and-can-t-be-opened",link:"#macos-error-app-is-damaged-and-can-t-be-opened",children:[]},{level:3,title:`Pulsar does not launch "GPU process isn't usable. Goodbye"`,slug:"pulsar-does-not-launch-gpu-process-isn-t-usable-goodbye",link:"#pulsar-does-not-launch-gpu-process-isn-t-usable-goodbye",children:[]}]}],path:"/docs/launch-manual/sections/faq/",pathLocale:"/",extraFields:[]},{title:"Getting Started",headers:[{level:2,title:"Why Pulsar?",slug:"why-pulsar",link:"#why-pulsar",children:[{level:3,title:"The Nucleus of Pulsar",slug:"the-nucleus-of-pulsar",link:"#the-nucleus-of-pulsar",children:[]},{level:3,title:"The Native Web",slug:"the-native-web",link:"#the-native-web",children:[]},{level:3,title:"JavaScript, Meet C++",slug:"javascript-meet-c",link:"#javascript-meet-c",children:[]},{level:3,title:"Web Tech: The Fun Parts",slug:"web-tech-the-fun-parts",link:"#web-tech-the-fun-parts",children:[]},{level:3,title:"An Open-Source Text Editor",slug:"an-open-source-text-editor",link:"#an-open-source-text-editor",children:[]}]},{level:2,title:"Installing Pulsar",slug:"installing-pulsar",link:"#installing-pulsar",children:[{level:3,title:"Universal releases",slug:"universal-releases",link:"#universal-releases",children:[]},{level:3,title:"Debian/Ubuntu based distributions",slug:"debian-ubuntu-based-distributions",link:"#debian-ubuntu-based-distributions",children:[]},{level:3,title:"Fedora/RHEL based distributions",slug:"fedora-rhel-based-distributions",link:"#fedora-rhel-based-distributions",children:[]},{level:3,title:"Updating Pulsar",slug:"updating-pulsar",link:"#updating-pulsar",children:[]},{level:3,title:"Portable Mode",slug:"portable-mode",link:"#portable-mode",children:[]},{level:3,title:"Building Pulsar from Source",slug:"building-pulsar-from-source",link:"#building-pulsar-from-source",children:[]},{level:3,title:"Proxy and Firewall Settings",slug:"proxy-and-firewall-settings",link:"#proxy-and-firewall-settings",children:[]}]},{level:2,title:"Pulsar Basics",slug:"pulsar-basics",link:"#pulsar-basics",children:[{level:3,title:"Terminology",slug:"terminology",link:"#terminology",children:[]},{level:3,title:"Command Palette",slug:"command-palette",link:"#command-palette",children:[]},{level:3,title:"Settings and Preferences",slug:"settings-and-preferences",link:"#settings-and-preferences",children:[]},{level:3,title:"Opening, Modifying, and Saving Files",slug:"opening-modifying-and-saving-files",link:"#opening-modifying-and-saving-files",children:[]},{level:3,title:"Opening Directories",slug:"opening-directories",link:"#opening-directories",children:[]}]},{level:2,title:"Summary",slug:"summary",link:"#summary",children:[]}],path:"/docs/launch-manual/sections/getting-started/",pathLocale:"/",extraFields:[]},{title:"Using Pulsar",headers:[{level:2,title:"Pulsar Packages",slug:"pulsar-packages",link:"#pulsar-packages",children:[{level:3,title:"Package Settings",slug:"package-settings",link:"#package-settings",children:[]},{level:3,title:"Pulsar Themes",slug:"pulsar-themes",link:"#pulsar-themes",children:[]},{level:3,title:"Command Line",slug:"command-line",link:"#command-line",children:[]}]},{level:2,title:"Moving in Pulsar",slug:"moving-in-pulsar",link:"#moving-in-pulsar",children:[{level:3,title:"Additional Movement and Selection Commands",slug:"additional-movement-and-selection-commands",link:"#additional-movement-and-selection-commands",children:[]},{level:3,title:"Navigating by Symbols",slug:"navigating-by-symbols",link:"#navigating-by-symbols",children:[]},{level:3,title:"Bookmarks",slug:"bookmarks",link:"#bookmarks",children:[]}]},{level:2,title:"Pulsar Selections",slug:"pulsar-selections",link:"#pulsar-selections",children:[]},{level:2,title:"Editing and Deleting Text",slug:"editing-and-deleting-text",link:"#editing-and-deleting-text",children:[{level:3,title:"Basic Manipulation",slug:"basic-manipulation",link:"#basic-manipulation",children:[]},{level:3,title:"Deleting and Cutting",slug:"deleting-and-cutting",link:"#deleting-and-cutting",children:[]},{level:3,title:"Multiple Cursors and Selections",slug:"multiple-cursors-and-selections",link:"#multiple-cursors-and-selections",children:[]},{level:3,title:"Whitespace",slug:"whitespace",link:"#whitespace",children:[]},{level:3,title:"Brackets",slug:"brackets",link:"#brackets",children:[]},{level:3,title:"Encoding",slug:"encoding",link:"#encoding",children:[]}]},{level:2,title:"Find and Replace",slug:"find-and-replace",link:"#find-and-replace",children:[]},{level:2,title:"Snippets",slug:"snippets",link:"#snippets",children:[{level:3,title:"Creating Your Own Snippets",slug:"creating-your-own-snippets",link:"#creating-your-own-snippets",children:[]},{level:3,title:"More Info",slug:"more-info",link:"#more-info",children:[]}]},{level:2,title:"Autocomplete",slug:"autocomplete",link:"#autocomplete",children:[]},{level:2,title:"Folding",slug:"folding",link:"#folding",children:[]},{level:2,title:"Panes",slug:"panes",link:"#panes",children:[]},{level:2,title:"Pending Pane Items",slug:"pending-pane-items",link:"#pending-pane-items",children:[{level:3,title:"Disabling Pending Pane Items",slug:"disabling-pending-pane-items",link:"#disabling-pending-pane-items",children:[]}]},{level:2,title:"Grammar",slug:"grammar",link:"#grammar",children:[]},{level:2,title:"Version Control in Pulsar",slug:"version-control-in-pulsar",link:"#version-control-in-pulsar",children:[{level:3,title:"Checkout HEAD revision",slug:"checkout-head-revision",link:"#checkout-head-revision",children:[]},{level:3,title:"Git status list",slug:"git-status-list",link:"#git-status-list",children:[]},{level:3,title:"Commit editor",slug:"commit-editor",link:"#commit-editor",children:[]},{level:3,title:"Status bar icons",slug:"status-bar-icons",link:"#status-bar-icons",children:[]},{level:3,title:"Line diffs",slug:"line-diffs",link:"#line-diffs",children:[]},{level:3,title:"Open on GitHub",slug:"open-on-github",link:"#open-on-github",children:[]}]},{level:2,title:"GitHub package",slug:"github-package",link:"#github-package",children:[{level:3,title:"Initialize repositories",slug:"initialize-repositories",link:"#initialize-repositories",children:[]},{level:3,title:"Clone repositories",slug:"clone-repositories",link:"#clone-repositories",children:[]},{level:3,title:"Branch",slug:"branch",link:"#branch",children:[]},{level:3,title:"Stage",slug:"stage",link:"#stage",children:[]},{level:3,title:"Discard changes",slug:"discard-changes",link:"#discard-changes",children:[]},{level:3,title:"Commit Preview",slug:"commit-preview",link:"#commit-preview",children:[]},{level:3,title:"Commit",slug:"commit",link:"#commit",children:[]},{level:3,title:"Amend and undo",slug:"amend-and-undo",link:"#amend-and-undo",children:[]},{level:3,title:"View commits",slug:"view-commits",link:"#view-commits",children:[]},{level:3,title:"Publish and push",slug:"publish-and-push",link:"#publish-and-push",children:[]},{level:3,title:"Fetch and pull",slug:"fetch-and-pull",link:"#fetch-and-pull",children:[]},{level:3,title:"Resolve conflicts",slug:"resolve-conflicts",link:"#resolve-conflicts",children:[]},{level:3,title:"Create a Pull Request",slug:"create-a-pull-request",link:"#create-a-pull-request",children:[]},{level:3,title:"View Pull Requests",slug:"view-pull-requests",link:"#view-pull-requests",children:[]},{level:3,title:"Open any Issue or Pull Request",slug:"open-any-issue-or-pull-request",link:"#open-any-issue-or-pull-request",children:[]},{level:3,title:"Checkout a Pull Request",slug:"checkout-a-pull-request",link:"#checkout-a-pull-request",children:[]},{level:3,title:"View Pull Request review comments",slug:"view-pull-request-review-comments",link:"#view-pull-request-review-comments",children:[]},{level:3,title:"Navigate Pull Request review comments",slug:"navigate-pull-request-review-comments",link:"#navigate-pull-request-review-comments",children:[]},{level:3,title:"Respond to a Pull Request review comment",slug:"respond-to-a-pull-request-review-comment",link:"#respond-to-a-pull-request-review-comment",children:[]}]},{level:2,title:"Writing in Pulsar",slug:"writing-in-pulsar",link:"#writing-in-pulsar",children:[{level:3,title:"Spell Checking",slug:"spell-checking",link:"#spell-checking",children:[]},{level:3,title:"Previews",slug:"previews",link:"#previews",children:[]},{level:3,title:"Snippets",slug:"snippets-1",link:"#snippets-1",children:[]}]},{level:2,title:"Basic Customization",slug:"basic-customization",link:"#basic-customization",children:[{level:3,title:"Configuring with CSON",slug:"configuring-with-cson",link:"#configuring-with-cson",children:[]},{level:3,title:"Style Tweaks",slug:"style-tweaks",link:"#style-tweaks",children:[]},{level:3,title:"Customizing Keybindings",slug:"customizing-keybindings",link:"#customizing-keybindings",children:[]},{level:3,title:"Global Configuration Settings",slug:"global-configuration-settings",link:"#global-configuration-settings",children:[]},{level:3,title:"Language Specific Configuration Settings",slug:"language-specific-configuration-settings",link:"#language-specific-configuration-settings",children:[]},{level:3,title:"Customizing Language Recognition",slug:"customizing-language-recognition",link:"#customizing-language-recognition",children:[]},{level:3,title:"Controlling Where Customization is Stored to Simplify Your Workflow",slug:"controlling-where-customization-is-stored-to-simplify-your-workflow",link:"#controlling-where-customization-is-stored-to-simplify-your-workflow",children:[]}]},{level:2,title:"Summary",slug:"summary",link:"#summary",children:[]}],path:"/docs/launch-manual/sections/using-pulsar/",pathLocale:"/",extraFields:[]},{title:"Atom-LanguageClient",headers:[],path:"/docs/packages/core/atom-languageclient/",pathLocale:"/",extraFields:[]},{title:"List of Pulsar Packages using Pulsar LanguageClient",headers:[{level:2,title:"Official Packages",slug:"official-packages",link:"#official-packages",children:[]},{level:2,title:"Community Packages",slug:"community-packages",link:"#community-packages",children:[]}],path:"/docs/packages/core/atom-languageclient/list.html",pathLocale:"/",extraFields:[]},{title:"Release Process",headers:[{level:2,title:"Steps",slug:"steps",link:"#steps",children:[]}],path:"/docs/packages/core/atom-languageclient/release-process.html",pathLocale:"/",extraFields:[]},{title:"Autocomplete Providers",headers:[{level:2,title:"Built-In Providers",slug:"built-in-providers",link:"#built-in-providers",children:[]},{level:2,title:"Providers For Built-In Grammars",slug:"providers-for-built-in-grammars",link:"#providers-for-built-in-grammars",children:[]},{level:2,title:"Providers For Third-Party Grammars",slug:"providers-for-third-party-grammars",link:"#providers-for-third-party-grammars",children:[]},{level:2,title:"Providers Not Tied To A Specific Grammar",slug:"providers-not-tied-to-a-specific-grammar",link:"#providers-not-tied-to-a-specific-grammar",children:[]},{level:2,title:"Providers Requested By The Community",slug:"providers-requested-by-the-community",link:"#providers-requested-by-the-community",children:[]},{level:2,title:"Packages That Claim Autocomplete, But Are Not API 1.0.0 Compatible",slug:"packages-that-claim-autocomplete-but-are-not-api-1-0-0-compatible",link:"#packages-that-claim-autocomplete-but-are-not-api-1-0-0-compatible",children:[]},{level:2,title:"Deprecated Providers",slug:"deprecated-providers",link:"#deprecated-providers",children:[]},{level:2,title:"Other Forks Of Autocomplete",slug:"other-forks-of-autocomplete",link:"#other-forks-of-autocomplete",children:[]}],path:"/docs/packages/core/autocomplete-plus/autocomplete-providers.html",pathLocale:"/",extraFields:[]},{title:"Autocomplete-Plus",headers:[],path:"/docs/packages/core/autocomplete-plus/",pathLocale:"/",extraFields:[]},{title:"Provider API",headers:[{level:2,title:"Defining A Provider",slug:"defining-a-provider",link:"#defining-a-provider",children:[]},{level:2,title:"Support For Asynchronous Request Handling",slug:"support-for-asynchronous-request-handling",link:"#support-for-asynchronous-request-handling",children:[]},{level:2,title:"The Suggestion Request's Options Object",slug:"the-suggestion-request-s-options-object",link:"#the-suggestion-request-s-options-object",children:[]},{level:2,title:"Suggestions",slug:"suggestions",link:"#suggestions",children:[]},{level:2,title:"Registering Your Provider With autocomplete+",slug:"registering-your-provider-with-autocomplete",link:"#registering-your-provider-with-autocomplete",children:[{level:3,title:"API 4.0.0",slug:"api-4-0-0",link:"#api-4-0-0",children:[]}]},{level:2,title:"Provider Examples",slug:"provider-examples",link:"#provider-examples",children:[]},{level:2,title:"Tips",slug:"tips",link:"#tips",children:[{level:3,title:"Generating a new prefix",slug:"generating-a-new-prefix",link:"#generating-a-new-prefix",children:[]}]}],path:"/docs/packages/core/autocomplete-plus/provider-api.html",pathLocale:"/",extraFields:[]},{title:"SymbolProvider Config API",headers:[{level:2,title:"Typename Objects",slug:"typename-objects",link:"#typename-objects",children:[]},{level:2,title:"Finding Scope Selectors",slug:"finding-scope-selectors",link:"#finding-scope-selectors",children:[]}],path:"/docs/packages/core/autocomplete-plus/symbolprovider-config-api.html",pathLocale:"/",extraFields:[]},{title:"GitHub",headers:[{level:2,title:"Roadmaps",slug:"roadmaps",link:"#roadmaps",children:[]},{level:2,title:"Monthly Planning",slug:"monthly-planning",link:"#monthly-planning",children:[]}],path:"/docs/packages/core/github/",pathLocale:"/",extraFields:[]},{title:"June 2017 - Monthly Planning",headers:[{level:2,title:"Looking Back",slug:"looking-back",link:"#looking-back",children:[]},{level:2,title:"This Month",slug:"this-month",link:"#this-month",children:[]},{level:2,title:"Other Topics Discussed",slug:"other-topics-discussed",link:"#other-topics-discussed",children:[]}],path:"/docs/packages/core/github/june-2017.html",pathLocale:"/",extraFields:[]},{title:"Incomplete Classpath Warning",headers:[],path:"/docs/packages/core/ide-java/incomplete-classpath-warning.html",pathLocale:"/",extraFields:[]},{title:"IDE-Java",headers:[],path:"/docs/packages/core/ide-java/",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:2,title:"Blog guide",slug:"blog-guide",link:"#blog-guide",children:[{level:3,title:"Writing a new post",slug:"writing-a-new-post",link:"#writing-a-new-post",children:[]},{level:3,title:"Testing locally",slug:"testing-locally",link:"#testing-locally",children:[]},{level:3,title:'Website "Blog" page(s)',slug:"website-blog-page-s",link:"#website-blog-page-s",children:[]},{level:3,title:"Questions? Suggestions",slug:"questions-suggestions",link:"#questions-suggestions",children:[]}]}],path:"/docs/resources/website/sections/blog-guide.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:2,title:"Building the website",slug:"building-the-website",link:"#building-the-website",children:[{level:3,title:"Prerequisites",slug:"prerequisites",link:"#prerequisites",children:[]},{level:3,title:"Clone the repository and install",slug:"clone-the-repository-and-install",link:"#clone-the-repository-and-install",children:[]},{level:3,title:"Running the website",slug:"running-the-website",link:"#running-the-website",children:[]},{level:3,title:"Notes",slug:"notes",link:"#notes",children:[]}]}],path:"/docs/resources/website/sections/building.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:2,title:"Configuration files",slug:"configuration-files",link:"#configuration-files",children:[{level:3,title:"config.js",slug:"config-js",link:"#config-js",children:[]},{level:3,title:"navbar.js",slug:"navbar-js",link:"#navbar-js",children:[]},{level:3,title:"sidebar.js",slug:"sidebar-js",link:"#sidebar-js",children:[]},{level:3,title:"Theme",slug:"theme",link:"#theme",children:[]}]}],path:"/docs/resources/website/sections/configuration-files.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:2,title:"Documentation style & reusable components",slug:"documentation-style-reusable-components",link:"#documentation-style-reusable-components",children:[{level:3,title:"Structure",slug:"structure",link:"#structure",children:[]},{level:3,title:"Links",slug:"links",link:"#links",children:[]},{level:3,title:"Naming",slug:"naming",link:"#naming",children:[]},{level:3,title:"Containers",slug:"containers",link:"#containers",children:[]},{level:3,title:"Writing style",slug:"writing-style",link:"#writing-style",children:[]}]}],path:"/docs/resources/website/sections/document-style.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:2,title:"File Organization",slug:"file-organization",link:"#file-organization",children:[{level:3,title:"index.md",slug:"index-md",link:"#index-md",children:[]},{level:3,title:"Sections",slug:"sections",link:"#sections",children:[]},{level:3,title:"Assets",slug:"assets",link:"#assets",children:[]}]}],path:"/docs/resources/website/sections/file-organization.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:2,title:"Configuration API",slug:"configuration-api",link:"#configuration-api",children:[{level:3,title:"Reading Config Settings",slug:"reading-config-settings",link:"#reading-config-settings",children:[]},{level:3,title:"Writing Config Settings",slug:"writing-config-settings",link:"#writing-config-settings",children:[]}]}],path:"/docs/launch-manual/sections/behind-pulsar/sections/configuration-api.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:2,title:"Developing Node Modules",slug:"developing-node-modules",link:"#developing-node-modules",children:[{level:3,title:"Linking a Node Module Into Your Pulsar Dev Environment",slug:"linking-a-node-module-into-your-pulsar-dev-environment",link:"#linking-a-node-module-into-your-pulsar-dev-environment",children:[]}]}],path:"/docs/launch-manual/sections/behind-pulsar/sections/developing-node-modules.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:2,title:"Interacting With Other Packages Via Services",slug:"interacting-with-other-packages-via-services",link:"#interacting-with-other-packages-via-services",children:[]}],path:"/docs/launch-manual/sections/behind-pulsar/sections/interacting-with-other-packages-via-services.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:2,title:"Keymaps In-Depth",slug:"keymaps-in-depth",link:"#keymaps-in-depth",children:[{level:3,title:"Structure of a Keymap File",slug:"structure-of-a-keymap-file",link:"#structure-of-a-keymap-file",children:[]},{level:3,title:"Removing Bindings",slug:"removing-bindings",link:"#removing-bindings",children:[]},{level:3,title:"Forcing Chromium's Native Keystroke Handling",slug:"forcing-chromium-s-native-keystroke-handling",link:"#forcing-chromium-s-native-keystroke-handling",children:[]},{level:3,title:"Overloading Key Bindings",slug:"overloading-key-bindings",link:"#overloading-key-bindings",children:[]},{level:3,title:"Step-by-Step: How Keydown Events are Mapped to Commands",slug:"step-by-step-how-keydown-events-are-mapped-to-commands",link:"#step-by-step-how-keydown-events-are-mapped-to-commands",children:[]},{level:3,title:"Overriding Pulsar's Keyboard Layout Recognition",slug:"overriding-pulsar-s-keyboard-layout-recognition",link:"#overriding-pulsar-s-keyboard-layout-recognition",children:[]}]}],path:"/docs/launch-manual/sections/behind-pulsar/sections/keymaps-in-depth.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:2,title:"Maintaining Your Packages",slug:"maintaining-your-packages",link:"#maintaining-your-packages",children:[{level:3,title:"Publishing a Package Manually",slug:"publishing-a-package-manually",link:"#publishing-a-package-manually",children:[]},{level:3,title:"Adding a Collaborator",slug:"adding-a-collaborator",link:"#adding-a-collaborator",children:[]},{level:3,title:"Transferring Ownership",slug:"transferring-ownership",link:"#transferring-ownership",children:[]},{level:3,title:"Unpublish Your Package",slug:"unpublish-your-package",link:"#unpublish-your-package",children:[]},{level:3,title:"Rename Your Package",slug:"rename-your-package",link:"#rename-your-package",children:[]}]}],path:"/docs/launch-manual/sections/behind-pulsar/sections/maintaining-your-packages.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:2,title:"Scoped Settings, Scopes and Scope Descriptors",slug:"scoped-settings-scopes-and-scope-descriptors",link:"#scoped-settings-scopes-and-scope-descriptors",children:[{level:3,title:"Scope Names in Syntax Tokens",slug:"scope-names-in-syntax-tokens",link:"#scope-names-in-syntax-tokens",children:[]},{level:3,title:"Scope Selectors",slug:"scope-selectors",link:"#scope-selectors",children:[]},{level:3,title:"Scope Descriptors",slug:"scope-descriptors",link:"#scope-descriptors",children:[]}]}],path:"/docs/launch-manual/sections/behind-pulsar/sections/scoped-settings-scopes-and-scope-descriptors.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:2,title:"Serialization in Pulsar",slug:"serialization-in-pulsar",link:"#serialization-in-pulsar",children:[{level:3,title:"Package Serialization Hook",slug:"package-serialization-hook",link:"#package-serialization-hook",children:[]},{level:3,title:"Serialization Methods",slug:"serialization-methods",link:"#serialization-methods",children:[]},{level:3,title:"Versioning",slug:"versioning",link:"#versioning",children:[]}]}],path:"/docs/launch-manual/sections/behind-pulsar/sections/serialization-in-pulsar.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:2,title:"Summary",slug:"summary",link:"#summary",children:[]}],path:"/docs/launch-manual/sections/behind-pulsar/sections/summary.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:2,title:"Building Pulsar",slug:"building-pulsar",link:"#building-pulsar",children:[{level:3,title:"Requirements and dependencies",slug:"requirements-and-dependencies",link:"#requirements-and-dependencies",children:[]},{level:3,title:"Building and running the application",slug:"building-and-running-the-application",link:"#building-and-running-the-application",children:[]},{level:3,title:"Building binaries",slug:"building-binaries",link:"#building-binaries",children:[]}]}],path:"/docs/launch-manual/sections/core-hacking/sections/building-pulsar.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:2,title:"Contributing to Official Pulsar Packages",slug:"contributing-to-official-pulsar-packages",link:"#contributing-to-official-pulsar-packages",children:[{level:3,title:"Hacking on Packages",slug:"hacking-on-packages",link:"#hacking-on-packages",children:[]}]}],path:"/docs/launch-manual/sections/core-hacking/sections/contributing-to-official-pulsar-packages.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:2,title:"Converting from TextMate",slug:"converting-from-textmate",link:"#converting-from-textmate",children:[{level:3,title:"Converting a TextMate Grammar Bundle",slug:"converting-a-textmate-grammar-bundle",link:"#converting-a-textmate-grammar-bundle",children:[]},{level:3,title:"Converting a TextMate Syntax Theme",slug:"converting-a-textmate-syntax-theme",link:"#converting-a-textmate-syntax-theme",children:[]}]}],path:"/docs/launch-manual/sections/core-hacking/sections/converting-from-textmate.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:2,title:"Creating a Fork of a Core Package",slug:"creating-a-fork-of-a-core-package",link:"#creating-a-fork-of-a-core-package",children:[{level:3,title:"Creating Your New Package",slug:"creating-your-new-package",link:"#creating-your-new-package",children:[]},{level:3,title:"Merging Upstream Changes into Your Package",slug:"merging-upstream-changes-into-your-package",link:"#merging-upstream-changes-into-your-package",children:[]}]}],path:"/docs/launch-manual/sections/core-hacking/sections/creating-a-fork-of-a-core-package.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:2,title:"Creating a Grammar",slug:"creating-a-grammar",link:"#creating-a-grammar",children:[{level:3,title:"Getting Started",slug:"getting-started",link:"#getting-started",children:[]},{level:3,title:"The Parser",slug:"the-parser",link:"#the-parser",children:[]},{level:3,title:"The Package",slug:"the-package",link:"#the-package",children:[]},{level:3,title:"The Grammar File",slug:"the-grammar-file",link:"#the-grammar-file",children:[]},{level:3,title:"Basic Fields",slug:"basic-fields",link:"#basic-fields",children:[]},{level:3,title:"Language Recognition",slug:"language-recognition",link:"#language-recognition",children:[]},{level:3,title:"Syntax Highlighting",slug:"syntax-highlighting",link:"#syntax-highlighting",children:[]},{level:3,title:"Language Injection",slug:"language-injection",link:"#language-injection",children:[]},{level:3,title:"Code Folding",slug:"code-folding",link:"#code-folding",children:[]},{level:3,title:"Comments",slug:"comments",link:"#comments",children:[]},{level:3,title:"Example Packages",slug:"example-packages",link:"#example-packages",children:[]}]}],path:"/docs/launch-manual/sections/core-hacking/sections/creating-a-grammar.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:2,title:"Creating a Legacy TextMate Grammar",slug:"creating-a-legacy-textmate-grammar",link:"#creating-a-legacy-textmate-grammar",children:[{level:3,title:"Getting Started",slug:"getting-started",link:"#getting-started",children:[]},{level:3,title:"Create the Package",slug:"create-the-package",link:"#create-the-package",children:[]},{level:3,title:"Adding Patterns",slug:"adding-patterns",link:"#adding-patterns",children:[]},{level:3,title:"Begin/End Patterns",slug:"begin-end-patterns",link:"#begin-end-patterns",children:[]},{level:3,title:"Repositories and the Include keyword, or how to avoid duplication",slug:"repositories-and-the-include-keyword-or-how-to-avoid-duplication",link:"#repositories-and-the-include-keyword-or-how-to-avoid-duplication",children:[]},{level:3,title:"Where to Go from Here",slug:"where-to-go-from-here",link:"#where-to-go-from-here",children:[]}]}],path:"/docs/launch-manual/sections/core-hacking/sections/creating-a-legacy-textmate-grammar.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:2,title:"Creating a Theme",slug:"creating-a-theme",link:"#creating-a-theme",children:[{level:3,title:"Getting Started",slug:"getting-started",link:"#getting-started",children:[]},{level:3,title:"Creating a Syntax Theme",slug:"creating-a-syntax-theme",link:"#creating-a-syntax-theme",children:[]},{level:3,title:"Creating a UI Theme",slug:"creating-a-ui-theme",link:"#creating-a-ui-theme",children:[]},{level:3,title:"Theme Variables",slug:"theme-variables",link:"#theme-variables",children:[]},{level:3,title:"Development workflow",slug:"development-workflow",link:"#development-workflow",children:[]},{level:3,title:"Publish your theme",slug:"publish-your-theme",link:"#publish-your-theme",children:[]}]}],path:"/docs/launch-manual/sections/core-hacking/sections/creating-a-theme.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:3,title:"Cross-Platform Compatibility",slug:"cross-platform-compatibility",link:"#cross-platform-compatibility",children:[]}],path:"/docs/launch-manual/sections/core-hacking/sections/cross-platform-compatibility.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:2,title:"Debugging",slug:"debugging",link:"#debugging",children:[{level:3,title:"Update to the Latest Version",slug:"update-to-the-latest-version",link:"#update-to-the-latest-version",children:[]},{level:3,title:"Using Safe Mode",slug:"using-safe-mode",link:"#using-safe-mode",children:[]},{level:3,title:"Clearing Saved State",slug:"clearing-saved-state",link:"#clearing-saved-state",children:[]},{level:3,title:"Reset to Factory Defaults",slug:"reset-to-factory-defaults",link:"#reset-to-factory-defaults",children:[]},{level:3,title:"Check for Linked Packages",slug:"check-for-linked-packages",link:"#check-for-linked-packages",children:[]},{level:3,title:"Check for Incompatible Packages",slug:"check-for-incompatible-packages",link:"#check-for-incompatible-packages",children:[]},{level:3,title:"Check Pulsar and Package Settings",slug:"check-pulsar-and-package-settings",link:"#check-pulsar-and-package-settings",children:[]},{level:3,title:"Check Your Configuration",slug:"check-your-configuration",link:"#check-your-configuration",children:[]},{level:3,title:"Check Your Keybindings",slug:"check-your-keybindings",link:"#check-your-keybindings",children:[]},{level:3,title:"Check Font Rendering Issues",slug:"check-font-rendering-issues",link:"#check-font-rendering-issues",children:[]},{level:3,title:"Check for Errors in the Developer Tools",slug:"check-for-errors-in-the-developer-tools",link:"#check-for-errors-in-the-developer-tools",children:[]},{level:3,title:"Find Crash Logs",slug:"find-crash-logs",link:"#find-crash-logs",children:[]},{level:3,title:"Diagnose Startup Performance",slug:"diagnose-startup-performance",link:"#diagnose-startup-performance",children:[]},{level:3,title:"Diagnose Runtime Performance",slug:"diagnose-runtime-performance",link:"#diagnose-runtime-performance",children:[]},{level:3,title:"Profiling Startup Performance",slug:"profiling-startup-performance",link:"#profiling-startup-performance",children:[]},{level:3,title:"Check Your Build Tools",slug:"check-your-build-tools",link:"#check-your-build-tools",children:[]},{level:3,title:"Check if your GPU is causing the problem",slug:"check-if-your-gpu-is-causing-the-problem",link:"#check-if-your-gpu-is-causing-the-problem",children:[]}]}],path:"/docs/launch-manual/sections/core-hacking/sections/debugging.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:2,title:"Hacking on the Core",slug:"hacking-on-the-core",link:"#hacking-on-the-core",children:[{level:3,title:"Running in Development Mode",slug:"running-in-development-mode",link:"#running-in-development-mode",children:[]},{level:3,title:"Running Pulsar Core Tests Locally",slug:"running-pulsar-core-tests-locally",link:"#running-pulsar-core-tests-locally",children:[]}]}],path:"/docs/launch-manual/sections/core-hacking/sections/hacking-on-the-core.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:2,title:"Handling URIs",slug:"handling-uris",link:"#handling-uris",children:[{level:3,title:"Modifying your package.json",slug:"modifying-your-package-json",link:"#modifying-your-package-json",children:[]},{level:3,title:"Modifying your Main Module",slug:"modifying-your-main-module",link:"#modifying-your-main-module",children:[]},{level:3,title:"Controlling Activation Deferral",slug:"controlling-activation-deferral",link:"#controlling-activation-deferral",children:[]},{level:3,title:"Linux Support",slug:"linux-support",link:"#linux-support",children:[]}]},{level:2,title:"Core URIs",slug:"core-uris",link:"#core-uris",children:[]}],path:"/docs/launch-manual/sections/core-hacking/sections/handling-uris.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:2,title:"Iconography",slug:"iconography",link:"#iconography",children:[{level:3,title:"Overview",slug:"overview",link:"#overview",children:[]},{level:3,title:"Usage",slug:"usage",link:"#usage",children:[]},{level:3,title:"Size",slug:"size",link:"#size",children:[]},{level:3,title:"Usability",slug:"usability",link:"#usability",children:[]}]}],path:"/docs/launch-manual/sections/core-hacking/sections/iconography.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:2,title:"Maintaining a Fork of a Core Package",slug:"maintaining-a-fork-of-a-core-package",link:"#maintaining-a-fork-of-a-core-package",children:[{level:3,title:"Step-by-step guide",slug:"step-by-step-guide",link:"#step-by-step-guide",children:[]}]}],path:"/docs/launch-manual/sections/core-hacking/sections/maintaining-a-fork-of-a-core-package.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:2,title:"Package: Active Editor Info",slug:"package-active-editor-info",link:"#package-active-editor-info",children:[{level:3,title:"Create the Package",slug:"create-the-package",link:"#create-the-package",children:[]},{level:3,title:"Add an Opener",slug:"add-an-opener",link:"#add-an-opener",children:[]},{level:3,title:"Updating the View",slug:"updating-the-view",link:"#updating-the-view",children:[]},{level:3,title:"Constraining Our Item's Locations",slug:"constraining-our-item-s-locations",link:"#constraining-our-item-s-locations",children:[]},{level:3,title:"Show Active Editor Info",slug:"show-active-editor-info",link:"#show-active-editor-info",children:[]},{level:3,title:"Serialization",slug:"serialization",link:"#serialization",children:[]},{level:3,title:"Summary",slug:"summary",link:"#summary",children:[]}]}],path:"/docs/launch-manual/sections/core-hacking/sections/package-active-editor-info.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:2,title:"Package: Modifying Text",slug:"package-modifying-text",link:"#package-modifying-text",children:[{level:3,title:"Basic Text Insertion",slug:"basic-text-insertion",link:"#basic-text-insertion",children:[]},{level:3,title:"Add the ASCII Art",slug:"add-the-ascii-art",link:"#add-the-ascii-art",children:[]},{level:3,title:"Summary",slug:"summary",link:"#summary",children:[]}]}],path:"/docs/launch-manual/sections/core-hacking/sections/package-modifying-text.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:2,title:"Package: Word Count",slug:"package-word-count",link:"#package-word-count",children:[{level:3,title:"Package Generator",slug:"package-generator",link:"#package-generator",children:[]},{level:3,title:"Developing Our Package",slug:"developing-our-package",link:"#developing-our-package",children:[]},{level:3,title:"Basic Debugging",slug:"basic-debugging",link:"#basic-debugging",children:[]},{level:3,title:"Testing",slug:"testing",link:"#testing",children:[]},{level:3,title:"Summary",slug:"summary",link:"#summary",children:[]}]}],path:"/docs/launch-manual/sections/core-hacking/sections/package-word-count.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:2,title:"Publishing",slug:"publishing",link:"#publishing",children:[{level:3,title:"Prepare Your Package",slug:"prepare-your-package",link:"#prepare-your-package",children:[]},{level:3,title:"Publish Your Package",slug:"publish-your-package",link:"#publish-your-package",children:[]}]}],path:"/docs/launch-manual/sections/core-hacking/sections/publishing.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:2,title:"Summary",slug:"summary",link:"#summary",children:[]}],path:"/docs/launch-manual/sections/core-hacking/sections/summary.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:2,title:"The Init File",slug:"the-init-file",link:"#the-init-file",children:[]}],path:"/docs/launch-manual/sections/core-hacking/sections/the-init-file.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:2,title:"Tools of the Trade",slug:"tools-of-the-trade",link:"#tools-of-the-trade",children:[]}],path:"/docs/launch-manual/sections/core-hacking/sections/tools-of-the-trade.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:2,title:"Using ppm (Pulsar Package Manager)",slug:"using-ppm-pulsar-package-manager",link:"#using-ppm-pulsar-package-manager",children:[]}],path:"/docs/launch-manual/sections/core-hacking/sections/using-ppm.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:2,title:"Writing Specs",slug:"writing-specs",link:"#writing-specs",children:[{level:3,title:"Create a New Spec",slug:"create-a-new-spec",link:"#create-a-new-spec",children:[]},{level:3,title:"Asynchronous Specs",slug:"asynchronous-specs",link:"#asynchronous-specs",children:[]},{level:3,title:"Running Specs",slug:"running-specs",link:"#running-specs",children:[]},{level:3,title:"Customizing your test runner",slug:"customizing-your-test-runner",link:"#customizing-your-test-runner",children:[]}]}],path:"/docs/launch-manual/sections/core-hacking/sections/writing-specs.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:2,title:"Common Issues",slug:"common-issues",link:"#common-issues",children:[{level:3,title:'macOS error "App is damaged and can\u2019t be opened"',slug:"macos-error-app-is-damaged-and-can-t-be-opened",link:"#macos-error-app-is-damaged-and-can-t-be-opened",children:[]},{level:3,title:`Pulsar does not launch "GPU process isn't usable. Goodbye"`,slug:"pulsar-does-not-launch-gpu-process-isn-t-usable-goodbye",link:"#pulsar-does-not-launch-gpu-process-isn-t-usable-goodbye",children:[]}]}],path:"/docs/launch-manual/sections/faq/sections/common-issues.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:2,title:"Having trouble?",slug:"having-trouble",link:"#having-trouble",children:[]}],path:"/docs/launch-manual/sections/faq/sections/get-help.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:2,title:"Installing Pulsar",slug:"installing-pulsar",link:"#installing-pulsar",children:[{level:3,title:"Universal releases",slug:"universal-releases",link:"#universal-releases",children:[]},{level:3,title:"Debian/Ubuntu based distributions",slug:"debian-ubuntu-based-distributions",link:"#debian-ubuntu-based-distributions",children:[]},{level:3,title:"Fedora/RHEL based distributions",slug:"fedora-rhel-based-distributions",link:"#fedora-rhel-based-distributions",children:[]},{level:3,title:"Updating Pulsar",slug:"updating-pulsar",link:"#updating-pulsar",children:[]},{level:3,title:"Portable Mode",slug:"portable-mode",link:"#portable-mode",children:[]},{level:3,title:"Building Pulsar from Source",slug:"building-pulsar-from-source",link:"#building-pulsar-from-source",children:[]},{level:3,title:"Proxy and Firewall Settings",slug:"proxy-and-firewall-settings",link:"#proxy-and-firewall-settings",children:[]}]}],path:"/docs/launch-manual/sections/getting-started/sections/installing-pulsar.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:2,title:"Pulsar Basics",slug:"pulsar-basics",link:"#pulsar-basics",children:[{level:3,title:"Terminology",slug:"terminology",link:"#terminology",children:[]},{level:3,title:"Command Palette",slug:"command-palette",link:"#command-palette",children:[]},{level:3,title:"Settings and Preferences",slug:"settings-and-preferences",link:"#settings-and-preferences",children:[]},{level:3,title:"Opening, Modifying, and Saving Files",slug:"opening-modifying-and-saving-files",link:"#opening-modifying-and-saving-files",children:[]},{level:3,title:"Opening Directories",slug:"opening-directories",link:"#opening-directories",children:[]}]}],path:"/docs/launch-manual/sections/getting-started/sections/pulsar-basics.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:2,title:"Summary",slug:"summary",link:"#summary",children:[]}],path:"/docs/launch-manual/sections/getting-started/sections/summary.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:2,title:"Why Pulsar?",slug:"why-pulsar",link:"#why-pulsar",children:[{level:3,title:"The Nucleus of Pulsar",slug:"the-nucleus-of-pulsar",link:"#the-nucleus-of-pulsar",children:[]},{level:3,title:"The Native Web",slug:"the-native-web",link:"#the-native-web",children:[]},{level:3,title:"JavaScript, Meet C++",slug:"javascript-meet-c",link:"#javascript-meet-c",children:[]},{level:3,title:"Web Tech: The Fun Parts",slug:"web-tech-the-fun-parts",link:"#web-tech-the-fun-parts",children:[]},{level:3,title:"An Open-Source Text Editor",slug:"an-open-source-text-editor",link:"#an-open-source-text-editor",children:[]}]}],path:"/docs/launch-manual/sections/getting-started/sections/why-pulsar.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:2,title:"Autocomplete",slug:"autocomplete",link:"#autocomplete",children:[]}],path:"/docs/launch-manual/sections/using-pulsar/sections/autocomplete.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:2,title:"Basic Customization",slug:"basic-customization",link:"#basic-customization",children:[{level:3,title:"Configuring with CSON",slug:"configuring-with-cson",link:"#configuring-with-cson",children:[]},{level:3,title:"Style Tweaks",slug:"style-tweaks",link:"#style-tweaks",children:[]},{level:3,title:"Customizing Keybindings",slug:"customizing-keybindings",link:"#customizing-keybindings",children:[]},{level:3,title:"Global Configuration Settings",slug:"global-configuration-settings",link:"#global-configuration-settings",children:[]},{level:3,title:"Language Specific Configuration Settings",slug:"language-specific-configuration-settings",link:"#language-specific-configuration-settings",children:[]},{level:3,title:"Customizing Language Recognition",slug:"customizing-language-recognition",link:"#customizing-language-recognition",children:[]},{level:3,title:"Controlling Where Customization is Stored to Simplify Your Workflow",slug:"controlling-where-customization-is-stored-to-simplify-your-workflow",link:"#controlling-where-customization-is-stored-to-simplify-your-workflow",children:[]}]}],path:"/docs/launch-manual/sections/using-pulsar/sections/basic-customization.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:2,title:"Editing and Deleting Text",slug:"editing-and-deleting-text",link:"#editing-and-deleting-text",children:[{level:3,title:"Basic Manipulation",slug:"basic-manipulation",link:"#basic-manipulation",children:[]},{level:3,title:"Deleting and Cutting",slug:"deleting-and-cutting",link:"#deleting-and-cutting",children:[]},{level:3,title:"Multiple Cursors and Selections",slug:"multiple-cursors-and-selections",link:"#multiple-cursors-and-selections",children:[]},{level:3,title:"Whitespace",slug:"whitespace",link:"#whitespace",children:[]},{level:3,title:"Brackets",slug:"brackets",link:"#brackets",children:[]},{level:3,title:"Encoding",slug:"encoding",link:"#encoding",children:[]}]}],path:"/docs/launch-manual/sections/using-pulsar/sections/editing-and-deleting-text.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:2,title:"Find and Replace",slug:"find-and-replace",link:"#find-and-replace",children:[]}],path:"/docs/launch-manual/sections/using-pulsar/sections/find-and-replace.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:2,title:"Folding",slug:"folding",link:"#folding",children:[]}],path:"/docs/launch-manual/sections/using-pulsar/sections/folding.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:2,title:"GitHub package",slug:"github-package",link:"#github-package",children:[{level:3,title:"Initialize repositories",slug:"initialize-repositories",link:"#initialize-repositories",children:[]},{level:3,title:"Clone repositories",slug:"clone-repositories",link:"#clone-repositories",children:[]},{level:3,title:"Branch",slug:"branch",link:"#branch",children:[]},{level:3,title:"Stage",slug:"stage",link:"#stage",children:[]},{level:3,title:"Discard changes",slug:"discard-changes",link:"#discard-changes",children:[]},{level:3,title:"Commit Preview",slug:"commit-preview",link:"#commit-preview",children:[]},{level:3,title:"Commit",slug:"commit",link:"#commit",children:[]},{level:3,title:"Amend and undo",slug:"amend-and-undo",link:"#amend-and-undo",children:[]},{level:3,title:"View commits",slug:"view-commits",link:"#view-commits",children:[]},{level:3,title:"Publish and push",slug:"publish-and-push",link:"#publish-and-push",children:[]},{level:3,title:"Fetch and pull",slug:"fetch-and-pull",link:"#fetch-and-pull",children:[]},{level:3,title:"Resolve conflicts",slug:"resolve-conflicts",link:"#resolve-conflicts",children:[]},{level:3,title:"Create a Pull Request",slug:"create-a-pull-request",link:"#create-a-pull-request",children:[]},{level:3,title:"View Pull Requests",slug:"view-pull-requests",link:"#view-pull-requests",children:[]},{level:3,title:"Open any Issue or Pull Request",slug:"open-any-issue-or-pull-request",link:"#open-any-issue-or-pull-request",children:[]},{level:3,title:"Checkout a Pull Request",slug:"checkout-a-pull-request",link:"#checkout-a-pull-request",children:[]},{level:3,title:"View Pull Request review comments",slug:"view-pull-request-review-comments",link:"#view-pull-request-review-comments",children:[]},{level:3,title:"Navigate Pull Request review comments",slug:"navigate-pull-request-review-comments",link:"#navigate-pull-request-review-comments",children:[]},{level:3,title:"Respond to a Pull Request review comment",slug:"respond-to-a-pull-request-review-comment",link:"#respond-to-a-pull-request-review-comment",children:[]}]}],path:"/docs/launch-manual/sections/using-pulsar/sections/github-package.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:2,title:"Grammar",slug:"grammar",link:"#grammar",children:[]}],path:"/docs/launch-manual/sections/using-pulsar/sections/grammar.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:2,title:"Moving in Pulsar",slug:"moving-in-pulsar",link:"#moving-in-pulsar",children:[{level:3,title:"Additional Movement and Selection Commands",slug:"additional-movement-and-selection-commands",link:"#additional-movement-and-selection-commands",children:[]},{level:3,title:"Navigating by Symbols",slug:"navigating-by-symbols",link:"#navigating-by-symbols",children:[]},{level:3,title:"Bookmarks",slug:"bookmarks",link:"#bookmarks",children:[]}]}],path:"/docs/launch-manual/sections/using-pulsar/sections/moving-in-pulsar.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:2,title:"Panes",slug:"panes",link:"#panes",children:[]}],path:"/docs/launch-manual/sections/using-pulsar/sections/panes.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:2,title:"Pending Pane Items",slug:"pending-pane-items",link:"#pending-pane-items",children:[{level:3,title:"Disabling Pending Pane Items",slug:"disabling-pending-pane-items",link:"#disabling-pending-pane-items",children:[]}]}],path:"/docs/launch-manual/sections/using-pulsar/sections/pending-pane-items.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:2,title:"Pulsar Packages",slug:"pulsar-packages",link:"#pulsar-packages",children:[{level:3,title:"Package Settings",slug:"package-settings",link:"#package-settings",children:[]},{level:3,title:"Pulsar Themes",slug:"pulsar-themes",link:"#pulsar-themes",children:[]},{level:3,title:"Command Line",slug:"command-line",link:"#command-line",children:[]}]}],path:"/docs/launch-manual/sections/using-pulsar/sections/pulsar-packages.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:2,title:"Pulsar Selections",slug:"pulsar-selections",link:"#pulsar-selections",children:[]}],path:"/docs/launch-manual/sections/using-pulsar/sections/pulsar-selections.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:2,title:"Snippets",slug:"snippets",link:"#snippets",children:[{level:3,title:"Creating Your Own Snippets",slug:"creating-your-own-snippets",link:"#creating-your-own-snippets",children:[]},{level:3,title:"More Info",slug:"more-info",link:"#more-info",children:[]}]}],path:"/docs/launch-manual/sections/using-pulsar/sections/snippets.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:2,title:"Summary",slug:"summary",link:"#summary",children:[]}],path:"/docs/launch-manual/sections/using-pulsar/sections/summary.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:2,title:"Version Control in Pulsar",slug:"version-control-in-pulsar",link:"#version-control-in-pulsar",children:[{level:3,title:"Checkout HEAD revision",slug:"checkout-head-revision",link:"#checkout-head-revision",children:[]},{level:3,title:"Git status list",slug:"git-status-list",link:"#git-status-list",children:[]},{level:3,title:"Commit editor",slug:"commit-editor",link:"#commit-editor",children:[]},{level:3,title:"Status bar icons",slug:"status-bar-icons",link:"#status-bar-icons",children:[]},{level:3,title:"Line diffs",slug:"line-diffs",link:"#line-diffs",children:[]},{level:3,title:"Open on GitHub",slug:"open-on-github",link:"#open-on-github",children:[]}]}],path:"/docs/launch-manual/sections/using-pulsar/sections/version-control-in-pulsar.html",pathLocale:"/",extraFields:[]},{title:"",headers:[{level:2,title:"Writing in Pulsar",slug:"writing-in-pulsar",link:"#writing-in-pulsar",children:[{level:3,title:"Spell Checking",slug:"spell-checking",link:"#spell-checking",children:[]},{level:3,title:"Previews",slug:"previews",link:"#previews",children:[]},{level:3,title:"Snippets",slug:"snippets",link:"#snippets",children:[]}]}],path:"/docs/launch-manual/sections/using-pulsar/sections/writing-in-pulsar.html",pathLocale:"/",extraFields:[]},{title:"",headers:[],path:"/404.html",pathLocale:"/",extraFields:[]},{title:"Category",headers:[],path:"/category/",pathLocale:"/",extraFields:[]},{title:"Tag",headers:[],path:"/tag/",pathLocale:"/",extraFields:[]},{title:"Articles",headers:[],path:"/blog/",pathLocale:"/",extraFields:[]},{title:"Encrypted",headers:[],path:"/encrypted/",pathLocale:"/",extraFields:[]},{title:"Slides",headers:[],path:"/slide/",pathLocale:"/",extraFields:[]},{title:"Star",headers:[],path:"/star/",pathLocale:"/",extraFields:[]},{title:"Timeline",headers:[],path:"/timeline/",pathLocale:"/",extraFields:[]},{title:"dev Category",headers:[],path:"/category/dev/",pathLocale:"/",extraFields:[]},{title:"backend Tag",headers:[],path:"/tag/backend/",pathLocale:"/",extraFields:[]},{title:"log Category",headers:[],path:"/category/log/",pathLocale:"/",extraFields:[]},{title:"sunset Tag",headers:[],path:"/tag/sunset/",pathLocale:"/",extraFields:[]},{title:"news Category",headers:[],path:"/category/news/",pathLocale:"/",extraFields:[]},{title:"video Tag",headers:[],path:"/tag/video/",pathLocale:"/",extraFields:[]},{title:"survey Category",headers:[],path:"/category/survey/",pathLocale:"/",extraFields:[]},{title:"update Tag",headers:[],path:"/tag/update/",pathLocale:"/",extraFields:[]},{title:"modernization Tag",headers:[],path:"/tag/modernization/",pathLocale:"/",extraFields:[]},{title:"tree-sitter Tag",headers:[],path:"/tag/tree-sitter/",pathLocale:"/",extraFields:[]},{title:"release Tag",headers:[],path:"/tag/release/",pathLocale:"/",extraFields:[]},{title:"releases Tag",headers:[],path:"/tag/releases/",pathLocale:"/",extraFields:[]},{title:"rolling Tag",headers:[],path:"/tag/rolling/",pathLocale:"/",extraFields:[]},{title:"regular Tag",headers:[],path:"/tag/regular/",pathLocale:"/",extraFields:[]},{title:"community Tag",headers:[],path:"/tag/community/",pathLocale:"/",extraFields:[]},{title:"socials Tag",headers:[],path:"/tag/socials/",pathLocale:"/",extraFields:[]},{title:"joke Tag",headers:[],path:"/tag/joke/",pathLocale:"/",extraFields:[]},{title:"feedback Tag",headers:[],path:"/tag/feedback/",pathLocale:"/",extraFields:[]},{title:"github Tag",headers:[],path:"/tag/github/",pathLocale:"/",extraFields:[]},{title:"stars Tag",headers:[],path:"/tag/stars/",pathLocale:"/",extraFields:[]},{title:"windows Tag",headers:[],path:"/tag/windows/",pathLocale:"/",extraFields:[]},{title:"chocolatey Tag",headers:[],path:"/tag/chocolatey/",pathLocale:"/",extraFields:[]},{title:"package manager Tag",headers:[],path:"/tag/package-manager/",pathLocale:"/",extraFields:[]},{title:"ci Tag",headers:[],path:"/tag/ci/",pathLocale:"/",extraFields:[]},{title:"electron Tag",headers:[],path:"/tag/electron/",pathLocale:"/",extraFields:[]}],uu=ne(Mv),Fv=()=>uu;import.meta.webpackHot&&(__VUE_HMR_RUNTIME__.updateSearchIndex=e=>{uu.value=e});const zv=({searchIndex:e,routeLocale:t,query:i,maxSuggestions:a})=>{const n=P(()=>e.value.filter(l=>l.pathLocale===t.value));return P(()=>{const l=i.value.trim().toLowerCase();if(!l)return[];const o=[],r=(c,d)=>{or(l,[d.title])&&o.push({link:`${c.path}#${d.slug}`,title:c.title,header:d.title});for(const h of d.children){if(o.length>=a.value)return;r(c,h)}};for(const c of n.value){if(o.length>=a.value)break;if(or(l,[c.title,...c.extraFields])){o.push({link:c.path,title:c.title});continue}for(const d of c.headers){if(o.length>=a.value)break;r(c,d)}}return o})},Hv=e=>{const t=ne(0);return{focusIndex:t,focusNext:()=>{t.value{t.value>0?t.value-=1:t.value=e.value.length-1}}},$v=K({name:"SearchBox",props:{locales:{type:Object,required:!1,default:()=>({})},hotKeys:{type:Array,required:!1,default:()=>[]},maxSuggestions:{type:Number,required:!1,default:5}},setup(e){const{locales:t,hotKeys:i,maxSuggestions:a}=vd(e),n=Xe(),l=yt(),o=Fv(),r=ne(null),c=ne(!1),d=ne(""),h=P(()=>{var E;return(E=t.value[l.value])!=null?E:{}}),p=zv({searchIndex:o,routeLocale:l,query:d,maxSuggestions:a}),{focusIndex:m,focusNext:f,focusPrev:y}=Hv(p);Vv({input:r,hotKeys:i});const _=P(()=>c.value&&!!p.value.length),x=()=>{!_.value||y()},b=()=>{!_.value||f()},w=E=>{if(!_.value)return;const C=p.value[E];!C||n.push(C.link).then(()=>{d.value="",m.value=0})};return()=>u("form",{class:"search-box",role:"search"},[u("input",{ref:r,type:"search",placeholder:h.value.placeholder,autocomplete:"off",spellcheck:!1,value:d.value,onFocus:()=>c.value=!0,onBlur:()=>c.value=!1,onInput:E=>d.value=E.target.value,onKeydown:E=>{switch(E.key){case"ArrowUp":{x();break}case"ArrowDown":{b();break}case"Enter":{E.preventDefault(),w(m.value);break}}}}),_.value&&u("ul",{class:"suggestions",onMouseleave:()=>m.value=-1},p.value.map(({link:E,title:C,header:F},S)=>u("li",{class:["suggestion",{focus:m.value===S}],onMouseenter:()=>m.value=S,onMousedown:()=>w(S)},u("a",{href:E,onClick:T=>T.preventDefault()},[u("span",{class:"page-title"},C),F&&u("span",{class:"page-header"},`> ${F}`)]))))])}});const Nv={},qv=["s","/"],Bv=10,Uv=kt({enhance({app:e}){e.component("SearchBox",t=>u($v,{locales:Nv,hotKeys:qv,maxSuggestions:Bv,...t}))}}),Ta=[hp,dm,bg,wg,Pg,Ig,Sg,Mg,Bg,Ov,Uv];var du=ne(Tu),hu=nl({key:"",path:"",title:"",lang:"",frontmatter:{},excerpt:"",headers:[]}),Pt=ne(hu),je=()=>Pt;import.meta.webpackHot&&(__VUE_HMR_RUNTIME__.updatePageData=e=>{du.value[e.key]=()=>Promise.resolve(e),e.key===Pt.value.key&&(Pt.value=e)});var pu=Symbol(""),Pe=()=>{const e=we(pu);if(!e)throw new Error("usePageFrontmatter() is called without provider.");return e},mu=Symbol(""),jv=()=>{const e=we(mu);if(!e)throw new Error("usePageHead() is called without provider.");return e},gu=Symbol(""),Wv=()=>{const e=we(gu);if(!e)throw new Error("usePageHeadTitle() is called without provider.");return e},vu=Symbol(""),fu=()=>{const e=we(vu);if(!e)throw new Error("usePageLang() is called without provider.");return e},Ml=Symbol(""),yt=()=>{const e=we(Ml);if(!e)throw new Error("useRouteLocale() is called without provider.");return e},zt=ne(ap),Fl=()=>zt;import.meta.webpackHot&&(__VUE_HMR_RUNTIME__.updateSiteData=e=>{zt.value=e});var _u=Symbol(""),Qa=()=>{const e=we(_u);if(!e)throw new Error("useSiteLocaleData() is called without provider.");return e},Gv=Symbol(""),Yt=Ut({resolvePageData:async e=>{const t=du.value[e],i=await(t==null?void 0:t());return i!=null?i:hu},resolvePageFrontmatter:e=>e.frontmatter,resolvePageHead:(e,t,i)=>{const a=ye(t.description)?t.description:i.description,n=[...ae(t.head)?t.head:[],...i.head,["title",{},e],["meta",{name:"description",content:a}]];return lp(n)},resolvePageHeadTitle:(e,t)=>`${e.title?`${e.title} | `:""}${t.title}`,resolvePageLang:e=>e.lang||"en",resolveRouteLocale:(e,t)=>dp(e,t),resolveSiteLocaleData:(e,t)=>({...e,...e.locales[t]})}),Xa=K({name:"ClientOnly",setup(e,t){const i=ne(!1);return Se(()=>{i.value=!0}),()=>{var a,n;return i.value?(n=(a=t.slots).default)==null?void 0:n.call(a):null}}}),bu=K({name:"Content",props:{pageKey:{type:String,required:!1,default:""}},setup(e){const t=je(),i=P(()=>ds[e.pageKey||t.value.key]);return()=>i.value?u(i.value):u("div","404 Not Found")}}),Yv="Layout",Kv="NotFound",Zv=Ta.reduce((e,t)=>({...e,...t.layouts}),{}),rr=K({name:"Vuepress",setup(){const e=je(),t=P(()=>{let i;if(e.value.path){const a=e.value.frontmatter.layout;ye(a)?i=a:i=Yv}else i=Kv;return Zv[i]});return()=>u(t.value)}}),mt=e=>ti(e)?e:`${Fl().value.base}${us(e)}`;const Jv=[["v-22a39d25","/about.html",{title:"About Us",type:"article",readingTime:{minutes:.86,words:257},excerpt:""},["/about","/about.md"]],["v-67f865c9","/community.html",{title:"Community areas",type:"article",readingTime:{minutes:.32,words:97},excerpt:""},["/community","/community.md"]],["v-0100cb31","/donate.html",{title:"Donate to Pulsar",type:"article",readingTime:{minutes:.5,words:151},excerpt:""},["/donate","/donate.md"]],["v-5395f6f8","/download.html",{title:"Pulsar Downloads",type:"article",readingTime:{minutes:4.78,words:1434},excerpt:""},["/download","/download.md"]],["v-8daa1a0e","/",{title:"Home",type:"home",readingTime:{minutes:1.32,words:396},excerpt:""},["/index.html","/index.md"]],["v-41185df1","/repos.html",{title:"Pulsar Repositories",type:"article",readingTime:{minutes:.66,words:199},excerpt:""},["/repos","/repos.md"]],["v-f7f18754","/blog/20221112-Daeraxa-ExamplePost.html",{title:"Example post",type:"page",readingTime:{minutes:.21,words:63},excerpt:` +
    +

    title: Example Post +author: Daeraxa +date: 2022-11-12 +category:

    +
      +
    • website
    • +
    • testing +tag:
    • +
    • new feature
    • +
    • placeholder +sticky: false +star: false +article: false
    • +
    +
    +

    This is the excerpt.

    +`},["/blog/20221112-Daeraxa-ExamplePost","/blog/20221112-Daeraxa-ExamplePost.md"]],["v-0075970e","/blog/20221127-confused-Techie-SunsetMisadventureBackend.html",{title:"A Sunset and the Misadventures of a Backend",type:"article",readingTime:{minutes:6.49,words:1948},excerpt:`

    What's the natural response to finding out an application you use is being sunset? +Spend the next several months creating a complex backend infrastructure of course!

    +`,author:"confused-Techie",date:"2022-11-27T00:00:00.000Z",localizedDate:"November 27, 2022",category:["dev","log"],tag:["backend","sunset"]},["/blog/20221127-confused-Techie-SunsetMisadventureBackend","/blog/20221127-confused-Techie-SunsetMisadventureBackend.md"]],["v-cfc0df38","/blog/20221208-Daeraxa-DistroTubeVideo.html",{title:"Featured on DistroTube!",type:"article",readingTime:{minutes:.51,words:154},excerpt:`

    Today we were featured on the DistroTube YouTube channel!
    +Watch here

    +`,author:"Daeraxa",date:"2022-12-08T00:00:00.000Z",localizedDate:"December 8, 2022",category:["news"],tag:["video"]},["/blog/20221208-Daeraxa-DistroTubeVideo","/blog/20221208-Daeraxa-DistroTubeVideo.md"]],["v-4769b7e2","/blog/20221215-confused-Techie-v1.100.1-beta.html",{title:"Our First Release!",type:"article",readingTime:{minutes:6.8,words:2039},excerpt:`

    Check out our first GitHub Release for Pulsar! Available Now!

    +`,author:"confused-Techie",date:"2022-12-15T00:00:00.000Z",localizedDate:"December 15, 2022",category:["dev"],tag:["sunset"]},["/blog/20221215-confused-Techie-v1.100.1-beta","/blog/20221215-confused-Techie-v1.100.1-beta.md"]],["v-59933042","/blog/20230114-confused-Techie-v1.101.0-beta.html",{title:"Our Second Beta!",type:"article",readingTime:{minutes:3.64,words:1093},excerpt:`

    Check out our Second GitHub Release for Pulsar! Available Now!

    +`,author:"confused-Techie",date:"2023-01-15T00:00:00.000Z",localizedDate:"January 15, 2023",category:["dev"]},["/blog/20230114-confused-Techie-v1.101.0-beta","/blog/20230114-confused-Techie-v1.101.0-beta.md"]],["v-3a050031","/blog/20230201-Daeraxa-FebUpdate.html",{title:"Community Update",type:"article",readingTime:{minutes:3.16,words:948},excerpt:`

    What has the Pulsar team and community been up to lately?

    +`,author:"Daeraxa",date:"2023-02-01T00:00:00.000Z",localizedDate:"February 1, 2023",category:["news","log"],tag:["update"]},["/blog/20230201-Daeraxa-FebUpdate","/blog/20230201-Daeraxa-FebUpdate.md"]],["v-5fdcc4a1","/blog/20230209-mauricioszabo-tree-sitter-part-1.html",{title:"Tales of Tree-Sitter part 1: the start of a tale",type:"article",readingTime:{minutes:2.96,words:889},excerpt:`

    How did I decide to start working on tree-sitter, and all preparations to +modernize it on Pulsar

    +`,author:"Maur\xEDcio",date:"2023-02-09T00:00:00.000Z",localizedDate:"February 9, 2023",category:["dev"],tag:["modernization","tree-sitter"]},["/blog/20230209-mauricioszabo-tree-sitter-part-1","/blog/20230209-mauricioszabo-tree-sitter-part-1.md"]],["v-f88b1c54","/blog/20230215-Daeraxa-v1.102.0.html",{title:"New Regular Release (v1.102.0)",type:"article",readingTime:{minutes:2.89,words:867},excerpt:`

    Check out our newest Regular Release for Pulsar! Available Now!

    +`,author:"Daeraxa",date:"2023-02-15T00:00:00.000Z",localizedDate:"February 15, 2023",category:["dev"],tag:["release"]},["/blog/20230215-Daeraxa-v1.102.0","/blog/20230215-Daeraxa-v1.102.0.md"]],["v-5999d3d4","/blog/20230216-Daeraxa-ReleaseStrategyUpdate.html",{title:"Changes to our release strategy",type:"article",readingTime:{minutes:5.32,words:1596},excerpt:`

    We recently decided to change our release strategy to reflect what we are actually doing and address some potential inaccuracies in our existing terminology.

    +`,author:"Daeraxa",date:"2023-02-16T00:00:00.000Z",localizedDate:"February 16, 2023",category:["news"],tag:["releases","rolling","regular"]},["/blog/20230216-Daeraxa-ReleaseStrategyUpdate","/blog/20230216-Daeraxa-ReleaseStrategyUpdate.md"]],["v-5b193c62","/blog/20230227-Daeraxa-Survey1.html",{title:"Survey: How did you hear about us?",type:"article",readingTime:{minutes:.42,words:125},excerpt:`

    A short survey asking how you heard about the Pulsar project as well as feedback +on our community and social presence.

    +`,author:"Daeraxa",date:"2023-02-27T00:00:00.000Z",localizedDate:"February 27, 2023",category:["survey"],tag:["community","socials"]},["/blog/20230227-Daeraxa-Survey1","/blog/20230227-Daeraxa-Survey1.md"]],["v-29fd1295","/blog/20230301-Daeraxa-MarUpdate.html",{title:"Community Update",type:"article",readingTime:{minutes:2.7,words:810},excerpt:`

    What has the Pulsar team and community been up to lately? Find out here!

    +`,author:"Daeraxa",date:"2023-03-01T00:00:00.000Z",localizedDate:"March 1, 2023",category:["news","log"],tag:["update"]},["/blog/20230301-Daeraxa-MarUpdate","/blog/20230301-Daeraxa-MarUpdate.md"]],["v-6fcbebb6","/blog/20230315-Daeraxa-v1.103.0.html",{title:"It's that time again, Pulsar 1.103.0 is available now!",type:"article",readingTime:{minutes:2.25,words:676},excerpt:`

    Check out our newest Regular Release for Pulsar! Available Now!

    +`,author:"Daeraxa",date:"2023-03-15T00:00:00.000Z",localizedDate:"March 15, 2023",category:["dev"],tag:["release"]},["/blog/20230315-Daeraxa-v1.103.0","/blog/20230315-Daeraxa-v1.103.0.md"]],["v-7f7a1a1a","/blog/20230319-confused-Techie-HowLicenseNoneDeletedPackages.html",{title:"How 'license: none' Deleted Packages",type:"article",readingTime:{minutes:6.96,words:2089},excerpt:`

    How setting license: 'none' removed almost 100 Packages from the Pulsar Package Registry.

    +`,author:"confused-Techie",date:"2023-03-19T00:00:00.000Z",localizedDate:"March 19, 2023",category:["dev"],tag:["backend"]},["/blog/20230319-confused-Techie-HowLicenseNoneDeletedPackages","/blog/20230319-confused-Techie-HowLicenseNoneDeletedPackages.md"]],["v-91e3038e","/blog/20230326-Daeraxa-Survey1-Results.html",{title:"Survey Results: How did you hear about us?",type:"article",readingTime:{minutes:3.26,words:977},excerpt:`

    Here we have the results and analysis of our first community survey (and an infographic for those who like that kind of thing).

    +`,author:"Daeraxa",date:"2023-03-26T00:00:00.000Z",localizedDate:"March 26, 2023",category:["survey"],tag:["community","socials"]},["/blog/20230326-Daeraxa-Survey1-Results","/blog/20230326-Daeraxa-Survey1-Results.md"]],["v-e1c35862","/blog/20230401-Daeraxa-AprUpdate.html",{title:"Community Update",type:"article",readingTime:{minutes:5.91,words:1774},excerpt:`

    Another dose of our regular monthly community update!

    +`,author:"Daeraxa",date:"2023-04-01T00:00:00.000Z",localizedDate:"April 1, 2023",category:["news","log"],tag:["update"]},["/blog/20230401-Daeraxa-AprUpdate","/blog/20230401-Daeraxa-AprUpdate.md"]],["v-0af04e6e","/blog/20230401-confused-Techie-PON.html",{title:"Meet PON (Pulsar Object Notation)",type:"article",readingTime:{minutes:1.75,words:525},excerpt:`

    The release of PON!

    +`,author:"confused-Techie",date:"2023-04-01T00:00:00.000Z",localizedDate:"April 1, 2023",category:["dev"],tag:["joke"]},["/blog/20230401-confused-Techie-PON","/blog/20230401-confused-Techie-PON.md"]],["v-3a723159","/blog/20230418-Daeraxa-v1.104.0.html",{title:"Get Ready for another fantastically essential release, Pulsar 1.104.0 is now available!",type:"article",readingTime:{minutes:2.51,words:753},excerpt:`

    Check out our newest Regular Release for Pulsar! Available Now!

    +`,author:"Daeraxa",date:"2023-04-15T00:00:00.000Z",localizedDate:"April 15, 2023",category:["dev"],tag:["release"]},["/blog/20230418-Daeraxa-v1.104.0","/blog/20230418-Daeraxa-v1.104.0.md"]],["v-1d4a9cb2","/blog/20230430-Daeraxa-Survey2.html",{title:"Survey: Project Feedback",type:"article",readingTime:{minutes:.4,words:120},excerpt:`

    A short survey asking for community feedback on a couple of aspects of the project

    +`,author:"Daeraxa",date:"2023-04-30T00:00:00.000Z",localizedDate:"April 30, 2023",category:["survey"],tag:["community","socials"]},["/blog/20230430-Daeraxa-Survey2","/blog/20230430-Daeraxa-Survey2.md"]],["v-638b3e6c","/blog/20230501-Daeraxa-MayUpdate.html",{title:"Community Update",type:"article",readingTime:{minutes:6.49,words:1947},excerpt:`

    Is it a bird? Is it a plane? No it's the May community update!

    +`,author:"Daeraxa",date:"2023-05-01T00:00:00.000Z",localizedDate:"May 1, 2023",category:["news","log"],tag:["update"]},["/blog/20230501-Daeraxa-MayUpdate","/blog/20230501-Daeraxa-MayUpdate.md"]],["v-dbbf0e92","/blog/20230516-Daeraxa-v1.105.0.html",{title:"Welcome to a rad new release, Pulsar 1.105.0 is available now!",type:"article",readingTime:{minutes:3.13,words:939},excerpt:`

    Welcome to a rad new release, Pulsar 1.105.0 is available now!

    +`,author:"Daeraxa",date:"2023-05-15T00:00:00.000Z",localizedDate:"May 15, 2023",category:["dev"],tag:["release"]},["/blog/20230516-Daeraxa-v1.105.0","/blog/20230516-Daeraxa-v1.105.0.md"]],["v-aebfc812","/blog/20230525-Daeraxa-Survey2-Results.html",{title:"Survey Results: Project feedback",type:"article",readingTime:{minutes:4.9,words:1469},excerpt:`

    Here we have the results and comments of our second community survey!

    +`,author:"Daeraxa",date:"2023-05-25T00:00:00.000Z",localizedDate:"May 25, 2023",category:["survey"],tag:["community","feedback"]},["/blog/20230525-Daeraxa-Survey2-Results","/blog/20230525-Daeraxa-Survey2-Results.md"]],["v-54f2f18e","/blog/20230601-Daeraxa-JuneUpdate.html",{title:"Community Update",type:"article",readingTime:{minutes:7.95,words:2385},excerpt:`

    What's this? No... it can't be. But it is! It's the newest instalment of the Pulsar Community Update!

    +`,author:"Daeraxa",date:"2023-06-01T00:00:00.000Z",localizedDate:"June 1, 2023",category:["news","log"],tag:["update"]},["/blog/20230601-Daeraxa-JuneUpdate","/blog/20230601-Daeraxa-JuneUpdate.md"]],["v-25d26d73","/blog/20230610-Daeraxa-2kStars.html",{title:"2k Stars!",type:"article",readingTime:{minutes:.54,words:161},excerpt:`

    We just hit 2000 stars on GitHub!

    +`,author:"Daeraxa",date:"2023-06-10T00:00:00.000Z",localizedDate:"June 10, 2023",category:["news"],tag:["github","stars"]},["/blog/20230610-Daeraxa-2kStars","/blog/20230610-Daeraxa-2kStars.md"]],["v-7e31f297","/blog/20230616-Daeraxa-v1.106.0.html",{title:"Pulsar v1.106.0: A Focus on Grammars",type:"article",readingTime:{minutes:3.65,words:1096},excerpt:`

    Welcome to our latest release, Pulsar 1.106.0 is available now!

    +`,author:"Daeraxa",date:"2023-06-15T00:00:00.000Z",localizedDate:"June 15, 2023",category:["dev"],tag:["release"]},["/blog/20230616-Daeraxa-v1.106.0","/blog/20230616-Daeraxa-v1.106.0.md"]],["v-4b1ee0b9","/blog/20230701-Daeraxa-JulyUpdate.html",{title:"Community Update",type:"article",readingTime:{minutes:5.62,words:1687},excerpt:`

    What has it got in its pocketses? It's the July community update!

    +`,author:"Daeraxa",date:"2023-07-01T00:00:00.000Z",localizedDate:"July 1, 2023",category:["news","log"],tag:["update"]},["/blog/20230701-Daeraxa-JulyUpdate","/blog/20230701-Daeraxa-JulyUpdate.md"]],["v-5e247e06","/blog/20230715-DeeDeeG-v1.107.0.html",{title:"Fresh off the CI press: Pulsar 1.107.0 is available now!",type:"article",readingTime:{minutes:3.97,words:1190},excerpt:`

    Fresh off the CI press: Pulsar 1.107.0 is available now!

    +`,author:"Daeraxa",date:"2023-07-15T00:00:00.000Z",localizedDate:"July 15, 2023",category:["dev"],tag:["release"]},["/blog/20230715-DeeDeeG-v1.107.0","/blog/20230715-DeeDeeG-v1.107.0.md"]],["v-6bf84516","/blog/20230716-Daeraxa-v1.107.1.html",{title:"Hotfix: Pulsar v1.107.1",type:"article",readingTime:{minutes:.39,words:118},excerpt:`

    Hotfix: Pulsar 1.107.1 is available now!

    +`,author:"Daeraxa",date:"2023-07-16T00:00:00.000Z",localizedDate:"July 16, 2023",category:["dev"],tag:["release"]},["/blog/20230716-Daeraxa-v1.107.1","/blog/20230716-Daeraxa-v1.107.1.md"]],["v-3cb6722f","/blog/20230801-Daeraxa-AugustUpdate.html",{title:"Community Update",type:"article",readingTime:{minutes:4.95,words:1486},excerpt:`

    How many roads must a man walk down? Not a clue, but what I do know is that this is the one and only Pulsar Community Update!

    +`,author:"Daeraxa",date:"2023-08-01T00:00:00.000Z",localizedDate:"August 1, 2023",category:["news","log"],tag:["update"]},["/blog/20230801-Daeraxa-AugustUpdate","/blog/20230801-Daeraxa-AugustUpdate.md"]],["v-0730d41e","/blog/20230816-DeeDeeG-v1.108.0.html",{title:"Great Scott! A new Pulsar release. Pulsar 1.108.0 is available now!",type:"article",readingTime:{minutes:3.8,words:1140},excerpt:`

    Great Scott! A new Pulsar release. Pulsar 1.108.0 is available now!

    +`,author:"Daeraxa",date:"2023-08-16T00:00:00.000Z",localizedDate:"August 16, 2023",category:["dev"],tag:["release"]},["/blog/20230816-DeeDeeG-v1.108.0","/blog/20230816-DeeDeeG-v1.108.0.md"]],["v-7b64cdf7","/blog/20230825-Daeraxa-ChocolateyUpdate.html",{title:"Chocolatey packages are up to date again!",type:"article",readingTime:{minutes:1.08,words:324},excerpt:`

    An update on the status of our Chocolatey packages.

    +`,author:"Daeraxa",date:"2023-08-25T00:00:00.000Z",localizedDate:"August 25, 2023",category:["news"],tag:["windows","chocolatey","package manager"]},["/blog/20230825-Daeraxa-ChocolateyUpdate","/blog/20230825-Daeraxa-ChocolateyUpdate.md"]],["v-356c6c42","/blog/20230903-confused-Techie-pulsars-ci.html",{title:"Pulsar's CI",type:"article",readingTime:{minutes:6.43,words:1930},excerpt:`

    How Pulsar's CI has recently changed.

    +`,author:"confused-Techie",date:"2023-09-07T00:00:00.000Z",localizedDate:"September 7, 2023",category:["dev"],tag:["ci"]},["/blog/20230903-confused-Techie-pulsars-ci","/blog/20230903-confused-Techie-pulsars-ci.md"]],["v-4dac8919","/blog/20230904-Daeraxa-SeptemberUpdate.html",{title:"Community Update",type:"article",readingTime:{minutes:4.34,words:1303},excerpt:`

    I want you to act as a Pulsar team member writing a blog post. I will provide titles of recent changes. You will elaborate on each topic and make each paragraph coherent from my ramblings. You will welcome all readers to the September Pulsar community update.

    +`,author:"Daeraxa",date:"2023-09-04T00:00:00.000Z",localizedDate:"September 4, 2023",category:["news","log"],tag:["update"]},["/blog/20230904-Daeraxa-SeptemberUpdate","/blog/20230904-Daeraxa-SeptemberUpdate.md"]],["v-42666037","/blog/20230916-Daeraxa-v1.109.0.html",{title:"Going the whole nine yards: Get Pulsar 1.109.0 now!",type:"article",readingTime:{minutes:5.33,words:1598},excerpt:`

    Going the whole nine yards: Get Pulsar 1.109.0 now!

    +`,author:"Daeraxa",date:"2023-09-16T00:00:00.000Z",localizedDate:"September 16, 2023",category:["dev"],tag:["release"]},["/blog/20230916-Daeraxa-v1.109.0","/blog/20230916-Daeraxa-v1.109.0.md"]],["v-64adae58","/blog/20230917-Daeraxa-LemmyCommunity.html",{title:"Lemmy community now open!",type:"article",readingTime:{minutes:.97,words:290},excerpt:`

    Join !pulsaredit@lemmy.ml!

    +`,author:"Daeraxa",date:"2023-09-17T00:00:00.000Z",localizedDate:"September 17, 2023",category:["news"],tag:["windows","chocolatey","package manager"]},["/blog/20230917-Daeraxa-LemmyCommunity","/blog/20230917-Daeraxa-LemmyCommunity.md"]],["v-25bd77f6","/blog/20230925-savetheclocktower-modern-tree-sitter-part-1.html",{title:"Modern Tree-sitter, part 1: the new old feature",type:"article",readingTime:{minutes:8.62,words:2585},excerpt:`

    The last few releases of Pulsar have been bragging about a feature that arguably isn\u2019t even new: our experimental \u201Cmodern\u201D Tree-sitter implementation. You might\u2019ve read that phrase a few times now without fully understanding what it means, and an explanation is long overdue.

    +`,author:"savetheclocktower",date:"2023-09-25T00:00:00.000Z",localizedDate:"September 25, 2023",category:["dev"],tag:["modernization","tree-sitter"]},["/blog/20230925-savetheclocktower-modern-tree-sitter-part-1","/blog/20230925-savetheclocktower-modern-tree-sitter-part-1.md"]],["v-5bad2e3c","/blog/20230927-savetheclocktower-modern-tree-sitter-part-2.html",{title:"Modern Tree-sitter, part 2: why scopes matter",type:"article",readingTime:{minutes:7.84,words:2351},excerpt:`

    In the last post, I tried to explain why the new Tree-sitter integration was worth writing about in the first place: because we needed to integrate it into a system defined by TextMate grammars, and we had to solve some challenging problems along the way.

    +

    Today I\u2019ll try to illustrate what that system looks like and why it\u2019s important.

    +`,author:"savetheclocktower",date:"2023-09-27T00:00:00.000Z",localizedDate:"September 27, 2023",category:["dev"],tag:["modernization","tree-sitter"]},["/blog/20230927-savetheclocktower-modern-tree-sitter-part-2","/blog/20230927-savetheclocktower-modern-tree-sitter-part-2.md"]],["v-6cc4d458","/blog/20231004-Daeraxa-OctoberUpdate.html",{title:"Community Update",type:"article",readingTime:{minutes:5.58,words:1674},excerpt:`

    As the leaves turn brown and the days grow shorter, make sure you draw up a chair and settle in for a nice, warm, pumpkin spice edition of the Pulsar community update!

    +`,author:"Daeraxa",date:"2023-10-04T00:00:00.000Z",localizedDate:"October 4, 2023",category:["news","log"],tag:["update"]},["/blog/20231004-Daeraxa-OctoberUpdate","/blog/20231004-Daeraxa-OctoberUpdate.md"]],["v-8b6c6124","/blog/20231013-savetheclocktower-modern-tree-sitter-part-3.html",{title:"Modern Tree-sitter, part 3: syntax highlighting via queries",type:"article",readingTime:{minutes:15.79,words:4738},excerpt:`

    Last time I laid out the case for why we chose to embrace TextMate-style scope names, even in newer Tree-sitter grammars. I set a difficult challenge for Pulsar: make it so that a Tree-sitter grammar can do anything a TextMate grammar can do.

    +

    Today I\u2019d like to show you the specific problems that we had to solve in order to pull that off.

    +`,author:"savetheclocktower",date:"2023-10-13T00:00:00.000Z",localizedDate:"October 13, 2023",category:["dev"],tag:["modernization","tree-sitter"]},["/blog/20231013-savetheclocktower-modern-tree-sitter-part-3","/blog/20231013-savetheclocktower-modern-tree-sitter-part-3.md"]],["v-e8324d12","/blog/20231016-Daeraxa-v1.110.0.html",{title:"Armed with a big ol' can of Raid: Pulsar 1.110.0 is available now!",type:"article",readingTime:{minutes:3.97,words:1192},excerpt:`

    Armed with a big ol' can of Raid: Pulsar 1.110.0 is available now!

    +`,author:"Daeraxa",date:"2023-10-16T00:00:00.000Z",localizedDate:"October 16, 2023",category:["dev"],tag:["release"]},["/blog/20231016-Daeraxa-v1.110.0","/blog/20231016-Daeraxa-v1.110.0.md"]],["v-5fc19751","/blog/20231031-savetheclocktower-modern-tree-sitter-part-4.html",{title:"Modern Tree-sitter, part 4: indentation and code folding",type:"article",readingTime:{minutes:13.28,words:3983},excerpt:`

    Last time we looked at Tree-sitter\u2019s query system and showed how it can be used to make a syntax highlighting engine in Pulsar. But syntax highlighting is simply the most visible of the various tasks that a language package performs.

    +

    Today we\u2019ll look at two other systems \u2014 indentation hinting and code folding \u2014 and I\u2019ll explain how queries can be used to support each one.

    +`,author:"savetheclocktower",date:"2023-10-31T00:00:00.000Z",localizedDate:"October 31, 2023",category:["dev"],tag:["modernization","tree-sitter"]},["/blog/20231031-savetheclocktower-modern-tree-sitter-part-4","/blog/20231031-savetheclocktower-modern-tree-sitter-part-4.md"]],["v-879bed80","/blog/20231109-Daeraxa-NovemberUpdate.html",{title:"Community Update",type:"article",readingTime:{minutes:3.85,words:1156},excerpt:`

    What month is most important for a Pulsar? A supernova-ember! ...What isn't a bad joke is this, the Pulsar community update!

    +`,author:"Daeraxa",date:"2023-11-09T00:00:00.000Z",localizedDate:"November 9, 2023",category:["news","log"],tag:["update"]},["/blog/20231109-Daeraxa-NovemberUpdate","/blog/20231109-Daeraxa-NovemberUpdate.md"]],["v-168f28ae","/blog/20231110-savetheclocktower-modern-tree-sitter-part-5.html",{title:"Modern Tree-sitter, part 5: injections",type:"article",readingTime:{minutes:16.04,words:4812},excerpt:`

    One annoying thing that software developers do is insist on writing in more than one language at once. Web developers are espeically obnoxious about this \u2014 routinely, for instance, putting CSS inside their HTML, or HTML inside their JavaScript, or CSS inside their HTML inside their JavaScript.

    +

    Code editors like Pulsar need to roll with this, so today we\u2019ll talk about how the modern Tree-sitter system handles what we call injections.

    +`,author:"savetheclocktower",date:"2023-11-10T00:00:00.000Z",localizedDate:"November 10, 2023",category:["dev"],tag:["modernization","tree-sitter"]},["/blog/20231110-savetheclocktower-modern-tree-sitter-part-5","/blog/20231110-savetheclocktower-modern-tree-sitter-part-5.md"]],["v-77f85357","/blog/20231116-Daeraxa-v1.111.0.html",{title:"If you're API and you know it, clap your hands!",type:"article",readingTime:{minutes:3.71,words:1113},excerpt:`

    Pulsar 1.111.0 is available now!

    +`,author:"Daeraxa",date:"2023-11-16T00:00:00.000Z",localizedDate:"November 16, 2023",category:["dev"],tag:["release"]},["/blog/20231116-Daeraxa-v1.111.0","/blog/20231116-Daeraxa-v1.111.0.md"]],["v-04bbc674","/blog/20231212-Daeraxa-DecemberUpdate.html",{title:"Community Update",type:"article",readingTime:{minutes:3.45,words:1036},excerpt:`

    If you have been good this year, you might just find something in your stocking this Christmas. Until then, you can find the Pulsar community update right here!

    +`,author:"Daeraxa",date:"2023-12-11T00:00:00.000Z",localizedDate:"December 11, 2023",category:["news","log"],tag:["update"]},["/blog/20231212-Daeraxa-DecemberUpdate","/blog/20231212-Daeraxa-DecemberUpdate.md"]],["v-6409cd37","/blog/20231216-Daeraxa-v1.112.0.html",{title:"Christmas has come early",type:"article",readingTime:{minutes:3.57,words:1071},excerpt:`

    Pulsar 1.112.0 is available now!

    +`,author:"Daeraxa",date:"2023-12-16T00:00:00.000Z",localizedDate:"December 16, 2023",category:["dev"],tag:["release"]},["/blog/20231216-Daeraxa-v1.112.0","/blog/20231216-Daeraxa-v1.112.0.md"]],["v-15a14140","/blog/20231219-DeeDeeG-v1.112.1.html",{title:"Hotfix: Pulsar v1.112.1",type:"article",readingTime:{minutes:.43,words:130},excerpt:`

    Hotfix: Pulsar 1.112.1 is available now!

    +`,author:"DeeDeeG",date:"2023-12-19T00:00:00.000Z",localizedDate:"December 19, 2023",category:["dev"],tag:["release"]},["/blog/20231219-DeeDeeG-v1.112.1","/blog/20231219-DeeDeeG-v1.112.1.md"]],["v-4f70c30a","/blog/20240112-Daeraxa-JanuaryUpdate.html",{title:"Community Update",type:"article",readingTime:{minutes:2.64,words:793},excerpt:`

    Happy new year! Welcome to the first Pulsar community update of 2024!

    +`,author:"Daeraxa",date:"2024-01-12T00:00:00.000Z",localizedDate:"January 12, 2024",category:["news","log"],tag:["update"]},["/blog/20240112-Daeraxa-JanuaryUpdate","/blog/20240112-Daeraxa-JanuaryUpdate.md"]],["v-3500d176","/blog/20240115-Daeraxa-v1.113.0.html",{title:"Unlucky for some, but not us. Our 13th release, Pulsar 1.113.0, is available now!",type:"article",readingTime:{minutes:3.46,words:1039},excerpt:`

    Pulsar 1.113.0 is available now!

    +`,author:"Daeraxa",date:"2024-01-15T00:00:00.000Z",localizedDate:"January 15, 2024",category:["dev"],tag:["release"]},["/blog/20240115-Daeraxa-v1.113.0","/blog/20240115-Daeraxa-v1.113.0.md"]],["v-67f98f2c","/blog/20240122-savetheclocktower-modern-tree-sitter-part-6.html",{title:"Modern Tree-sitter, part 6: reinventing symbols-view",type:"article",readingTime:{minutes:11.98,words:3594},excerpt:`

    We\u2019ve been telling a series of stories about all the different ways that Tree-sitter can improve the editing experience in Pulsar. Today\u2019s story about symbols-view starts a bit slowly, but it\u2019s got a great ending:\xA0the addition of a major new feature to Pulsar 1.113.

    +`,author:"savetheclocktower",date:"2024-01-22T00:00:00.000Z",localizedDate:"January 22, 2024",category:["dev"],tag:["modernization","tree-sitter"]},["/blog/20240122-savetheclocktower-modern-tree-sitter-part-6","/blog/20240122-savetheclocktower-modern-tree-sitter-part-6.md"]],["v-1181f0fe","/blog/20240124-mauricioszabo-the-quest-for-electron-lts.html",{title:"The quest for Electron LTS",type:"article",readingTime:{minutes:5.21,words:1564},excerpt:"",author:"mauricioszabo",date:"2024-01-24T00:00:00.000Z",localizedDate:"January 24, 2024",category:["dev"],tag:["modernization","electron"]},["/blog/20240124-mauricioszabo-the-quest-for-electron-lts","/blog/20240124-mauricioszabo-the-quest-for-electron-lts.md"]],["v-703ed5aa","/blog/20240201-Daeraxa-FebruaryUpdate.html",{title:"Community Update",type:"article",readingTime:{minutes:3.84,words:1151},excerpt:`

    Here it comes sashaying into your feeds, it's the Fab-ruary community update!

    +`,author:"Daeraxa",date:"2024-02-01T00:00:00.000Z",localizedDate:"February 1, 2024",category:["news","log"],tag:["update"]},["/blog/20240201-Daeraxa-FebruaryUpdate","/blog/20240201-Daeraxa-FebruaryUpdate.md"]],["v-21124b56","/blog/20240215-Daeraxa-v1.114.0.html",{title:"A Valentine's release bursting with love, Pulsar 1.114.0 is available now!",type:"article",readingTime:{minutes:3.7,words:1110},excerpt:`

    Pulsar 1.114.0 is available now!

    +`,author:"Daeraxa",date:"2024-02-16T00:00:00.000Z",localizedDate:"February 16, 2024",category:["dev"],tag:["release"]},["/blog/20240215-Daeraxa-v1.114.0","/blog/20240215-Daeraxa-v1.114.0.md"]],["v-88d8cd3c","/blog/20240323-savetheclocktower-v1.115.0.html",{title:"A week later than you\u2019re accustomed to \u2014 but worth the wait! Pulsar 1.115.0 is available now!",type:"article",readingTime:{minutes:2.03,words:609},excerpt:`

    Pulsar 1.115.0 is available now!

    +`,author:"savetheclocktower",date:"2024-03-23T00:00:00.000Z",localizedDate:"March 23, 2024",category:["dev"],tag:["release"]},["/blog/20240323-savetheclocktower-v1.115.0","/blog/20240323-savetheclocktower-v1.115.0.md"]],["v-3ac80fd4","/blog/20240417-confused-Techie-v1.116.0.html",{title:"For all the code ninjas out there, Pulsar 1.116.0 is available now!",type:"article",readingTime:{minutes:1.79,words:536},excerpt:`

    Pulsar 1.116.0 is available now!

    +`,author:"confused-Techie",date:"2024-04-17T00:00:00.000Z",localizedDate:"April 17, 2024",category:["dev"],tag:["release"]},["/blog/20240417-confused-Techie-v1.116.0","/blog/20240417-confused-Techie-v1.116.0.md"]],["v-83b0b1e4","/blog/20240520-confused-Techie-v1.117.0.html",{title:"With special love for Markdown and Tree-sitter, Pulsar 1.117.0 is available now!",type:"article",readingTime:{minutes:1.74,words:523},excerpt:`

    Pulsar 1.117.0 is available now!

    +`,author:"confused-Techie",date:"2024-05-20T00:00:00.000Z",localizedDate:"May 20, 2024",category:["dev"],tag:["release"]},["/blog/20240520-confused-Techie-v1.117.0","/blog/20240520-confused-Techie-v1.117.0.md"]],["v-cdb5fcd6","/blog/20240616-confused-Techie-v1.118.0.html",{title:"Hot dog, it's another Pulsar release!",type:"article",readingTime:{minutes:1.94,words:581},excerpt:`

    Pulsar 1.118.0 is available now!

    +`,author:"confused-Techie",date:"2024-06-16T00:00:00.000Z",localizedDate:"June 16, 2024",category:["dev"],tag:["release"]},["/blog/20240616-confused-Techie-v1.118.0","/blog/20240616-confused-Techie-v1.118.0.md"]],["v-d8729a94","/blog/20240717-confused-Techie-v1.119.0.html",{title:"Pulsar v1.119.0 is Live!",type:"article",readingTime:{minutes:1.37,words:410},excerpt:`

    Pulsar 1.119.0 is available now!

    +`,author:"confused-Techie",date:"2024-07-17T00:00:00.000Z",localizedDate:"July 17, 2024",category:["dev"],tag:["release"]},["/blog/20240717-confused-Techie-v1.119.0","/blog/20240717-confused-Techie-v1.119.0.md"]],["v-147825fb","/docs/",{title:"Documentation Home",type:"article",readingTime:{minutes:.94,words:283},excerpt:"Home for Pulsar's Documentation"},["/docs/index.html","/docs/index.md"]],["v-ed2312e4","/docs/atom-archive/",{title:"Atom Documentation Archive",type:"article",readingTime:{minutes:.7,words:210},excerpt:"Archived Atom Documentation in its original form"},["/docs/atom-archive/index.html","/docs/atom-archive/index.md"]],["v-033b47ca","/docs/launch-manual/",{title:"Launch Manual",type:"article",readingTime:{minutes:.78,words:233},excerpt:""},["/docs/launch-manual/index.html","/docs/launch-manual/index.md"]],["v-0166a572","/docs/packages/",{title:"Packages",type:"article",readingTime:{minutes:1.07,words:322},excerpt:"Documentation Resource for Packages"},["/docs/packages/index.html","/docs/packages/index.md"]],["v-cf57a236","/docs/resources/",{title:"Resources",type:"article",readingTime:{minutes:.26,words:78},excerpt:""},["/docs/resources/index.html","/docs/resources/index.md"]],["v-ad7b6e3a","/docs/atom-archive/api/",{title:"Reference : API",type:"article",readingTime:{minutes:.02,words:6},excerpt:""},["/docs/atom-archive/api/index.html","/docs/atom-archive/api/index.md"]],["v-4368211c","/docs/atom-archive/atom-server-side-apis/",{title:"Appendix E : Atom server-side APIs",type:"article",readingTime:{minutes:.34,words:102},excerpt:""},["/docs/atom-archive/atom-server-side-apis/index.html","/docs/atom-archive/atom-server-side-apis/index.md"]],["v-3973978e","/docs/atom-archive/behind-atom/",{title:"Chapter 4 : Behind Atom",type:"article",readingTime:{minutes:.59,words:176},excerpt:""},["/docs/atom-archive/behind-atom/index.html","/docs/atom-archive/behind-atom/index.md"]],["v-ad775132","/docs/atom-archive/faq/",{title:"Appendix B : FAQ",type:"article",readingTime:{minutes:1.5,words:449},excerpt:""},["/docs/atom-archive/faq/index.html","/docs/atom-archive/faq/index.md"]],["v-634d92c5","/docs/atom-archive/getting-started/",{title:"Chapter 1 : Getting started",type:"article",readingTime:{minutes:.29,words:86},excerpt:""},["/docs/atom-archive/getting-started/index.html","/docs/atom-archive/getting-started/index.md"]],["v-6c7e3cf8","/docs/atom-archive/hacking-atom/",{title:"Chapter 3 : Hacking Atom",type:"article",readingTime:{minutes:1.26,words:378},excerpt:""},["/docs/atom-archive/hacking-atom/index.html","/docs/atom-archive/hacking-atom/index.md"]],["v-6e0db890","/docs/atom-archive/resources/",{title:"Appendix A : Resources",type:"article",readingTime:{minutes:.22,words:66},excerpt:""},["/docs/atom-archive/resources/index.html","/docs/atom-archive/resources/index.md"]],["v-a6cdf728","/docs/atom-archive/shadow-dom/",{title:"Appendix C : Shadow DOM",type:"article",readingTime:{minutes:.28,words:85},excerpt:""},["/docs/atom-archive/shadow-dom/index.html","/docs/atom-archive/shadow-dom/index.md"]],["v-36dcd4ed","/docs/atom-archive/upgrading-to-1-0-apis/",{title:"Appendix D : Upgrading to 1.0 APIs",type:"article",readingTime:{minutes:.56,words:167},excerpt:""},["/docs/atom-archive/upgrading-to-1-0-apis/index.html","/docs/atom-archive/upgrading-to-1-0-apis/index.md"]],["v-3835d9b2","/docs/atom-archive/using-atom/",{title:"Chapter 2 : Using Atom",type:"article",readingTime:{minutes:.86,words:259},excerpt:""},["/docs/atom-archive/using-atom/index.html","/docs/atom-archive/using-atom/index.md"]],["v-6566cb89","/docs/packages/core/",{title:"Core Packages Documentation",type:"article",readingTime:{minutes:.46,words:139},excerpt:"Wiki Resource for many First Party Packages"},["/docs/packages/core/index.html","/docs/packages/core/index.md"]],["v-625b0d60","/docs/resources/conduct/",{title:"Code of Conduct",type:"article",readingTime:{minutes:.09,words:26},excerpt:""},["/docs/resources/conduct/index.html","/docs/resources/conduct/index.md"]],["v-734cbc8c","/docs/resources/glossary/",{title:"Glossary",type:"article",readingTime:{minutes:.01,words:3},excerpt:""},["/docs/resources/glossary/index.html","/docs/resources/glossary/index.md"]],["v-a73e70e8","/docs/resources/privacy/",{title:"Privacy Policy",type:"article",readingTime:{minutes:.08,words:25},excerpt:""},["/docs/resources/privacy/index.html","/docs/resources/privacy/index.md"]],["v-49ddb9a0","/docs/resources/pulsar-api/",{title:"Pulsar API",type:"article",readingTime:{minutes:.13,words:39},excerpt:""},["/docs/resources/pulsar-api/index.html","/docs/resources/pulsar-api/index.md"]],["v-923287ec","/docs/resources/tooling/",{title:"Tooling",type:"article",readingTime:{minutes:.08,words:24},excerpt:""},["/docs/resources/tooling/index.html","/docs/resources/tooling/index.md"]],["v-7080a64e","/docs/resources/website/",{title:"Website",type:"article",readingTime:{minutes:.22,words:67},excerpt:""},["/docs/resources/website/index.html","/docs/resources/website/index.md"]],["v-7c51deeb","/docs/atom-archive/atom-server-side-apis/sections/atom-package-server-api.html",{title:"",type:"article",readingTime:{minutes:3.12,words:936},excerpt:""},["/docs/atom-archive/atom-server-side-apis/sections/atom-package-server-api","/docs/atom-archive/atom-server-side-apis/sections/atom-package-server-api.md"]],["v-1c7613fc","/docs/atom-archive/atom-server-side-apis/sections/atom-update-server-api.html",{title:"",type:"article",readingTime:{minutes:.21,words:63},excerpt:""},["/docs/atom-archive/atom-server-side-apis/sections/atom-update-server-api","/docs/atom-archive/atom-server-side-apis/sections/atom-update-server-api.md"]],["v-6ff500c4","/docs/atom-archive/behind-atom/sections/configuration-api.html",{title:"",type:"article",readingTime:{minutes:.98,words:294},excerpt:""},["/docs/atom-archive/behind-atom/sections/configuration-api","/docs/atom-archive/behind-atom/sections/configuration-api.md"]],["v-15f047dd","/docs/atom-archive/behind-atom/sections/developing-node-modules.html",{title:"",type:"article",readingTime:{minutes:1.02,words:306},excerpt:""},["/docs/atom-archive/behind-atom/sections/developing-node-modules","/docs/atom-archive/behind-atom/sections/developing-node-modules.md"]],["v-778fbb37","/docs/atom-archive/behind-atom/sections/how-atom-uses-chromium-snapshots.html",{title:"",type:"article",readingTime:{minutes:1.04,words:313},excerpt:""},["/docs/atom-archive/behind-atom/sections/how-atom-uses-chromium-snapshots","/docs/atom-archive/behind-atom/sections/how-atom-uses-chromium-snapshots.md"]],["v-012c1bd2","/docs/atom-archive/behind-atom/sections/interacting-with-other-packages-via-services.html",{title:"",type:"article",readingTime:{minutes:.82,words:246},excerpt:""},["/docs/atom-archive/behind-atom/sections/interacting-with-other-packages-via-services","/docs/atom-archive/behind-atom/sections/interacting-with-other-packages-via-services.md"]],["v-b6a94a42","/docs/atom-archive/behind-atom/sections/keymaps-in-depth.html",{title:"",type:"article",readingTime:{minutes:5.76,words:1727},excerpt:""},["/docs/atom-archive/behind-atom/sections/keymaps-in-depth","/docs/atom-archive/behind-atom/sections/keymaps-in-depth.md"]],["v-ee9016a0","/docs/atom-archive/behind-atom/sections/maintaining-your-packages.html",{title:"",type:"article",readingTime:{minutes:2.65,words:795},excerpt:""},["/docs/atom-archive/behind-atom/sections/maintaining-your-packages","/docs/atom-archive/behind-atom/sections/maintaining-your-packages.md"]],["v-4bb0fcfd","/docs/atom-archive/behind-atom/sections/scoped-settings-scopes-and-scope-descriptors.html",{title:"",type:"article",readingTime:{minutes:1.81,words:543},excerpt:""},["/docs/atom-archive/behind-atom/sections/scoped-settings-scopes-and-scope-descriptors","/docs/atom-archive/behind-atom/sections/scoped-settings-scopes-and-scope-descriptors.md"]],["v-5c815036","/docs/atom-archive/behind-atom/sections/serialization-in-atom.html",{title:"",type:"article",readingTime:{minutes:1.78,words:534},excerpt:""},["/docs/atom-archive/behind-atom/sections/serialization-in-atom","/docs/atom-archive/behind-atom/sections/serialization-in-atom.md"]],["v-293dceca","/docs/atom-archive/behind-atom/sections/summary.html",{title:"",type:"article",readingTime:{minutes:.06,words:17},excerpt:""},["/docs/atom-archive/behind-atom/sections/summary","/docs/atom-archive/behind-atom/sections/summary.md"]],["v-78782b86","/docs/atom-archive/faq/sections/atom-in-the-cloud.html",{title:"",type:"article",readingTime:{minutes:.12,words:36},excerpt:""},["/docs/atom-archive/faq/sections/atom-in-the-cloud","/docs/atom-archive/faq/sections/atom-in-the-cloud.md"]],["v-bc28a444","/docs/atom-archive/faq/sections/how-can-i-contribute-to-atom.html",{title:"",type:"article",readingTime:{minutes:.26,words:79},excerpt:""},["/docs/atom-archive/faq/sections/how-can-i-contribute-to-atom","/docs/atom-archive/faq/sections/how-can-i-contribute-to-atom.md"]],["v-71e09326","/docs/atom-archive/faq/sections/how-can-i-tell-if-subpixel-antialiasing-is-working.html",{title:"",type:"article",readingTime:{minutes:.32,words:95},excerpt:""},["/docs/atom-archive/faq/sections/how-can-i-tell-if-subpixel-antialiasing-is-working","/docs/atom-archive/faq/sections/how-can-i-tell-if-subpixel-antialiasing-is-working.md"]],["v-83ab2196","/docs/atom-archive/faq/sections/how-do-i-accept-input-from-my-program-or-script-when-using-the-script-package.html",{title:"",type:"article",readingTime:{minutes:.33,words:100},excerpt:""},["/docs/atom-archive/faq/sections/how-do-i-accept-input-from-my-program-or-script-when-using-the-script-package","/docs/atom-archive/faq/sections/how-do-i-accept-input-from-my-program-or-script-when-using-the-script-package.md"]],["v-58dbd8e4","/docs/atom-archive/faq/sections/how-do-i-build-or-execute-code-i-ve-written-in-atom.html",{title:"",type:"article",readingTime:{minutes:.55,words:165},excerpt:""},["/docs/atom-archive/faq/sections/how-do-i-build-or-execute-code-i-ve-written-in-atom","/docs/atom-archive/faq/sections/how-do-i-build-or-execute-code-i-ve-written-in-atom.md"]],["v-69d7b7aa","/docs/atom-archive/faq/sections/how-do-i-make-atom-recognize-a-file-with-extension-x-as-language-y.html",{title:"",type:"article",readingTime:{minutes:.31,words:94},excerpt:""},["/docs/atom-archive/faq/sections/how-do-i-make-atom-recognize-a-file-with-extension-x-as-language-y","/docs/atom-archive/faq/sections/how-do-i-make-atom-recognize-a-file-with-extension-x-as-language-y.md"]],["v-6d023415","/docs/atom-archive/faq/sections/how-do-i-make-the-welcome-screen-stop-showing-up.html",{title:"",type:"article",readingTime:{minutes:.16,words:48},excerpt:""},["/docs/atom-archive/faq/sections/how-do-i-make-the-welcome-screen-stop-showing-up","/docs/atom-archive/faq/sections/how-do-i-make-the-welcome-screen-stop-showing-up.md"]],["v-58d2c851","/docs/atom-archive/faq/sections/how-do-i-preview-web-page-changes-automatically.html",{title:"",type:"article",readingTime:{minutes:.24,words:71},excerpt:""},["/docs/atom-archive/faq/sections/how-do-i-preview-web-page-changes-automatically","/docs/atom-archive/faq/sections/how-do-i-preview-web-page-changes-automatically.md"]],["v-446213fa","/docs/atom-archive/faq/sections/how-do-i-turn-on-line-wrap.html",{title:"",type:"article",readingTime:{minutes:.22,words:65},excerpt:""},["/docs/atom-archive/faq/sections/how-do-i-turn-on-line-wrap","/docs/atom-archive/faq/sections/how-do-i-turn-on-line-wrap.md"]],["v-671ef772","/docs/atom-archive/faq/sections/how-do-i-uninstall-atom-on-macos.html",{title:"",type:"article",readingTime:{minutes:.25,words:75},excerpt:""},["/docs/atom-archive/faq/sections/how-do-i-uninstall-atom-on-macos","/docs/atom-archive/faq/sections/how-do-i-uninstall-atom-on-macos.md"]],["v-6e77009f","/docs/atom-archive/faq/sections/how-do-i-use-a-newline-in-the-result-of-find-and-replace.html",{title:"",type:"article",readingTime:{minutes:.37,words:112},excerpt:""},["/docs/atom-archive/faq/sections/how-do-i-use-a-newline-in-the-result-of-find-and-replace","/docs/atom-archive/faq/sections/how-do-i-use-a-newline-in-the-result-of-find-and-replace.md"]],["v-50328086","/docs/atom-archive/faq/sections/i-am-unable-to-update-to-the-latest-version-of-atom-on-macos-how-do-i-fix-this.html",{title:"",type:"article",readingTime:{minutes:.56,words:169},excerpt:""},["/docs/atom-archive/faq/sections/i-am-unable-to-update-to-the-latest-version-of-atom-on-macos-how-do-i-fix-this","/docs/atom-archive/faq/sections/i-am-unable-to-update-to-the-latest-version-of-atom-on-macos-how-do-i-fix-this.md"]],["v-52c5c70a","/docs/atom-archive/faq/sections/i-have-a-question-about-a-specific-atom-community-package-where-is-the-best-place-to-ask-it.html",{title:"",type:"article",readingTime:{minutes:.4,words:119},excerpt:""},["/docs/atom-archive/faq/sections/i-have-a-question-about-a-specific-atom-community-package-where-is-the-best-place-to-ask-it","/docs/atom-archive/faq/sections/i-have-a-question-about-a-specific-atom-community-package-where-is-the-best-place-to-ask-it.md"]],["v-779d7601","/docs/atom-archive/faq/sections/i-m-getting-an-error-about-a-self-signed-certificate-what-do-i-do.html",{title:"",type:"article",readingTime:{minutes:.53,words:160},excerpt:""},["/docs/atom-archive/faq/sections/i-m-getting-an-error-about-a-self-signed-certificate-what-do-i-do","/docs/atom-archive/faq/sections/i-m-getting-an-error-about-a-self-signed-certificate-what-do-i-do.md"]],["v-6bf81d3a","/docs/atom-archive/faq/sections/i-m-having-a-problem-with-julia-what-do-i-do.html",{title:"",type:"article",readingTime:{minutes:.17,words:51},excerpt:""},["/docs/atom-archive/faq/sections/i-m-having-a-problem-with-julia-what-do-i-do","/docs/atom-archive/faq/sections/i-m-having-a-problem-with-julia-what-do-i-do.md"]],["v-103f9060","/docs/atom-archive/faq/sections/i-m-having-a-problem-with-platformio-what-do-i-do.html",{title:"",type:"article",readingTime:{minutes:.19,words:56},excerpt:""},["/docs/atom-archive/faq/sections/i-m-having-a-problem-with-platformio-what-do-i-do","/docs/atom-archive/faq/sections/i-m-having-a-problem-with-platformio-what-do-i-do.md"]],["v-f400a296","/docs/atom-archive/faq/sections/i-m-trying-to-change-my-syntax-colors-from-styles-less-but-it-isn-t-working.html",{title:"",type:"article",readingTime:{minutes:.21,words:64},excerpt:""},["/docs/atom-archive/faq/sections/i-m-trying-to-change-my-syntax-colors-from-styles-less-but-it-isn-t-working","/docs/atom-archive/faq/sections/i-m-trying-to-change-my-syntax-colors-from-styles-less-but-it-isn-t-working.md"]],["v-69a4dea8","/docs/atom-archive/faq/sections/i-m-using-an-international-keyboard-and-keys-that-use-altgr-or-ctrl-alt-aren-t-working.html",{title:"",type:"article",readingTime:{minutes:.16,words:49},excerpt:""},["/docs/atom-archive/faq/sections/i-m-using-an-international-keyboard-and-keys-that-use-altgr-or-ctrl-alt-aren-t-working","/docs/atom-archive/faq/sections/i-m-using-an-international-keyboard-and-keys-that-use-altgr-or-ctrl-alt-aren-t-working.md"]],["v-4ea46d24","/docs/atom-archive/faq/sections/is-atom-open-source.html",{title:"",type:"article",readingTime:{minutes:.06,words:19},excerpt:""},["/docs/atom-archive/faq/sections/is-atom-open-source","/docs/atom-archive/faq/sections/is-atom-open-source.md"]],["v-3e42c98a","/docs/atom-archive/faq/sections/macos-mojave-font-rendering-change.html",{title:"",type:"article",readingTime:{minutes:.79,words:237},excerpt:""},["/docs/atom-archive/faq/sections/macos-mojave-font-rendering-change","/docs/atom-archive/faq/sections/macos-mojave-font-rendering-change.md"]],["v-5bf72dd3","/docs/atom-archive/faq/sections/the-menu-bar-disappeared-how-do-i-get-it-back.html",{title:"",type:"article",readingTime:{minutes:.25,words:74},excerpt:""},["/docs/atom-archive/faq/sections/the-menu-bar-disappeared-how-do-i-get-it-back","/docs/atom-archive/faq/sections/the-menu-bar-disappeared-how-do-i-get-it-back.md"]],["v-25bdd335","/docs/atom-archive/faq/sections/what-does-atom-cost.html",{title:"",type:"article",readingTime:{minutes:.14,words:43},excerpt:""},["/docs/atom-archive/faq/sections/what-does-atom-cost","/docs/atom-archive/faq/sections/what-does-atom-cost.md"]],["v-7e76b4c2","/docs/atom-archive/faq/sections/what-does-safe-mode-do.html",{title:"",type:"article",readingTime:{minutes:.47,words:142},excerpt:""},["/docs/atom-archive/faq/sections/what-does-safe-mode-do","/docs/atom-archive/faq/sections/what-does-safe-mode-do.md"]],["v-8a4db88a","/docs/atom-archive/faq/sections/what-is-this-line-on-the-right-in-the-editor-view.html",{title:"",type:"article",readingTime:{minutes:.23,words:70},excerpt:""},["/docs/atom-archive/faq/sections/what-is-this-line-on-the-right-in-the-editor-view","/docs/atom-archive/faq/sections/what-is-this-line-on-the-right-in-the-editor-view.md"]],["v-39dbf018","/docs/atom-archive/faq/sections/what-platforms-does-atom-run-on.html",{title:"",type:"article",readingTime:{minutes:.19,words:56},excerpt:""},["/docs/atom-archive/faq/sections/what-platforms-does-atom-run-on","/docs/atom-archive/faq/sections/what-platforms-does-atom-run-on.md"]],["v-09b88655","/docs/atom-archive/faq/sections/what-s-the-difference-between-an-ide-and-an-editor.html",{title:"",type:"article",readingTime:{minutes:1.13,words:340},excerpt:""},["/docs/atom-archive/faq/sections/what-s-the-difference-between-an-ide-and-an-editor","/docs/atom-archive/faq/sections/what-s-the-difference-between-an-ide-and-an-editor.md"]],["v-616d7939","/docs/atom-archive/faq/sections/why-does-atom-collect-usage-data.html",{title:"",type:"article",readingTime:{minutes:.27,words:80},excerpt:""},["/docs/atom-archive/faq/sections/why-does-atom-collect-usage-data","/docs/atom-archive/faq/sections/why-does-atom-collect-usage-data.md"]],["v-43d2cead","/docs/atom-archive/faq/sections/why-does-macos-say-that-atom-wants-to-access-my-calendar-contacts-photos-etc.html",{title:"",type:"article",readingTime:{minutes:2.67,words:802},excerpt:""},["/docs/atom-archive/faq/sections/why-does-macos-say-that-atom-wants-to-access-my-calendar-contacts-photos-etc","/docs/atom-archive/faq/sections/why-does-macos-say-that-atom-wants-to-access-my-calendar-contacts-photos-etc.md"]],["v-65c8d593","/docs/atom-archive/faq/sections/why-is-atom-deleting-trailing-whitespace-why-is-there-a-newline-at-the-end-of-the-file.html",{title:"",type:"article",readingTime:{minutes:.38,words:115},excerpt:""},["/docs/atom-archive/faq/sections/why-is-atom-deleting-trailing-whitespace-why-is-there-a-newline-at-the-end-of-the-file","/docs/atom-archive/faq/sections/why-is-atom-deleting-trailing-whitespace-why-is-there-a-newline-at-the-end-of-the-file.md"]],["v-258423d8","/docs/atom-archive/getting-started/sections/atom-basics.html",{title:"",type:"article",readingTime:{minutes:8.3,words:2490},excerpt:""},["/docs/atom-archive/getting-started/sections/atom-basics","/docs/atom-archive/getting-started/sections/atom-basics.md"]],["v-d48e3644","/docs/atom-archive/getting-started/sections/installing-atom.html",{title:"",type:"article",readingTime:{minutes:5.22,words:1567},excerpt:""},["/docs/atom-archive/getting-started/sections/installing-atom","/docs/atom-archive/getting-started/sections/installing-atom.md"]],["v-3162898f","/docs/atom-archive/getting-started/sections/summary.html",{title:"",type:"article",readingTime:{minutes:.18,words:53},excerpt:""},["/docs/atom-archive/getting-started/sections/summary","/docs/atom-archive/getting-started/sections/summary.md"]],["v-60602147","/docs/atom-archive/getting-started/sections/why-atom.html",{title:"",type:"article",readingTime:{minutes:2.41,words:723},excerpt:""},["/docs/atom-archive/getting-started/sections/why-atom","/docs/atom-archive/getting-started/sections/why-atom.md"]],["v-00ff85c6","/docs/atom-archive/hacking-atom/sections/contributing-to-official-atom-packages.html",{title:"",type:"article",readingTime:{minutes:1.24,words:372},excerpt:""},["/docs/atom-archive/hacking-atom/sections/contributing-to-official-atom-packages","/docs/atom-archive/hacking-atom/sections/contributing-to-official-atom-packages.md"]],["v-38bce125","/docs/atom-archive/hacking-atom/sections/converting-from-textmate.html",{title:"",type:"article",readingTime:{minutes:1.31,words:394},excerpt:""},["/docs/atom-archive/hacking-atom/sections/converting-from-textmate","/docs/atom-archive/hacking-atom/sections/converting-from-textmate.md"]],["v-752e2619","/docs/atom-archive/hacking-atom/sections/creating-a-fork-of-a-core-package-in-atom-atom.html",{title:"",type:"article",readingTime:{minutes:1.54,words:462},excerpt:""},["/docs/atom-archive/hacking-atom/sections/creating-a-fork-of-a-core-package-in-atom-atom","/docs/atom-archive/hacking-atom/sections/creating-a-fork-of-a-core-package-in-atom-atom.md"]],["v-7741ac96","/docs/atom-archive/hacking-atom/sections/creating-a-grammar.html",{title:"",type:"article",readingTime:{minutes:7.29,words:2186},excerpt:""},["/docs/atom-archive/hacking-atom/sections/creating-a-grammar","/docs/atom-archive/hacking-atom/sections/creating-a-grammar.md"]],["v-41063e28","/docs/atom-archive/hacking-atom/sections/creating-a-legacy-textmate-grammar.html",{title:"",type:"article",readingTime:{minutes:5.26,words:1579},excerpt:""},["/docs/atom-archive/hacking-atom/sections/creating-a-legacy-textmate-grammar","/docs/atom-archive/hacking-atom/sections/creating-a-legacy-textmate-grammar.md"]],["v-049d4d93","/docs/atom-archive/hacking-atom/sections/creating-a-theme.html",{title:"",type:"article",readingTime:{minutes:5.37,words:1610},excerpt:""},["/docs/atom-archive/hacking-atom/sections/creating-a-theme","/docs/atom-archive/hacking-atom/sections/creating-a-theme.md"]],["v-0762ec4e","/docs/atom-archive/hacking-atom/sections/cross-platform-compatibility.html",{title:"",type:"article",readingTime:{minutes:1.81,words:543},excerpt:""},["/docs/atom-archive/hacking-atom/sections/cross-platform-compatibility","/docs/atom-archive/hacking-atom/sections/cross-platform-compatibility.md"]],["v-28cbb2a8","/docs/atom-archive/hacking-atom/sections/debugging.html",{title:"",type:"article",readingTime:{minutes:10.29,words:3087},excerpt:""},["/docs/atom-archive/hacking-atom/sections/debugging","/docs/atom-archive/hacking-atom/sections/debugging.md"]],["v-bb85f9fa","/docs/atom-archive/hacking-atom/sections/hacking-on-atom-core.html",{title:"",type:"article",readingTime:{minutes:7.57,words:2272},excerpt:""},["/docs/atom-archive/hacking-atom/sections/hacking-on-atom-core","/docs/atom-archive/hacking-atom/sections/hacking-on-atom-core.md"]],["v-06c8b972","/docs/atom-archive/hacking-atom/sections/handling-uris.html",{title:"",type:"article",readingTime:{minutes:2.46,words:739},excerpt:""},["/docs/atom-archive/hacking-atom/sections/handling-uris","/docs/atom-archive/hacking-atom/sections/handling-uris.md"]],["v-5746d296","/docs/atom-archive/hacking-atom/sections/iconography.html",{title:"",type:"article",readingTime:{minutes:.81,words:243},excerpt:""},["/docs/atom-archive/hacking-atom/sections/iconography","/docs/atom-archive/hacking-atom/sections/iconography.md"]],["v-be0ab716","/docs/atom-archive/hacking-atom/sections/maintaining-a-fork-of-a-core-package-in-atom-atom.html",{title:"",type:"article",readingTime:{minutes:1.6,words:479},excerpt:""},["/docs/atom-archive/hacking-atom/sections/maintaining-a-fork-of-a-core-package-in-atom-atom","/docs/atom-archive/hacking-atom/sections/maintaining-a-fork-of-a-core-package-in-atom-atom.md"]],["v-5d85798e","/docs/atom-archive/hacking-atom/sections/package-active-editor-info.html",{title:"",type:"article",readingTime:{minutes:4.38,words:1314},excerpt:""},["/docs/atom-archive/hacking-atom/sections/package-active-editor-info","/docs/atom-archive/hacking-atom/sections/package-active-editor-info.md"]],["v-4284f843","/docs/atom-archive/hacking-atom/sections/package-modifying-text.html",{title:"",type:"article",readingTime:{minutes:3.89,words:1166},excerpt:""},["/docs/atom-archive/hacking-atom/sections/package-modifying-text","/docs/atom-archive/hacking-atom/sections/package-modifying-text.md"]],["v-36ba24e9","/docs/atom-archive/hacking-atom/sections/package-word-count.html",{title:"",type:"article",readingTime:{minutes:12.95,words:3886},excerpt:""},["/docs/atom-archive/hacking-atom/sections/package-word-count","/docs/atom-archive/hacking-atom/sections/package-word-count.md"]],["v-5d0a102e","/docs/atom-archive/hacking-atom/sections/publishing.html",{title:"",type:"article",readingTime:{minutes:2.26,words:678},excerpt:""},["/docs/atom-archive/hacking-atom/sections/publishing","/docs/atom-archive/hacking-atom/sections/publishing.md"]],["v-7d723830","/docs/atom-archive/hacking-atom/sections/summary.html",{title:"",type:"article",readingTime:{minutes:.37,words:112},excerpt:""},["/docs/atom-archive/hacking-atom/sections/summary","/docs/atom-archive/hacking-atom/sections/summary.md"]],["v-7061e28e","/docs/atom-archive/hacking-atom/sections/the-init-file.html",{title:"",type:"article",readingTime:{minutes:1.02,words:305},excerpt:""},["/docs/atom-archive/hacking-atom/sections/the-init-file","/docs/atom-archive/hacking-atom/sections/the-init-file.md"]],["v-5aa3b8b8","/docs/atom-archive/hacking-atom/sections/tools-of-the-trade.html",{title:"",type:"article",readingTime:{minutes:.82,words:245},excerpt:""},["/docs/atom-archive/hacking-atom/sections/tools-of-the-trade","/docs/atom-archive/hacking-atom/sections/tools-of-the-trade.md"]],["v-f2fe7262","/docs/atom-archive/hacking-atom/sections/writing-specs.html",{title:"",type:"article",readingTime:{minutes:4.62,words:1385},excerpt:""},["/docs/atom-archive/hacking-atom/sections/writing-specs","/docs/atom-archive/hacking-atom/sections/writing-specs.md"]],["v-53169d12","/docs/atom-archive/resources/sections/glossary.html",{title:"",type:"article",readingTime:{minutes:1.58,words:474},excerpt:""},["/docs/atom-archive/resources/sections/glossary","/docs/atom-archive/resources/sections/glossary.md"]],["v-5a059e16","/docs/atom-archive/shadow-dom/sections/removing-shadow-dom-styles.html",{title:"",type:"article",readingTime:{minutes:1.92,words:576},excerpt:""},["/docs/atom-archive/shadow-dom/sections/removing-shadow-dom-styles","/docs/atom-archive/shadow-dom/sections/removing-shadow-dom-styles.md"]],["v-8f696676","/docs/atom-archive/upgrading-to-1-0-apis/sections/upgrading-your-package.html",{title:"",type:"article",readingTime:{minutes:8.5,words:2551},excerpt:""},["/docs/atom-archive/upgrading-to-1-0-apis/sections/upgrading-your-package","/docs/atom-archive/upgrading-to-1-0-apis/sections/upgrading-your-package.md"]],["v-416277a8","/docs/atom-archive/upgrading-to-1-0-apis/sections/upgrading-your-syntax-theme.html",{title:"",type:"article",readingTime:{minutes:.94,words:282},excerpt:""},["/docs/atom-archive/upgrading-to-1-0-apis/sections/upgrading-your-syntax-theme","/docs/atom-archive/upgrading-to-1-0-apis/sections/upgrading-your-syntax-theme.md"]],["v-3bf32d2b","/docs/atom-archive/upgrading-to-1-0-apis/sections/upgrading-your-ui-theme-or-package-selectors.html",{title:"",type:"article",readingTime:{minutes:3.17,words:952},excerpt:""},["/docs/atom-archive/upgrading-to-1-0-apis/sections/upgrading-your-ui-theme-or-package-selectors","/docs/atom-archive/upgrading-to-1-0-apis/sections/upgrading-your-ui-theme-or-package-selectors.md"]],["v-2cf2a68a","/docs/atom-archive/using-atom/sections/atom-packages.html",{title:"",type:"article",readingTime:{minutes:3.17,words:950},excerpt:""},["/docs/atom-archive/using-atom/sections/atom-packages","/docs/atom-archive/using-atom/sections/atom-packages.md"]],["v-0fe6afd0","/docs/atom-archive/using-atom/sections/atom-selections.html",{title:"",type:"article",readingTime:{minutes:1.43,words:428},excerpt:""},["/docs/atom-archive/using-atom/sections/atom-selections","/docs/atom-archive/using-atom/sections/atom-selections.md"]],["v-2c165692","/docs/atom-archive/using-atom/sections/autocomplete.html",{title:"",type:"article",readingTime:{minutes:.42,words:127},excerpt:""},["/docs/atom-archive/using-atom/sections/autocomplete","/docs/atom-archive/using-atom/sections/autocomplete.md"]],["v-61e2f142","/docs/atom-archive/using-atom/sections/basic-customization.html",{title:"",type:"article",readingTime:{minutes:7.47,words:2240},excerpt:""},["/docs/atom-archive/using-atom/sections/basic-customization","/docs/atom-archive/using-atom/sections/basic-customization.md"]],["v-34299b28","/docs/atom-archive/using-atom/sections/editing-and-deleting-text.html",{title:"",type:"article",readingTime:{minutes:4.5,words:1349},excerpt:""},["/docs/atom-archive/using-atom/sections/editing-and-deleting-text","/docs/atom-archive/using-atom/sections/editing-and-deleting-text.md"]],["v-0311d835","/docs/atom-archive/using-atom/sections/find-and-replace.html",{title:"",type:"article",readingTime:{minutes:2.09,words:628},excerpt:""},["/docs/atom-archive/using-atom/sections/find-and-replace","/docs/atom-archive/using-atom/sections/find-and-replace.md"]],["v-6252cab2","/docs/atom-archive/using-atom/sections/folding.html",{title:"",type:"article",readingTime:{minutes:.87,words:262},excerpt:""},["/docs/atom-archive/using-atom/sections/folding","/docs/atom-archive/using-atom/sections/folding.md"]],["v-faca997a","/docs/atom-archive/using-atom/sections/github-package.html",{title:"",type:"article",readingTime:{minutes:6.29,words:1887},excerpt:""},["/docs/atom-archive/using-atom/sections/github-package","/docs/atom-archive/using-atom/sections/github-package.md"]],["v-3ae8d9e8","/docs/atom-archive/using-atom/sections/grammar.html",{title:"",type:"article",readingTime:{minutes:.7,words:211},excerpt:""},["/docs/atom-archive/using-atom/sections/grammar","/docs/atom-archive/using-atom/sections/grammar.md"]],["v-4d358836","/docs/atom-archive/using-atom/sections/moving-in-atom.html",{title:"",type:"article",readingTime:{minutes:5.59,words:1677},excerpt:""},["/docs/atom-archive/using-atom/sections/moving-in-atom","/docs/atom-archive/using-atom/sections/moving-in-atom.md"]],["v-0d49e5a8","/docs/atom-archive/using-atom/sections/panes.html",{title:"",type:"article",readingTime:{minutes:.84,words:252},excerpt:""},["/docs/atom-archive/using-atom/sections/panes","/docs/atom-archive/using-atom/sections/panes.md"]],["v-52fd2aae","/docs/atom-archive/using-atom/sections/pending-pane-items.html",{title:"",type:"article",readingTime:{minutes:.76,words:227},excerpt:""},["/docs/atom-archive/using-atom/sections/pending-pane-items","/docs/atom-archive/using-atom/sections/pending-pane-items.md"]],["v-3aec9d69","/docs/atom-archive/using-atom/sections/snippets.html",{title:"",type:"article",readingTime:{minutes:3.33,words:998},excerpt:""},["/docs/atom-archive/using-atom/sections/snippets","/docs/atom-archive/using-atom/sections/snippets.md"]],["v-21ac7a6d","/docs/atom-archive/using-atom/sections/summary.html",{title:"",type:"article",readingTime:{minutes:.33,words:99},excerpt:""},["/docs/atom-archive/using-atom/sections/summary","/docs/atom-archive/using-atom/sections/summary.md"]],["v-fd5de582","/docs/atom-archive/using-atom/sections/version-control-in-atom.html",{title:"",type:"article",readingTime:{minutes:2.66,words:799},excerpt:""},["/docs/atom-archive/using-atom/sections/version-control-in-atom","/docs/atom-archive/using-atom/sections/version-control-in-atom.md"]],["v-783f784b","/docs/atom-archive/using-atom/sections/writing-in-atom.html",{title:"",type:"article",readingTime:{minutes:1.87,words:560},excerpt:""},["/docs/atom-archive/using-atom/sections/writing-in-atom","/docs/atom-archive/using-atom/sections/writing-in-atom.md"]],["v-0aa2ec94","/docs/launch-manual/sections/behind-pulsar/",{title:"Behind Pulsar",type:"article",readingTime:{minutes:.51,words:154},excerpt:"Advanced Pulsar hacking"},["/docs/launch-manual/sections/behind-pulsar/index.html","/docs/launch-manual/sections/behind-pulsar/index.md"]],["v-278e8fc5","/docs/launch-manual/sections/core-hacking/",{title:"Hacking the Core",type:"article",readingTime:{minutes:1.73,words:520},excerpt:"Info on building from source + hacking on Pulsar's core"},["/docs/launch-manual/sections/core-hacking/index.html","/docs/launch-manual/sections/core-hacking/index.md"]],["v-3e3eb0e0","/docs/launch-manual/sections/faq/",{title:"FAQ",type:"article",readingTime:{minutes:.18,words:54},excerpt:"Frequently asked questions"},["/docs/launch-manual/sections/faq/index.html","/docs/launch-manual/sections/faq/index.md"]],["v-35eded6e","/docs/launch-manual/sections/getting-started/",{title:"Getting Started",type:"article",readingTime:{minutes:.26,words:78},excerpt:""},["/docs/launch-manual/sections/getting-started/index.html","/docs/launch-manual/sections/getting-started/index.md"]],["v-44f94232","/docs/launch-manual/sections/using-pulsar/",{title:"Using Pulsar",type:"article",readingTime:{minutes:.35,words:104},excerpt:""},["/docs/launch-manual/sections/using-pulsar/index.html","/docs/launch-manual/sections/using-pulsar/index.md"]],["v-7412c3f9","/docs/packages/core/atom-languageclient/",{title:"Atom-LanguageClient",type:"article",readingTime:{minutes:.24,words:72},excerpt:""},["/docs/packages/core/atom-languageclient/index.html","/docs/packages/core/atom-languageclient/index.md"]],["v-4119e722","/docs/packages/core/atom-languageclient/list.html",{title:"List of Pulsar Packages using Pulsar LanguageClient",type:"article",readingTime:{minutes:1.18,words:355},excerpt:""},["/docs/packages/core/atom-languageclient/list","/docs/packages/core/atom-languageclient/list.md"]],["v-4da9456e","/docs/packages/core/atom-languageclient/release-process.html",{title:"Release Process",type:"article",readingTime:{minutes:.81,words:242},excerpt:""},["/docs/packages/core/atom-languageclient/release-process","/docs/packages/core/atom-languageclient/release-process.md"]],["v-4ebc0363","/docs/packages/core/autocomplete-plus/autocomplete-providers.html",{title:"Autocomplete Providers",type:"article",readingTime:{minutes:4.7,words:1409},excerpt:""},["/docs/packages/core/autocomplete-plus/autocomplete-providers","/docs/packages/core/autocomplete-plus/autocomplete-providers.md"]],["v-6df8120e","/docs/packages/core/autocomplete-plus/",{title:"Autocomplete-Plus",type:"article",readingTime:{minutes:.22,words:67},excerpt:""},["/docs/packages/core/autocomplete-plus/index.html","/docs/packages/core/autocomplete-plus/index.md"]],["v-5d8b44fc","/docs/packages/core/autocomplete-plus/provider-api.html",{title:"Provider API",type:"article",readingTime:{minutes:4.68,words:1405},excerpt:""},["/docs/packages/core/autocomplete-plus/provider-api","/docs/packages/core/autocomplete-plus/provider-api.md"]],["v-0c09001f","/docs/packages/core/autocomplete-plus/symbolprovider-config-api.html",{title:"SymbolProvider Config API",type:"article",readingTime:{minutes:1.58,words:475},excerpt:""},["/docs/packages/core/autocomplete-plus/symbolprovider-config-api","/docs/packages/core/autocomplete-plus/symbolprovider-config-api.md"]],["v-04523903","/docs/packages/core/github/",{title:"GitHub",type:"article",readingTime:{minutes:.34,words:101},excerpt:""},["/docs/packages/core/github/index.html","/docs/packages/core/github/index.md"]],["v-6a77de6d","/docs/packages/core/github/june-2017.html",{title:"June 2017 - Monthly Planning",type:"article",readingTime:{minutes:1.07,words:321},excerpt:""},["/docs/packages/core/github/june-2017","/docs/packages/core/github/june-2017.md"]],["v-3e52d25b","/docs/packages/core/ide-java/incomplete-classpath-warning.html",{title:"Incomplete Classpath Warning",type:"article",readingTime:{minutes:.33,words:98},excerpt:""},["/docs/packages/core/ide-java/incomplete-classpath-warning","/docs/packages/core/ide-java/incomplete-classpath-warning.md"]],["v-49f587fe","/docs/packages/core/ide-java/",{title:"IDE-Java",type:"article",readingTime:{minutes:.21,words:63},excerpt:""},["/docs/packages/core/ide-java/index.html","/docs/packages/core/ide-java/index.md"]],["v-c8081340","/docs/resources/website/sections/blog-guide.html",{title:"",type:"article",readingTime:{minutes:2.51,words:754},excerpt:`

    Blog guide

    +

    This is a guide on how to add a blog post to the website which will be shown on +https://pulsar-edit.dev/article/.

    +

    We are using the Vuepress Blog Plugin +which comes as part of our Vuepress Hope Theme with some light configuration +to suit our purposes.

    +

    This is all implemented in the main website repository.

    +

    Writing a new post

    +
      +
    • Create a new .md file in pulsar-edit.github.io/docs/blog. +
        +
      • This file should be named YYYYMMDD-<author>-<title>.md e.g 20221031-CreativeUsername-ThisIsMyBlogPost.md
      • +
      +
    • +
    • The metadata displayed on the website is dependent on a number of items that +are configured in the YAML frontmatter of the file. You may in theory omit any of these except the title +field but it's strongly recommend that you use title, author, date, category +and tag as the minimum as the others will default to false. +
        +
      • Frontmatter items supported currently are: +
          +
        • title - String: The displayed title of the post, consider this as H1
        • +
        • author - String: The name of the author to be displayed.
        • +
        • date - Date (ISO 8601): Allows display of date as well as enabling +sorting on the timeline, set to the same as your filename date but with +hyphens (e.g. 2022-10-31).
        • +
        • category - String (multiline): Enables filtering by category, this should be based on the +subject of the post e.g. release, dev log, announcement. This is a +multiline field if you want to set more than one category.
        • +
        • tag - String (multiline): Enables filtering by tags, this should be based on the content of +the post and areas it touches on e.g. website, editor, config.
        • +
        • sticky - Bool: Enables "pinning" on the
        • +
        • star - Bool: Enables use of the star category for any important articles +we want to remain visible.
        • +
        • article - Bool: You probably won't want to use this but setting this to false +will exclude this page from appearing. This is set on the "example" blog +post intentionally.
        • +
        +
      • +
      +
    • +
    • An excerpt can be added to the post by creating an html comment \`
    • +
    +`},["/docs/resources/website/sections/blog-guide","/docs/resources/website/sections/blog-guide.md"]],["v-fa6566c6","/docs/resources/website/sections/building.html",{title:"",type:"article",readingTime:{minutes:.97,words:292},excerpt:""},["/docs/resources/website/sections/building","/docs/resources/website/sections/building.md"]],["v-c2b13dfe","/docs/resources/website/sections/configuration-files.html",{title:"",type:"article",readingTime:{minutes:2.12,words:635},excerpt:""},["/docs/resources/website/sections/configuration-files","/docs/resources/website/sections/configuration-files.md"]],["v-71e2ac9c","/docs/resources/website/sections/document-style.html",{title:"",type:"article",readingTime:{minutes:2.58,words:774},excerpt:""},["/docs/resources/website/sections/document-style","/docs/resources/website/sections/document-style.md"]],["v-f38bdb06","/docs/resources/website/sections/file-organization.html",{title:"",type:"article",readingTime:{minutes:1.67,words:500},excerpt:""},["/docs/resources/website/sections/file-organization","/docs/resources/website/sections/file-organization.md"]],["v-4861ba81","/docs/launch-manual/sections/behind-pulsar/sections/configuration-api.html",{title:"",type:"article",readingTime:{minutes:1.06,words:318},excerpt:""},["/docs/launch-manual/sections/behind-pulsar/sections/configuration-api","/docs/launch-manual/sections/behind-pulsar/sections/configuration-api.md"]],["v-1a40e700","/docs/launch-manual/sections/behind-pulsar/sections/developing-node-modules.html",{title:"",type:"article",readingTime:{minutes:1.24,words:371},excerpt:""},["/docs/launch-manual/sections/behind-pulsar/sections/developing-node-modules","/docs/launch-manual/sections/behind-pulsar/sections/developing-node-modules.md"]],["v-0a930154","/docs/launch-manual/sections/behind-pulsar/sections/interacting-with-other-packages-via-services.html",{title:"",type:"article",readingTime:{minutes:.82,words:246},excerpt:""},["/docs/launch-manual/sections/behind-pulsar/sections/interacting-with-other-packages-via-services","/docs/launch-manual/sections/behind-pulsar/sections/interacting-with-other-packages-via-services.md"]],["v-563ab79c","/docs/launch-manual/sections/behind-pulsar/sections/keymaps-in-depth.html",{title:"",type:"article",readingTime:{minutes:5.67,words:1701},excerpt:""},["/docs/launch-manual/sections/behind-pulsar/sections/keymaps-in-depth","/docs/launch-manual/sections/behind-pulsar/sections/keymaps-in-depth.md"]],["v-322ec6da","/docs/launch-manual/sections/behind-pulsar/sections/maintaining-your-packages.html",{title:"",type:"article",readingTime:{minutes:2.79,words:837},excerpt:""},["/docs/launch-manual/sections/behind-pulsar/sections/maintaining-your-packages","/docs/launch-manual/sections/behind-pulsar/sections/maintaining-your-packages.md"]],["v-56da0c3a","/docs/launch-manual/sections/behind-pulsar/sections/scoped-settings-scopes-and-scope-descriptors.html",{title:"",type:"article",readingTime:{minutes:2.15,words:645},excerpt:""},["/docs/launch-manual/sections/behind-pulsar/sections/scoped-settings-scopes-and-scope-descriptors","/docs/launch-manual/sections/behind-pulsar/sections/scoped-settings-scopes-and-scope-descriptors.md"]],["v-33739e9c","/docs/launch-manual/sections/behind-pulsar/sections/serialization-in-pulsar.html",{title:"",type:"article",readingTime:{minutes:1.81,words:544},excerpt:""},["/docs/launch-manual/sections/behind-pulsar/sections/serialization-in-pulsar","/docs/launch-manual/sections/behind-pulsar/sections/serialization-in-pulsar.md"]],["v-1b3c033e","/docs/launch-manual/sections/behind-pulsar/sections/summary.html",{title:"",type:"article",readingTime:{minutes:.06,words:17},excerpt:""},["/docs/launch-manual/sections/behind-pulsar/sections/summary","/docs/launch-manual/sections/behind-pulsar/sections/summary.md"]],["v-e880b502","/docs/launch-manual/sections/core-hacking/sections/building-pulsar.html",{title:"",type:"article",readingTime:{minutes:2.21,words:662},excerpt:""},["/docs/launch-manual/sections/core-hacking/sections/building-pulsar","/docs/launch-manual/sections/core-hacking/sections/building-pulsar.md"]],["v-13ba24f3","/docs/launch-manual/sections/core-hacking/sections/contributing-to-official-pulsar-packages.html",{title:"",type:"article",readingTime:{minutes:1.25,words:375},excerpt:""},["/docs/launch-manual/sections/core-hacking/sections/contributing-to-official-pulsar-packages","/docs/launch-manual/sections/core-hacking/sections/contributing-to-official-pulsar-packages.md"]],["v-f3d252b4","/docs/launch-manual/sections/core-hacking/sections/converting-from-textmate.html",{title:"",type:"article",readingTime:{minutes:1.23,words:369},excerpt:""},["/docs/launch-manual/sections/core-hacking/sections/converting-from-textmate","/docs/launch-manual/sections/core-hacking/sections/converting-from-textmate.md"]],["v-6ef9d234","/docs/launch-manual/sections/core-hacking/sections/creating-a-fork-of-a-core-package.html",{title:"",type:"article",readingTime:{minutes:1.57,words:470},excerpt:""},["/docs/launch-manual/sections/core-hacking/sections/creating-a-fork-of-a-core-package","/docs/launch-manual/sections/core-hacking/sections/creating-a-fork-of-a-core-package.md"]],["v-43bb93f6","/docs/launch-manual/sections/core-hacking/sections/creating-a-grammar.html",{title:"",type:"article",readingTime:{minutes:7.46,words:2237},excerpt:""},["/docs/launch-manual/sections/core-hacking/sections/creating-a-grammar","/docs/launch-manual/sections/core-hacking/sections/creating-a-grammar.md"]],["v-1032ada6","/docs/launch-manual/sections/core-hacking/sections/creating-a-legacy-textmate-grammar.html",{title:"",type:"article",readingTime:{minutes:5.27,words:1581},excerpt:""},["/docs/launch-manual/sections/core-hacking/sections/creating-a-legacy-textmate-grammar","/docs/launch-manual/sections/core-hacking/sections/creating-a-legacy-textmate-grammar.md"]],["v-f63d57d8","/docs/launch-manual/sections/core-hacking/sections/creating-a-theme.html",{title:"",type:"article",readingTime:{minutes:5.24,words:1572},excerpt:""},["/docs/launch-manual/sections/core-hacking/sections/creating-a-theme","/docs/launch-manual/sections/core-hacking/sections/creating-a-theme.md"]],["v-78bfd6da","/docs/launch-manual/sections/core-hacking/sections/cross-platform-compatibility.html",{title:"",type:"article",readingTime:{minutes:1.81,words:543},excerpt:""},["/docs/launch-manual/sections/core-hacking/sections/cross-platform-compatibility","/docs/launch-manual/sections/core-hacking/sections/cross-platform-compatibility.md"]],["v-c040e972","/docs/launch-manual/sections/core-hacking/sections/debugging.html",{title:"",type:"article",readingTime:{minutes:10.47,words:3142},excerpt:""},["/docs/launch-manual/sections/core-hacking/sections/debugging","/docs/launch-manual/sections/core-hacking/sections/debugging.md"]],["v-143cd798","/docs/launch-manual/sections/core-hacking/sections/hacking-on-the-core.html",{title:"",type:"article",readingTime:{minutes:.98,words:295},excerpt:""},["/docs/launch-manual/sections/core-hacking/sections/hacking-on-the-core","/docs/launch-manual/sections/core-hacking/sections/hacking-on-the-core.md"]],["v-c277d734","/docs/launch-manual/sections/core-hacking/sections/handling-uris.html",{title:"",type:"article",readingTime:{minutes:2.45,words:734},excerpt:""},["/docs/launch-manual/sections/core-hacking/sections/handling-uris","/docs/launch-manual/sections/core-hacking/sections/handling-uris.md"]],["v-544678d8","/docs/launch-manual/sections/core-hacking/sections/iconography.html",{title:"",type:"article",readingTime:{minutes:.82,words:246},excerpt:""},["/docs/launch-manual/sections/core-hacking/sections/iconography","/docs/launch-manual/sections/core-hacking/sections/iconography.md"]],["v-05ecf928","/docs/launch-manual/sections/core-hacking/sections/maintaining-a-fork-of-a-core-package.html",{title:"",type:"article",readingTime:{minutes:1.56,words:468},excerpt:""},["/docs/launch-manual/sections/core-hacking/sections/maintaining-a-fork-of-a-core-package","/docs/launch-manual/sections/core-hacking/sections/maintaining-a-fork-of-a-core-package.md"]],["v-878fda62","/docs/launch-manual/sections/core-hacking/sections/package-active-editor-info.html",{title:"",type:"article",readingTime:{minutes:4.45,words:1335},excerpt:""},["/docs/launch-manual/sections/core-hacking/sections/package-active-editor-info","/docs/launch-manual/sections/core-hacking/sections/package-active-editor-info.md"]],["v-e59f4bf8","/docs/launch-manual/sections/core-hacking/sections/package-modifying-text.html",{title:"",type:"article",readingTime:{minutes:3.83,words:1149},excerpt:""},["/docs/launch-manual/sections/core-hacking/sections/package-modifying-text","/docs/launch-manual/sections/core-hacking/sections/package-modifying-text.md"]],["v-93d2e1ac","/docs/launch-manual/sections/core-hacking/sections/package-word-count.html",{title:"",type:"article",readingTime:{minutes:12.85,words:3855},excerpt:""},["/docs/launch-manual/sections/core-hacking/sections/package-word-count","/docs/launch-manual/sections/core-hacking/sections/package-word-count.md"]],["v-3ce2332a","/docs/launch-manual/sections/core-hacking/sections/publishing.html",{title:"",type:"article",readingTime:{minutes:2.13,words:639},excerpt:""},["/docs/launch-manual/sections/core-hacking/sections/publishing","/docs/launch-manual/sections/core-hacking/sections/publishing.md"]],["v-82dca6e2","/docs/launch-manual/sections/core-hacking/sections/summary.html",{title:"",type:"article",readingTime:{minutes:.38,words:114},excerpt:""},["/docs/launch-manual/sections/core-hacking/sections/summary","/docs/launch-manual/sections/core-hacking/sections/summary.md"]],["v-69f77fd8","/docs/launch-manual/sections/core-hacking/sections/the-init-file.html",{title:"",type:"article",readingTime:{minutes:1.19,words:356},excerpt:""},["/docs/launch-manual/sections/core-hacking/sections/the-init-file","/docs/launch-manual/sections/core-hacking/sections/the-init-file.md"]],["v-4bffba0e","/docs/launch-manual/sections/core-hacking/sections/tools-of-the-trade.html",{title:"",type:"article",readingTime:{minutes:.93,words:280},excerpt:""},["/docs/launch-manual/sections/core-hacking/sections/tools-of-the-trade","/docs/launch-manual/sections/core-hacking/sections/tools-of-the-trade.md"]],["v-fbe0eb5e","/docs/launch-manual/sections/core-hacking/sections/using-ppm.html",{title:"",type:"article",readingTime:{minutes:.66,words:198},excerpt:""},["/docs/launch-manual/sections/core-hacking/sections/using-ppm","/docs/launch-manual/sections/core-hacking/sections/using-ppm.md"]],["v-28a937ee","/docs/launch-manual/sections/core-hacking/sections/writing-specs.html",{title:"",type:"article",readingTime:{minutes:4.5,words:1351},excerpt:""},["/docs/launch-manual/sections/core-hacking/sections/writing-specs","/docs/launch-manual/sections/core-hacking/sections/writing-specs.md"]],["v-76c0c524","/docs/launch-manual/sections/faq/sections/common-issues.html",{title:"",type:"article",readingTime:{minutes:.37,words:110},excerpt:""},["/docs/launch-manual/sections/faq/sections/common-issues","/docs/launch-manual/sections/faq/sections/common-issues.md"]],["v-495d25f0","/docs/launch-manual/sections/faq/sections/get-help.html",{title:"",type:"article",readingTime:{minutes:.28,words:84},excerpt:""},["/docs/launch-manual/sections/faq/sections/get-help","/docs/launch-manual/sections/faq/sections/get-help.md"]],["v-8c05c62e","/docs/launch-manual/sections/getting-started/sections/installing-pulsar.html",{title:"",type:"article",readingTime:{minutes:3.34,words:1002},excerpt:""},["/docs/launch-manual/sections/getting-started/sections/installing-pulsar","/docs/launch-manual/sections/getting-started/sections/installing-pulsar.md"]],["v-6660d212","/docs/launch-manual/sections/getting-started/sections/pulsar-basics.html",{title:"",type:"article",readingTime:{minutes:9.87,words:2960},excerpt:""},["/docs/launch-manual/sections/getting-started/sections/pulsar-basics","/docs/launch-manual/sections/getting-started/sections/pulsar-basics.md"]],["v-14a6a286","/docs/launch-manual/sections/getting-started/sections/summary.html",{title:"",type:"article",readingTime:{minutes:.18,words:53},excerpt:""},["/docs/launch-manual/sections/getting-started/sections/summary","/docs/launch-manual/sections/getting-started/sections/summary.md"]],["v-1ca4faf8","/docs/launch-manual/sections/getting-started/sections/why-pulsar.html",{title:"",type:"article",readingTime:{minutes:2.4,words:719},excerpt:""},["/docs/launch-manual/sections/getting-started/sections/why-pulsar","/docs/launch-manual/sections/getting-started/sections/why-pulsar.md"]],["v-9ad007fc","/docs/launch-manual/sections/using-pulsar/sections/autocomplete.html",{title:"",type:"article",readingTime:{minutes:.41,words:122},excerpt:""},["/docs/launch-manual/sections/using-pulsar/sections/autocomplete","/docs/launch-manual/sections/using-pulsar/sections/autocomplete.md"]],["v-55d9a5b4","/docs/launch-manual/sections/using-pulsar/sections/basic-customization.html",{title:"",type:"article",readingTime:{minutes:7.25,words:2175},excerpt:""},["/docs/launch-manual/sections/using-pulsar/sections/basic-customization","/docs/launch-manual/sections/using-pulsar/sections/basic-customization.md"]],["v-73d97d01","/docs/launch-manual/sections/using-pulsar/sections/editing-and-deleting-text.html",{title:"",type:"article",readingTime:{minutes:5.24,words:1571},excerpt:""},["/docs/launch-manual/sections/using-pulsar/sections/editing-and-deleting-text","/docs/launch-manual/sections/using-pulsar/sections/editing-and-deleting-text.md"]],["v-282b3c00","/docs/launch-manual/sections/using-pulsar/sections/find-and-replace.html",{title:"",type:"article",readingTime:{minutes:2.09,words:627},excerpt:""},["/docs/launch-manual/sections/using-pulsar/sections/find-and-replace","/docs/launch-manual/sections/using-pulsar/sections/find-and-replace.md"]],["v-ea7dfef2","/docs/launch-manual/sections/using-pulsar/sections/folding.html",{title:"",type:"article",readingTime:{minutes:1.46,words:439},excerpt:""},["/docs/launch-manual/sections/using-pulsar/sections/folding","/docs/launch-manual/sections/using-pulsar/sections/folding.md"]],["v-a1dd9864","/docs/launch-manual/sections/using-pulsar/sections/github-package.html",{title:"",type:"article",readingTime:{minutes:6.19,words:1856},excerpt:""},["/docs/launch-manual/sections/using-pulsar/sections/github-package","/docs/launch-manual/sections/using-pulsar/sections/github-package.md"]],["v-0af9c8e1","/docs/launch-manual/sections/using-pulsar/sections/grammar.html",{title:"",type:"article",readingTime:{minutes:.69,words:206},excerpt:""},["/docs/launch-manual/sections/using-pulsar/sections/grammar","/docs/launch-manual/sections/using-pulsar/sections/grammar.md"]],["v-15cc7078","/docs/launch-manual/sections/using-pulsar/sections/moving-in-pulsar.html",{title:"",type:"article",readingTime:{minutes:5.31,words:1594},excerpt:""},["/docs/launch-manual/sections/using-pulsar/sections/moving-in-pulsar","/docs/launch-manual/sections/using-pulsar/sections/moving-in-pulsar.md"]],["v-76058cbd","/docs/launch-manual/sections/using-pulsar/sections/panes.html",{title:"",type:"article",readingTime:{minutes:1.71,words:512},excerpt:""},["/docs/launch-manual/sections/using-pulsar/sections/panes","/docs/launch-manual/sections/using-pulsar/sections/panes.md"]],["v-67da6db9","/docs/launch-manual/sections/using-pulsar/sections/pending-pane-items.html",{title:"",type:"article",readingTime:{minutes:.76,words:227},excerpt:""},["/docs/launch-manual/sections/using-pulsar/sections/pending-pane-items","/docs/launch-manual/sections/using-pulsar/sections/pending-pane-items.md"]],["v-814e3bea","/docs/launch-manual/sections/using-pulsar/sections/pulsar-packages.html",{title:"",type:"article",readingTime:{minutes:3.86,words:1159},excerpt:""},["/docs/launch-manual/sections/using-pulsar/sections/pulsar-packages","/docs/launch-manual/sections/using-pulsar/sections/pulsar-packages.md"]],["v-bca7e1de","/docs/launch-manual/sections/using-pulsar/sections/pulsar-selections.html",{title:"",type:"article",readingTime:{minutes:1.67,words:502},excerpt:""},["/docs/launch-manual/sections/using-pulsar/sections/pulsar-selections","/docs/launch-manual/sections/using-pulsar/sections/pulsar-selections.md"]],["v-20452234","/docs/launch-manual/sections/using-pulsar/sections/snippets.html",{title:"",type:"article",readingTime:{minutes:3.17,words:951},excerpt:""},["/docs/launch-manual/sections/using-pulsar/sections/snippets","/docs/launch-manual/sections/using-pulsar/sections/snippets.md"]],["v-4a1ab042","/docs/launch-manual/sections/using-pulsar/sections/summary.html",{title:"",type:"article",readingTime:{minutes:.33,words:99},excerpt:""},["/docs/launch-manual/sections/using-pulsar/sections/summary","/docs/launch-manual/sections/using-pulsar/sections/summary.md"]],["v-13272930","/docs/launch-manual/sections/using-pulsar/sections/version-control-in-pulsar.html",{title:"",type:"article",readingTime:{minutes:2.48,words:743},excerpt:""},["/docs/launch-manual/sections/using-pulsar/sections/version-control-in-pulsar","/docs/launch-manual/sections/using-pulsar/sections/version-control-in-pulsar.md"]],["v-e092af18","/docs/launch-manual/sections/using-pulsar/sections/writing-in-pulsar.html",{title:"",type:"article",readingTime:{minutes:1.81,words:544},excerpt:""},["/docs/launch-manual/sections/using-pulsar/sections/writing-in-pulsar","/docs/launch-manual/sections/using-pulsar/sections/writing-in-pulsar.md"]],["v-3706649a","/404.html",{title:"",type:"page",readingTime:{minutes:0,words:0},excerpt:""},["/404"]],["v-5bc93818","/category/",{title:"Category",type:"page",readingTime:{minutes:0,words:0},excerpt:""},["/category/index.html"]],["v-744d024e","/tag/",{title:"Tag",type:"page",readingTime:{minutes:0,words:0},excerpt:""},["/tag/index.html"]],["v-145ac574","/blog/",{title:"Articles",type:"page",readingTime:{minutes:0,words:0},excerpt:""},["/blog/index.html"]],["v-75ed4ea4","/encrypted/",{title:"Encrypted",type:"page",readingTime:{minutes:0,words:0},excerpt:""},["/encrypted/index.html"]],["v-d804e652","/slide/",{title:"Slides",type:"page",readingTime:{minutes:0,words:0},excerpt:""},["/slide/index.html"]],["v-154dc4c4","/star/",{title:"Star",type:"page",readingTime:{minutes:0,words:0},excerpt:""},["/star/index.html"]],["v-01560935","/timeline/",{title:"Timeline",type:"page",readingTime:{minutes:0,words:0},excerpt:""},["/timeline/index.html"]],["v-65ee6ad2","/category/dev/",{title:"dev Category",type:"page",readingTime:{minutes:0,words:0},excerpt:""},["/category/dev/index.html"]],["v-0b77a069","/tag/backend/",{title:"backend Tag",type:"page",readingTime:{minutes:0,words:0},excerpt:""},["/tag/backend/index.html"]],["v-65f23183","/category/log/",{title:"log Category",type:"page",readingTime:{minutes:0,words:0},excerpt:""},["/category/log/index.html"]],["v-f5401b6a","/tag/sunset/",{title:"sunset Tag",type:"page",readingTime:{minutes:0,words:0},excerpt:""},["/tag/sunset/index.html"]],["v-586be6a4","/category/news/",{title:"news Category",type:"page",readingTime:{minutes:0,words:0},excerpt:""},["/category/news/index.html"]],["v-007c0ae2","/tag/video/",{title:"video Tag",type:"page",readingTime:{minutes:0,words:0},excerpt:""},["/tag/video/index.html"]],["v-109540fd","/category/survey/",{title:"survey Category",type:"page",readingTime:{minutes:0,words:0},excerpt:""},["/category/survey/index.html"]],["v-33e16b10","/tag/update/",{title:"update Tag",type:"page",readingTime:{minutes:0,words:0},excerpt:""},["/tag/update/index.html"]],["v-5b38f390","/tag/modernization/",{title:"modernization Tag",type:"page",readingTime:{minutes:0,words:0},excerpt:""},["/tag/modernization/index.html"]],["v-f6458faa","/tag/tree-sitter/",{title:"tree-sitter Tag",type:"page",readingTime:{minutes:0,words:0},excerpt:""},["/tag/tree-sitter/index.html"]],["v-6c28ad56","/tag/release/",{title:"release Tag",type:"page",readingTime:{minutes:0,words:0},excerpt:""},["/tag/release/index.html"]],["v-18ed05d5","/tag/releases/",{title:"releases Tag",type:"page",readingTime:{minutes:0,words:0},excerpt:""},["/tag/releases/index.html"]],["v-7d8d32d8","/tag/rolling/",{title:"rolling Tag",type:"page",readingTime:{minutes:0,words:0},excerpt:""},["/tag/rolling/index.html"]],["v-6486a861","/tag/regular/",{title:"regular Tag",type:"page",readingTime:{minutes:0,words:0},excerpt:""},["/tag/regular/index.html"]],["v-9ffc7398","/tag/community/",{title:"community Tag",type:"page",readingTime:{minutes:0,words:0},excerpt:""},["/tag/community/index.html"]],["v-54365ad2","/tag/socials/",{title:"socials Tag",type:"page",readingTime:{minutes:0,words:0},excerpt:""},["/tag/socials/index.html"]],["v-28a80d22","/tag/joke/",{title:"joke Tag",type:"page",readingTime:{minutes:0,words:0},excerpt:""},["/tag/joke/index.html"]],["v-9c858a88","/tag/feedback/",{title:"feedback Tag",type:"page",readingTime:{minutes:0,words:0},excerpt:""},["/tag/feedback/index.html"]],["v-132a6ac4","/tag/github/",{title:"github Tag",type:"page",readingTime:{minutes:0,words:0},excerpt:""},["/tag/github/index.html"]],["v-08115088","/tag/stars/",{title:"stars Tag",type:"page",readingTime:{minutes:0,words:0},excerpt:""},["/tag/stars/index.html"]],["v-4a89825a","/tag/windows/",{title:"windows Tag",type:"page",readingTime:{minutes:0,words:0},excerpt:""},["/tag/windows/index.html"]],["v-7ab1f304","/tag/chocolatey/",{title:"chocolatey Tag",type:"page",readingTime:{minutes:0,words:0},excerpt:""},["/tag/chocolatey/index.html"]],["v-31f54a17","/tag/package-manager/",{title:"package manager Tag",type:"page",readingTime:{minutes:0,words:0},excerpt:""},["/tag/package-manager/index.html"]],["v-0da0b37b","/tag/ci/",{title:"ci Tag",type:"page",readingTime:{minutes:0,words:0},excerpt:""},["/tag/ci/index.html"]],["v-181c8802","/tag/electron/",{title:"electron Tag",type:"page",readingTime:{minutes:0,words:0},excerpt:""},["/tag/electron/index.html"]]];var Qv=()=>Jv.reduce((e,[t,i,a,n])=>(e.push({name:t,path:i,component:rr,meta:a},...n.map(l=>({path:l,redirect:i}))),e),[{name:"404",path:"/:catchAll(.*)",component:rr}]),Xv=Im,e1=()=>{const e=hg({history:Xv(fl(zt.value.base)),routes:Qv(),scrollBehavior:(t,i,a)=>a||(t.hash?{el:t.hash}:{top:0})});return e.beforeResolve(async(t,i)=>{var a;(t.path!==i.path||i===Tt)&&([Pt.value]=await Promise.all([Yt.resolvePageData(t.name),(a=ds[t.name])==null?void 0:a.__asyncLoader()]))}),e},t1=e=>{e.component("ClientOnly",Xa),e.component("Content",bu)},i1=(e,t)=>{const i=P(()=>Yt.resolveRouteLocale(zt.value.locales,t.currentRoute.value.path)),a=P(()=>Yt.resolveSiteLocaleData(zt.value,i.value)),n=P(()=>Yt.resolvePageFrontmatter(Pt.value)),l=P(()=>Yt.resolvePageHeadTitle(Pt.value,a.value)),o=P(()=>Yt.resolvePageHead(l.value,n.value,a.value)),r=P(()=>Yt.resolvePageLang(Pt.value));return e.provide(Ml,i),e.provide(_u,a),e.provide(pu,n),e.provide(gu,l),e.provide(mu,o),e.provide(vu,r),Object.defineProperties(e.config.globalProperties,{$frontmatter:{get:()=>n.value},$head:{get:()=>o.value},$headTitle:{get:()=>l.value},$lang:{get:()=>r.value},$page:{get:()=>Pt.value},$routeLocale:{get:()=>i.value},$site:{get:()=>zt.value},$siteLocale:{get:()=>a.value},$withBase:{get:()=>mt}}),{pageData:Pt,pageFrontmatter:n,pageHead:o,pageHeadTitle:l,pageLang:r,routeLocale:i,siteData:zt,siteLocaleData:a}},a1=()=>{const e=xe(),t=jv(),i=fu(),a=ne([]),n=()=>{t.value.forEach(o=>{const r=n1(o);r&&a.value.push(r)})},l=()=>{document.documentElement.lang=i.value,a.value.forEach(o=>{o.parentNode===document.head&&document.head.removeChild(o)}),a.value.splice(0,a.value.length),t.value.forEach(o=>{const r=l1(o);r!==null&&(document.head.appendChild(r),a.value.push(r))})};Ke(Gv,l),Se(()=>{n(),l(),De(()=>e.path,()=>l())})},n1=([e,t,i=""])=>{const a=Object.entries(t).map(([r,c])=>ye(c)?`[${r}="${c}"]`:c===!0?`[${r}]`:"").join(""),n=`head > ${e}${a}`;return Array.from(document.querySelectorAll(n)).find(r=>r.innerText===i)||null},l1=([e,t,i])=>{if(!ye(e))return null;const a=document.createElement(e);return vl(t)&&Object.entries(t).forEach(([n,l])=>{ye(l)?a.setAttribute(n,l):l===!0&&a.setAttribute(n,"")}),ye(i)&&a.appendChild(document.createTextNode(i)),a},o1=tp,r1=async()=>{var i;const e=o1({name:"VuepressApp",setup(){var a;a1();for(const n of Ta)(a=n.setup)==null||a.call(n);return()=>[u(js),...Ta.flatMap(({rootComponents:n=[]})=>n.map(l=>u(l)))]}}),t=e1();t1(e),i1(e,t);for(const a of Ta)await((i=a.enhance)==null?void 0:i.call(a,{app:e,router:t,siteData:zt}));return e.use(t),{app:e,router:t}};r1().then(({app:e,router:t})=>{t.isReady().then(()=>{e.mount("#app")})});export{ac as _,$e as a,ts as b,wa as c,r1 as createVueApp,Oe as d,u1 as e,c1 as f,Mi as o,Me as r,Pd as w}; diff --git a/assets/atom-basics.html.54e153e2.js b/assets/atom-basics.html.54e153e2.js new file mode 100644 index 0000000000..0add6d7df1 --- /dev/null +++ b/assets/atom-basics.html.54e153e2.js @@ -0,0 +1,18 @@ +import{_ as l,a as d,b as c,c as h,d as m,e as p,f as u,g as f}from"./finder.04f876a5.js";import{_ as g}from"./platform-selector.697b4dd8.js";import{_ as w,o as b,c as y,a as t,b as e,d as o,w as i,f as a,r}from"./app.0e1565ce.js";const k={},v=a('

    Atom Basics

    Now that Atom is installed on your system, let's fire it up, configure it and get acquainted with the editor.

    When you launch Atom for the first time, you should get a screen that looks like this:

    Atom's welcome screen

    This is the Atom welcome screen and gives you a pretty good starting point for how to get started with the editor.

    Terminology

    ',6),_=a('

    Command Palette

    In that welcome screen, we are introduced to probably the most important command in Atom, the Command Palette. If you press Cmd+Shift+PCtrl+Shift+P while focused in an editor pane, the command palette will pop up.

    Note:

    Throughout the book, we will use shortcut keybindings like Cmd+Shift+PCtrl+Shift+P to demonstrate how to run a command. These are the default keybindings for the platform that we detected you running.

    If you want to see a different platform than the one we detected, you may choose a different one by using the platform selector near the top of the page:

    Platform Selector

    If the Platform Selector is not present, then the current page doesn't have any platform-specific content.

    If you have customized your Atom keymap, you can always see the keybinding you have mapped in the Command Palette or the Keybindings tab in the Settings View.

    This search-driven menu can do just about any major task that is possible in Atom. Instead of clicking around all the application menus to look for something, you can press Cmd+Shift+PCtrl+Shift+P and search for the command.

    Command Palette

    Not only can you see and quickly search through thousands of possible commands, but you can also see if there is a keybinding associated with it. This is great because it means you can guess your way to doing interesting things while also learning the shortcut keystrokes for doing it.

    For the rest of the book, we will try to be clear as to the text you can search for in the Command Palette in addition to the keybinding for different commands.

    Settings and Preferences

    Atom has a number of settings and preferences you can modify in the Settings View.

    Settings View

    This includes things like changing the theme, specifying how to handle wrapping, font settings, tab size, scroll speed and much more. You can also use this screen to install new packages and themes, which we'll cover in Atom Packages.

    To open the Settings View, you can: ::: tabs Settings View

    ',14),x=a('
    • Use the File > Settings menu item in the menu bar

    :::

    • Search for settings-view:open in the Command Palette
    • Use the Cmd+, or Ctrl+, keybinding
    Changing the Theme

    The Settings View also lets you change the themes for Atom. Atom ships with 4 different UI themes, dark and light variants of the Atom and One theme, as well as 8 different syntax themes. You can modify the active theme by clicking on the Themes tab in the sidebar of the Settings View, or you can install new themes by clicking the Install tab.

    Changing the theme from the Settings View

    The UI themes control the style of UI elements like the tabs and the tree view, while the syntax themes control the syntax highlighting of text you load into the editor. To change the syntax or UI theme, simply pick something different in the appropriate dropdown list.

    There are also dozens of themes on https://atom.io that you can choose from if you want something different. We will cover customizing a theme in Style Tweaks and creating your own theme in Creating a Theme.

    Soft Wrap

    You can use the Settings View to specify your whitespace and wrapping preferences.

    Whitespace and wrapping preferences settings

    Enabling "Soft Tabs" will insert spaces instead of actual tab characters when you press the Tab key and the "Tab Length" setting specifies how many spaces to insert when you do so, or how many spaces are used to represent a tab if "Soft Tabs" is disabled.

    The "Soft Wrap" option will wrap lines that are too long to fit in your current window. If soft wrapping is disabled, the lines will simply run off the side of the screen and you will have to scroll the window to see the rest of the content. If "Soft Wrap At Preferred Line Length" is toggled, the lines will wrap at 80 characters instead of the end of the screen. You can also change the default line length to a value other than 80 on this screen.

    ',13),S=a('

    Opening, Modifying, and Saving Files

    Now that your editor is looking and acting how you want, let's start opening up and editing files. This is a text editor after all, right?

    Opening a File

    There are several ways to open a file in Atom. You can do it by choosing File > Open from the menu bar or by pressing Cmd+O Ctrl+O to choose a file from the standard dialog.

    Open file by dialog

    This is useful for opening a file that is not contained in the project you're currently in (more on that next), or if you're starting from a new window for some reason.

    ',6),C=t("code",null,"atom",-1),T={class:"platform-mac"},A=t("code",null,"atom",-1),P=t("code",null,"apm",-1),F={class:"platform-windows platform-linux"},I=t("code",null,"atom",-1),V=t("code",null,"apm",-1),q=a(`

    You can run the atom command with one or more file paths to open up those files in Atom.

    $ atom --help
    +> Atom Editor v1.8.0
    +
    +> Usage: atom [options] [path ...]
    +
    +> One or more paths to files or folders may be specified. If there is an
    +> existing Atom window that contains all of the given folders, the paths
    +> will be opened in that window. Otherwise, they will be opened in a new
    +> window.
    +
    +> ...
    +

    This is a great tool if you're used to the terminal or you work from the terminal a lot. Just fire off atom [files] and you're ready to start editing. You can even open a file at a certain line (and optionally column) so the cursor will be positioned exactly where you want. For example, you may search some keyword in a repository to find the line you want to edit:

    $ git grep -n 'Opening a File$'
    +content/getting-started/sections/atom-basics.md:84:##### Opening a File
    +

    and then jump to the beginning of that line by appending a colon and the line number to the file path:

    $ atom content/getting-started/sections/atom-basics.md:84
    +

    Sometimes you may want the cursor to jump to the exact column position of the searched keyword. Just append another colon plus the column number:

    $ git grep -n --column 'Windows Explorer'
    +content/getting-started/sections/atom-basics.md:150:722
    +$ atom content/getting-started/sections/atom-basics.md:150:722
    +
    Editing and Saving a File
    `,9),z={href:"https://atom.io/packages",target:"_blank",rel:"noopener noreferrer"},O=a('

    To save a file you can choose File > Save from the menu bar or Cmd+SCtrl+S to save the file. If you choose File > Save As or press Cmd+Shift+SCtrl+Shift+S then you can save the current content in your editor under a different file name. Finally, you can choose File > Save All or press Alt+Cmd+S to save all the open files in Atom.

    Opening Directories

    Atom doesn't just work with single files though; you will most likely spend most of your time working on projects with multiple files. To open a directory, choose the menu item File > OpenFile > Open Folder and select a directory from the dialog. You can also add more than one directory to your current Atom window, by choosing File > Add Project Folder from the menu bar or pressing Cmd+Shift+OCtrl+Shift+A.

    You can open any number of directories from the command line by passing their paths to the atom command line tool. For example, you could run the command atom ./hopes ./dreams to open both the hopes and the dreams directories at the same time.

    When you open Atom with one or more directories, you will automatically get a Tree View on the side of your window.

    Tree View in an open project

    The Tree View allows you to explore and modify the file and directory structure of your project. You can open, rename, delete and create new files from this view.

    You can also hide and show it with Cmd+\\Ctrl+\\ or the tree-view: toggle command from the Command Palette, and Ctrl+0 Alt+\\ will focus it. When the Tree view has focus you can press A, M, or Delete to add, move or delete files and folders. You can also right-click on a file or folder in the Tree view to see many of the various options, including all of these plus showing the file in FinderWindows Exploreryour native filesystem or copying the file path to the clipboard.

    Note

    Atom Packages

    Like many parts of Atom, the Tree View is not built directly into the editor, but is its own standalone package that is shipped with Atom by default. Packages that are bundled with Atom are referred to as Core packages. Ones that aren't bundled with Atom are referred to as Community packages.

    You can find the source code to the Tree View on GitHub at https://github.com/atom/tree-view.

    This is one of the interesting things about Atom. Many of its core features are actually just packages implemented the same way you would implement any other functionality. This means that if you don't like the Tree View for example, you could write your own implementation of that functionality and replace it entirely.

    Opening a File in a Project

    Once you have a project open in Atom, you can easily find and open any file within that project.

    If you press Cmd+TCtrl+T or Cmd+PCtrl+P, the Fuzzy Finder will pop up. This will let you quickly search for any file in your project by typing parts of the path.

    Opening files with the Fuzzy Finder

    You can also search through only the files currently opened (rather than every file in your project) with Cmd+BCtrl+B. This searches through your "buffers" or open files. You can also limit this fuzzy search with Cmd+Shift+BCtrl+Shift+B, which searches only through the files which are new or have been modified since your last Git commit.

    ',14),j=t("code",null,"core.ignoredNames",-1),N=t("code",null,"fuzzy-finder.ignoredNames",-1),Y=t("code",null,"core.excludeVCSIgnoredPaths",-1),B={href:"https://git-scm.com/docs/gitignore",target:"_blank",rel:"noopener noreferrer"},W=t("code",null,".gitignore",-1),E=t("code",null,"core.ignoredNames",-1),L=t("code",null,"fuzzy-finder.ignoredNames",-1),U={href:"https://github.com/isaacs/minimatch",target:"_blank",rel:"noopener noreferrer"},$=a('

    Tips

    Configuration Setting Notation

    Sometimes you'll see us refer to configuration settings all spelled out like "Ignored Names in Core Settings". Other times you'll see us use the shorthand name like core.ignoredNames. Both of these refer to the same thing. The shorthand is the package name, then a dot ., followed by the "camel-cased" name of the setting.

    If you have a phrase you want to camel-case, follow these steps:

    1. Lowercase the first word
    2. Capitalize the first letter in all other words
    3. Remove the spaces

    So "Ignored Names" becomes "ignoredNames".

    ',1);function G(M,R){const n=r("RouterLink"),s=r("ExternalLinkIcon");return b(),y("div",null,[v,t("p",null,[e("You can find definitions for all of the various terms that we use throughout the manual in our "),o(n,{to:"/resources/sections/glossary/"},{default:i(()=>[e("Glossary")]),_:1}),e(".")]),_,t("template",null,[x,t("p",null,[e("In "),o(n,{to:"/sections/basic-customization/"},{default:i(()=>[e("Basic Customization")]),_:1}),e(" we will see how to set different wrap preferences for different types of files (for example, if you want to wrap Markdown files but not other files).")]),S,t("p",null,[e("Another way to open a file in Atom is from the command line using the "),C,e(" command. "),t("span",T,[e('The Atom menu bar has a command named "Install Shell Commands" which installs the '),A,e(" and "),P,e(" commands "),o(n,{to:"/installing-atom/#installing-atom-on-mac"},{default:i(()=>[e("if Atom wasn't able to install them itself")]),_:1}),e(" .")]),t("span",F,[e("The "),I,e(" and "),V,e(" commands are installed automatically as a part of Atom's "),o(n,{to:"/sections/installing-atom/"},{default:i(()=>[e("installation process")]),_:1}),e(".")])]),q,t("p",null,[e("Editing a file is pretty straightforward. You can click around and scroll with your mouse and type to change the content. There is no special editing mode or key commands. If you prefer editors with modes or more complex key commands, you should take a look at the "),t("a",z,[e("Atom package list"),o(s)]),e(". There are a lot of packages that emulate popular styles.")]),O,t("p",null,[e("The fuzzy finder uses the "),j,e(", "),N,e(" and "),Y,e(" configuration settings to filter out files and folders that will not be shown. If you have a project with tons of files you don't want it to search through, you can add patterns or paths to either of these config settings or your "),t("a",B,[e("standard "),W,e(" files"),o(s)]),e(". We'll learn more about config settings in "),o(n,{to:"/using-atom/sections/basic-customization/#global-configuration-settings"},{default:i(()=>[e("Global Configuration Settings")]),_:1}),e(", but for now you can easily set these in the Settings View under Core Settings.")]),t("p",null,[e("Both "),E,e(" and "),L,e(" are interpreted as glob patterns as implemented by the "),t("a",U,[e("minimatch Node module"),o(s)]),e(".")]),$])])}const K=w(k,[["render",G],["__file","atom-basics.html.vue"]]);export{K as default}; diff --git a/assets/atom-basics.html.fc2f4a06.js b/assets/atom-basics.html.fc2f4a06.js new file mode 100644 index 0000000000..0164594857 --- /dev/null +++ b/assets/atom-basics.html.fc2f4a06.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-258423d8","path":"/docs/atom-archive/getting-started/sections/atom-basics.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[],"git":{"updatedTime":1668309800000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":5},{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":8.3,"words":2490},"filePathRelative":"docs/atom-archive/getting-started/sections/atom-basics.md"}');export{e as data}; diff --git a/assets/atom-in-the-cloud.html.be1543ce.js b/assets/atom-in-the-cloud.html.be1543ce.js new file mode 100644 index 0000000000..e76452a982 --- /dev/null +++ b/assets/atom-in-the-cloud.html.be1543ce.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-78782b86","path":"/docs/atom-archive/faq/sections/atom-in-the-cloud.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"Atom in the cloud?","slug":"atom-in-the-cloud","link":"#atom-in-the-cloud","children":[]}],"git":{"updatedTime":1667690985000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1},{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":0.12,"words":36},"filePathRelative":"docs/atom-archive/faq/sections/atom-in-the-cloud.md"}');export{e as data}; diff --git a/assets/atom-in-the-cloud.html.d130be16.js b/assets/atom-in-the-cloud.html.d130be16.js new file mode 100644 index 0000000000..598190c285 --- /dev/null +++ b/assets/atom-in-the-cloud.html.d130be16.js @@ -0,0 +1 @@ +import{_ as a,o as n,c as s,a as o,b as e,d as r,r as c}from"./app.0e1565ce.js";const d={},i=o("h3",{id:"atom-in-the-cloud",tabindex:"-1"},[o("a",{class:"header-anchor",href:"#atom-in-the-cloud","aria-hidden":"true"},"#"),e(" Atom in the cloud?")],-1),l={href:"https://github.com/atom/atom/discussions",target:"_blank",rel:"noopener noreferrer"};function h(m,_){const t=c("ExternalLinkIcon");return n(),s("div",null,[i,o("p",null,[e("The Atom team has no plans to make a cloud- or server-based version of Atom. For discussion of the idea, see the "),o("a",l,[e("Atom message board"),r(t)]),e(".")])])}const f=a(d,[["render",h],["__file","atom-in-the-cloud.html.vue"]]);export{f as default}; diff --git a/assets/atom-package-server-api.html.0b048bfe.js b/assets/atom-package-server-api.html.0b048bfe.js new file mode 100644 index 0000000000..1e64d33d8e --- /dev/null +++ b/assets/atom-package-server-api.html.0b048bfe.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-7c51deeb","path":"/docs/atom-archive/atom-server-side-apis/sections/atom-package-server-api.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"Atom package server API","slug":"atom-package-server-api","link":"#atom-package-server-api","children":[]}],"git":{"updatedTime":1667693110000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":2},{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":3.12,"words":936},"filePathRelative":"docs/atom-archive/atom-server-side-apis/sections/atom-package-server-api.md"}');export{e as data}; diff --git a/assets/atom-package-server-api.html.a54744b0.js b/assets/atom-package-server-api.html.a54744b0.js new file mode 100644 index 0000000000..bbc36291e8 --- /dev/null +++ b/assets/atom-package-server-api.html.a54744b0.js @@ -0,0 +1,59 @@ +import{_ as o,o as i,c as r,a,b as e,d as n,f as t,r as p}from"./app.0e1565ce.js";const c={},l=a("h3",{id:"atom-package-server-api",tabindex:"-1"},[a("a",{class:"header-anchor",href:"#atom-package-server-api","aria-hidden":"true"},"#"),e(" Atom package server API")],-1),d={href:"https://github.com/atom/apm",target:"_blank",rel:"noopener noreferrer"},u=a("code",null,"apm",-1),h=a("code",null,"package.json",-1),g=a("code",null,"apm",-1),k=a("code",null,"apm",-1),m={href:"https://github.com/atom/settings-view/blob/master/lib/package-manager.coffee",target:"_blank",rel:"noopener noreferrer"},v=a("code",null,"settings-view",-1),b=a("div",{class:"custom-container warning"},[a("p",{class:"custom-container-title"},"WARNING"),a("p",null,[a("strong",null,"Warning:"),e(" This API should be considered pre-release and is subject to change.")])],-1),q=a("h4",{id:"authorization",tabindex:"-1"},[a("a",{class:"header-anchor",href:"#authorization","aria-hidden":"true"},"#"),e(" Authorization")],-1),f={href:"https://atom.io/account",target:"_blank",rel:"noopener noreferrer"},y=a("code",null,"Authorization",-1),_=t(`

    Media type

    All requests that take parameters require application/json.

    API Resources

    Packages
    Listing packages
    GET /api/packages

    Parameters:

    • page (optional)
    • sort (optional) - One of downloads, created_at, updated_at, stars. Defaults to downloads
    • direction (optional) - asc or desc. Defaults to desc. stars can only be ordered desc

    Returns a list of all packages in the following format:

      [
    +    {
    +      "releases": {
    +        "latest": "0.6.0"
    +      },
    +      "name": "thedaniel-test-package",
    +      "repository": {
    +        "type": "git",
    +        "url": "https://github.com/thedaniel/test-package"
    +      }
    +    },
    +    ...
    +  ]
    +

    Results are paginated 30 at a time, and links to the next and last pages are provided in the Link header:

    Link: <https://www.atom.io/api/packages?page=1>; rel="self",
    +      <https://www.atom.io/api/packages?page=41>; rel="last",
    +      <https://www.atom.io/api/packages?page=2>; rel="next"
    +

    By default, results are sorted by download count, descending.

    Searching packages

    Parameters:

    • q (required) - Search query
    • page (optional)
    • sort (optional) - One of downloads, created_at, updated_at, stars. Defaults to the relevance of the search query.
    • direction (optional) - asc or desc. Defaults to desc.

    Returns results in the same format as listing packages.

    Showing package details
    GET /api/packages/:package_name

    Returns package details and versions for a single package

    Parameters:

    `,22),x=a("strong",null,"engine",-1),w={href:"http://semver.org",target:"_blank",rel:"noopener noreferrer"},j=t(`

    Returns:

      {
    +    "releases": {
    +      "latest": "0.6.0"
    +    },
    +    "name": "thedaniel-test-package",
    +    "repository": {
    +      "type": "git",
    +      "url": "https://github.com/thedaniel/test-package"
    +    },
    +    "versions": [
    +      (see single version output below)
    +      ...,
    +    ]
    +  }
    +
    Creating a package
    POST /api/packages

    Create a new package; requires authentication.

    The name and version will be fetched from the package.json file in the specified repository. The authenticating user must have access to the indicated repository.

    Parameters:

    • repository - String. The repository containing the plugin, in the form "owner/repo"

    Returns:

    • 201 - Successfully created, returns created package.
    • 400 - Repository is inaccessible, nonexistent, not an atom package. Possible error messages include:
      • That repo does not exist, isn't an atom package, or atombot does not have access
      • The package.json at owner/repo isn't valid
    • 409 - A package by that name already exists
    Deleting a package
    DELETE /api/packages/:package_name

    Delete a package; requires authentication.

    Returns:

    • 204 - Success
    • 400 - Repository is inaccessible
    • 401 - Unauthorized
    Renaming a package

    Packages are renamed by publishing a new version with the name changed in package.json. See Creating a new package version for details.

    Requests made to the previous name will forward to the new name.

    Package Versions
    GET /api/packages/:package_name/versions/:version_name

    Returns package.json with dist key added for e.g. tarball download:

    {
    +	"bugs": {
    +		"url": "https://github.com/thedaniel/test-package/issues"
    +	},
    +	"dependencies": {
    +		"async": "~0.2.6",
    +		"pegjs": "~0.7.0",
    +		"season": "~0.13.0"
    +	},
    +	"description": "Expand snippets matching the current prefix with \`tab\`.",
    +	"dist": {
    +		"tarball": "https://codeload.github.com/..."
    +	},
    +	"engines": {
    +		"atom": "*"
    +	},
    +	"main": "./lib/snippets",
    +	"name": "thedaniel-test-package",
    +	"publishConfig": {
    +		"registry": "https://..."
    +	},
    +	"repository": {
    +		"type": "git",
    +		"url": "https://github.com/thedaniel/test-package.git"
    +	},
    +	"version": "0.6.0"
    +}
    +
    Creating a new package version
    POST /api/packages/:package_name/versions

    Creates a new package version from a git tag; requires authentication. If rename is not true, the name field in package.json must match the current package name.

    Parameters:

    • tag - A git tag for the version you'd like to create. It's important to note that the version name will not be taken from the tag, but from the version key in the package.json file at that ref. The authenticating user must have access to the package repository.
    • rename - Boolean indicating whether this version contains a new name for the package.

    Returns:

    • 201 - Successfully created. Returns created version.
    • 400 - Git tag not found / Repository inaccessible / package.json invalid
    • 409 - Version exists
    Deleting a version
    DELETE /api/packages/:package_name/versions/:version_name

    Deletes a package version; requires authentication.

    Note that a version cannot be republished with a different tag if it is deleted. If you need to delete the latest version of a package for example for security reasons, you'll need to increment the version when republishing.

    Returns 204 No Content

    Stars

    Listing user stars
    GET /api/users/:login/stars

    List a user's starred packages.

    Return value is similar to GET /api/packages

    GET /api/stars

    List the authenticated user's starred packages; requires authentication.

    Return value is similar to GET /api/packages

    Starring a package
    POST /api/packages/:name/star

    Star a package; requires authentication.

    Returns a package.

    Unstarring a package
    DELETE /api/packages/:name/star

    Unstar a package; requires authentication.

    Returns 204 No Content.

    Listing a package's stargazers
    GET /api/packages/:name/stargazers

    List the users that have starred a package.

    Returns a list of user objects:

    [{ "login": "aperson" }, { "login": "anotherperson" }]
    +
    `,55);function T(R,E){const s=p("ExternalLinkIcon");return i(),r("div",null,[l,a("p",null,[e("This guide describes the web API used by "),a("a",d,[e("apm"),n(s)]),e(" and Atom. The vast majority of use cases are met by the "),u,e(" command-line tool, which does other useful things like incrementing your version in "),h,e(" and making sure you have pushed your git tag. In fact, Atom itself shells out to "),g,e(" rather than hitting the API directly. If you're curious about how Atom uses "),k,e(", see the "),a("a",m,[e("PackageManager class"),n(s)]),e(" in the "),v,e(" package.")]),b,q,a("p",null,[e("For calls to the API that require authentication, provide a valid token from your "),a("a",f,[e("atom.io account page"),n(s)]),e(" in the "),y,e(" header.")]),_,a("ul",null,[a("li",null,[x,e(" (optional) - Only show packages with versions compatible with this Atom version. Must be valid "),a("a",w,[e("SemVer"),n(s)]),e(".")])]),j])}const A=o(c,[["render",T],["__file","atom-package-server-api.html.vue"]]);export{A as default}; diff --git a/assets/atom-packages.html.4f74879c.js b/assets/atom-packages.html.4f74879c.js new file mode 100644 index 0000000000..80a8a574fa --- /dev/null +++ b/assets/atom-packages.html.4f74879c.js @@ -0,0 +1,21 @@ +import{_ as r,a as c,b as d,c as h}from"./unity-theme.516a5ae9.js";import{_ as m,o as p,c as u,a as t,b as e,d as a,w as s,f as o,r as l}from"./app.0e1565ce.js";const g={},f=t("h3",{id:"atom-packages",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#atom-packages","aria-hidden":"true"},"#"),e(" Atom Packages")],-1),k={href:"https://github.com/atom/tree-view",target:"_blank",rel:"noopener noreferrer"},b={href:"https://github.com/atom/settings-view",target:"_blank",rel:"noopener noreferrer"},v={href:"https://github.com/atom/welcome",target:"_blank",rel:"noopener noreferrer"},w={href:"https://github.com/atom/spell-check",target:"_blank",rel:"noopener noreferrer"},y={href:"https://github.com/atom/one-dark-ui",target:"_blank",rel:"noopener noreferrer"},_={href:"https://github.com/atom/fuzzy-finder",target:"_blank",rel:"noopener noreferrer"},x=o('

    This means that packages can be incredibly powerful and can change everything from the very look and feel of the entire interface to the basic operation of even core functionality.

    In order to install a new package, you can use the Install tab in the now familiar Settings View. Open up the Settings View using Cmd+,Ctrl+,, click on the "Install" tab and type your search query into the box under Install Packages.

    The packages listed here have been published to https://atom.io/packages which is the official registry for Atom packages. Searching on the Settings View will go to the Atom package registry and pull in anything that matches your search terms.

    Package install screen

    All of the packages will come up with an "Install" button. Clicking that will download the package and install it. Your editor will now have the functionality that the package provides.

    Package Settings

    Once a package is installed in Atom, it will show up in the Settings View under the "Packages" tab, along with all the preinstalled packages that come with Atom. To filter the list in order to find one, you can type into search box directly under the "Installed Packages" heading.

    Package settings screen

    Clicking on the "Settings" button for a package will give you the settings screen for that package specifically. Here you have the option of changing some of the default variables for the package, seeing what all the command keybindings are, disabling the package temporarily, looking at the source code, seeing the current version of the package, reporting issues and uninstalling the package.

    If a new version of any of your packages is released, Atom will automatically detect it and you can upgrade the package from either this screen or from the "Updates" tab. This helps you easily keep all your installed packages up to date.

    Atom Themes

    You can also find and install new themes for Atom from the Settings View. These can be either UI themes or syntax themes and you can search for them from the "Install" tab, just like searching for new packages. Make sure to press the "Themes" toggle next to the search box.

    Theme search screen

    Clicking on the theme title will take you to a profile page for the theme on atom.io, which often has a screenshot of the theme. This way you can see what it looks like before installing it.

    ',14),A=o('

    Example of the Unity UI theme with Monokai syntax theme

    Command Line

    You can also install packages or themes from the command line using apm.

    Tip

    Check that you have apm installed by running the following command in your terminal:

    $ apm help install
    +

    You should see a message print out with details about the apm install command.

    If you do not, see the Installing Atom section for instructions on how to install the atom and apm commands for your system.

    You can also install packages by using the apm install command:

    • apm install <package_name> to install the latest version.
    • apm install <package_name>@<package_version> to install a specific version.
    `,6),C=t("code",null,"apm install emmet@0.1.5",-1),S=t("code",null,"0.1.5",-1),I={href:"https://github.com/atom/emmet",target:"_blank",rel:"noopener noreferrer"},q=o(`

    You can also use apm to find new packages to install. If you run apm search, you can search the package registry for a search term.

    $ apm search coffee
    +> Search Results For 'coffee' (29)
    +> \u251C\u2500\u2500 build-coffee Atom Build provider for coffee, compiles CoffeeScript (1160 downloads, 2 stars)
    +> \u251C\u2500\u2500 scallahan-coffee-syntax A coffee inspired theme from the guys over at S.CALLAHAN (183 downloads, 0 stars)
    +> \u251C\u2500\u2500 coffee-paste Copy/Paste As : Js \u27A4 Coffee / Coffee \u27A4 Js (902 downloads, 4 stars)
    +> \u251C\u2500\u2500 atom-coffee-repl Coffee REPL for Atom Editor (894 downloads, 2 stars)
    +> \u251C\u2500\u2500 coffee-navigator Code navigation panel for Coffee Script (3493 downloads, 22 stars)
    +> ...
    +> \u251C\u2500\u2500 language-iced-coffeescript Iced coffeescript for atom (202 downloads, 1 star)
    +> \u2514\u2500\u2500 slontech-syntax Dark theme for web developers ( HTML, CSS/LESS, PHP, MYSQL, javascript, AJAX, coffee, JSON ) (2018 downloads, 3 stars)
    +

    You can use apm view to see more information about a specific package.

    $ apm view build-coffee
    +> build-coffee
    +> \u251C\u2500\u2500 0.6.4
    +> \u251C\u2500\u2500 https://github.com/idleberg/atom-build-coffee
    +> \u251C\u2500\u2500 Atom Build provider for coffee, compiles CoffeeScript
    +> \u251C\u2500\u2500 1152 downloads
    +> \u2514\u2500\u2500 2 stars
    +>
    +> Run \`apm install build-coffee\` to install this package.
    +
    `,4);function T(P,L){const n=l("ExternalLinkIcon"),i=l("RouterLink");return p(),u("div",null,[f,t("p",null,[e("First we'll start with the Atom package system. As we mentioned previously, Atom itself is a very basic core of functionality that ships with a number of useful packages that add new features like the "),t("a",k,[e("Tree View"),a(n)]),e(" and the "),t("a",b,[e("Settings View"),a(n)]),e(".")]),t("p",null,[e("In fact, there are more than 80 packages that comprise all of the functionality that is available in Atom by default. For example, the "),t("a",v,[e("Welcome screen"),a(n)]),e(" that you see when you first start Atom, the "),t("a",w,[e("spell checker"),a(n)]),e(", the "),t("a",y,[e("themes"),a(n)]),e(" and the "),t("a",_,[e("Fuzzy Finder"),a(n)]),e(" are all packages that are separately maintained and all use the same APIs that you have access to, as we'll see in great detail in "),a(i,{to:"/hacking-atom/"},{default:s(()=>[e("Hacking Atom")]),_:1}),e(".")]),x,t("p",null,[e('Clicking on "Install" will install the theme and make it available in the Theme dropdowns as we saw in '),a(i,{to:"/getting-started/sections/atom-basics/#changing-the-theme"},{default:s(()=>[e("Changing the Theme")]),_:1}),e(".")]),A,t("p",null,[e("For example "),C,e(" installs the "),S,e(" release of the "),t("a",I,[e("Emmet"),a(n)]),e(" package.")]),q])}const Y=m(g,[["render",T],["__file","atom-packages.html.vue"]]);export{Y as default}; diff --git a/assets/atom-packages.html.52a02db4.js b/assets/atom-packages.html.52a02db4.js new file mode 100644 index 0000000000..7d96104453 --- /dev/null +++ b/assets/atom-packages.html.52a02db4.js @@ -0,0 +1 @@ +const a=JSON.parse('{"key":"v-2cf2a68a","path":"/docs/atom-archive/using-atom/sections/atom-packages.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"Atom Packages","slug":"atom-packages","link":"#atom-packages","children":[]}],"git":{"updatedTime":1664634443000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1},{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":3.17,"words":950},"filePathRelative":"docs/atom-archive/using-atom/sections/atom-packages.md"}');export{a as data}; diff --git a/assets/atom-selections.html.308f14c6.js b/assets/atom-selections.html.308f14c6.js new file mode 100644 index 0000000000..e72c6bb0ce --- /dev/null +++ b/assets/atom-selections.html.308f14c6.js @@ -0,0 +1 @@ +import{_ as l,o as s,c as e,d as o,w as c,f as i,r,a as t,b as d}from"./app.0e1565ce.js";const n={},m=i('

    Atom Selections

    Text selections in Atom support a number of actions, such as scoping deletion, indentation and search actions, and marking text for actions such as quoting and bracketing.

    Selections mirror many of the movement commands. They're actually exactly the same keybindings as the movement commands, but with a Shift key added in.

    • Shift+Up or Ctrl+Shift+P - Select up
    • Shift+Down or Ctrl+Shift+N - Select down
    • Shift+Left or Ctrl+Shift+B - Select previous character
    • Shift+Right or Ctrl+Shift+F - Select next character
    • Alt+Shift+LeftCtrl+Shift+Left or Alt+Shift+B - Select to beginning of word
    • Alt+Shift+RightCtrl+Shift+Right or Alt+Shift+F - Select to end of word
    • Cmd+Shift+RightShift+End or Ctrl+Shift+E - Select to end of line
    • Cmd+Shift+LeftShift+Home or Ctrl+Shift+A - Select to first character of line
    • Cmd+Shift+UpCtrl+Shift+Home - Select to top of file
    • Cmd+Shift+DownCtrl+Shift+End - Select to bottom of file

    In addition to the cursor movement selection commands, there are also a few commands that help with selecting specific areas of content.

    • Cmd+ACtrl+A - Select the entire contents of the file
    • Cmd+LCtrl+L - Select the entire line
    ',6),f=t("ul",null,[t("li",null,[t("kbd",{class:"platform-mac"},"Ctrl+Shift+W"),d(" - Select the current word")])],-1);function p(b,k){const a=r("Tabs");return s(),e("div",null,[m,o(a,{id:"76",data:[{title:"Mac"}],"tab-id":"atom-selections"},{tab0:c(({title:h,value:S,isActive:u})=>[f]),_:1})])}const C=l(n,[["render",p],["__file","atom-selections.html.vue"]]);export{C as default}; diff --git a/assets/atom-selections.html.6ccd154b.js b/assets/atom-selections.html.6ccd154b.js new file mode 100644 index 0000000000..26e136f373 --- /dev/null +++ b/assets/atom-selections.html.6ccd154b.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-0fe6afd0","path":"/docs/atom-archive/using-atom/sections/atom-selections.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"Atom Selections","slug":"atom-selections","link":"#atom-selections","children":[]}],"git":{"updatedTime":1664634443000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1},{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":1.43,"words":428},"filePathRelative":"docs/atom-archive/using-atom/sections/atom-selections.md"}');export{e as data}; diff --git a/assets/atom-update-server-api.html.874968a8.js b/assets/atom-update-server-api.html.874968a8.js new file mode 100644 index 0000000000..a710469b56 --- /dev/null +++ b/assets/atom-update-server-api.html.874968a8.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-1c7613fc","path":"/docs/atom-archive/atom-server-side-apis/sections/atom-update-server-api.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"Atom update server API","slug":"atom-update-server-api","link":"#atom-update-server-api","children":[]}],"git":{"updatedTime":1667693110000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":2},{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":0.21,"words":63},"filePathRelative":"docs/atom-archive/atom-server-side-apis/sections/atom-update-server-api.md"}');export{e as data}; diff --git a/assets/atom-update-server-api.html.bfd69d08.js b/assets/atom-update-server-api.html.bfd69d08.js new file mode 100644 index 0000000000..56cc575da3 --- /dev/null +++ b/assets/atom-update-server-api.html.bfd69d08.js @@ -0,0 +1,7 @@ +import{_ as n,o,c as r,a as e,b as a,d as p,f as s,r as i}from"./app.0e1565ce.js";const d={},c=s('

    Atom update server API

    WARNING

    Warning: This API should be considered pre-release and is subject to change.

    Atom updates

    Listing Atom updates
    GET /api/updates
    ',5),u={href:"https://github.com/Squirrel/",target:"_blank",rel:"noopener noreferrer"},l=s(`

    Returns:

    {
    +	"name": "0.96.0",
    +	"notes": "[HTML release notes]",
    +	"pub_date": "2014-05-19T15:52:06.000Z",
    +	"url": "https://www.atom.io/api/updates/download"
    +}
    +
    `,2);function h(m,v){const t=i("ExternalLinkIcon");return o(),r("div",null,[c,e("p",null,[a("Atom update feed, following the format expected by "),e("a",u,[a("Squirrel"),p(t)]),a(".")]),l])}const _=n(d,[["render",h],["__file","atom-update-server-api.html.vue"]]);export{_ as default}; diff --git a/assets/autocomplete-description.1efc24d3.jpg b/assets/autocomplete-description.1efc24d3.jpg new file mode 100644 index 0000000000..8b24b6e8a2 Binary files /dev/null and b/assets/autocomplete-description.1efc24d3.jpg differ diff --git a/assets/autocomplete-providers.html.2ebd5ee8.js b/assets/autocomplete-providers.html.2ebd5ee8.js new file mode 100644 index 0000000000..eca6b444ba --- /dev/null +++ b/assets/autocomplete-providers.html.2ebd5ee8.js @@ -0,0 +1 @@ +import{_ as a,o as r,c as i,a as t,b as e,d as l,w as d,f as c,r as s}from"./app.0e1565ce.js";const _={},h=c('

    Autocomplete Providers

    Note

    Please note that its possible this is outdated, as its original version was published by @'percova' on Nov 12, 2019.

    Built-In Providers

    GrammarSelectorProviderStatus
    All*SymbolProviderDefault Provider
    All*FuzzyProviderDeprecated

    Providers For Built-In Grammars

    ',5),g=t("thead",null,[t("tr",null,[t("th",{style:{"text-align":"left"}},"Grammar"),t("th",{style:{"text-align":"left"}},"Selector"),t("th",{style:{"text-align":"left"}},"Provider"),t("th",{style:{"text-align":"left"}},"API Status")])],-1),u=t("tr",null,[t("td",{style:{"text-align":"left"}},"Null Grammar"),t("td",{style:{"text-align":"left"}},[t("code",null,".text.plain.null-grammar")]),t("td",{style:{"text-align":"left"}},"\xA0"),t("td",{style:{"text-align":"left"}},"\xA0")],-1),f={style:{"text-align":"left"}},p={href:"https://atom.io/packages/language-coffee-script",target:"_blank",rel:"noopener noreferrer"},y=t("td",{style:{"text-align":"left"}},[t("code",null,".source.litcoffee")],-1),x=t("td",{style:{"text-align":"left"}},null,-1),m=t("td",{style:{"text-align":"left"}},null,-1),k={style:{"text-align":"left"}},b={href:"https://atom.io/packages/language-coffee-script",target:"_blank",rel:"noopener noreferrer"},v=t("td",{style:{"text-align":"left"}},[t("code",null,".source.coffee")],-1),j=t("td",{style:{"text-align":"left"}},null,-1),S=t("td",{style:{"text-align":"left"}},null,-1),P={style:{"text-align":"left"}},L={href:"https://atom.io/packages/language-json",target:"_blank",rel:"noopener noreferrer"},C=t("td",{style:{"text-align":"left"}},[t("code",null,".source.json")],-1),T=t("td",{style:{"text-align":"left"}},null,-1),R=t("td",{style:{"text-align":"left"}},null,-1),M={style:{"text-align":"left"}},w={href:"https://atom.io/packages/language-shellscript",target:"_blank",rel:"noopener noreferrer"},A=t("td",{style:{"text-align":"left"}},[t("code",null,".text.shell-session")],-1),G=t("td",{style:{"text-align":"left"}},null,-1),q=t("td",{style:{"text-align":"left"}},null,-1),H={style:{"text-align":"left"}},E={href:"https://atom.io/packages/language-shellscript",target:"_blank",rel:"noopener noreferrer"},I=t("td",{style:{"text-align":"left"}},[t("code",null,".source.shell")],-1),N=t("td",{style:{"text-align":"left"}},null,-1),J=t("td",{style:{"text-align":"left"}},null,-1),O={style:{"text-align":"left"}},B={href:"https://atom.io/packages/language-hyperlink",target:"_blank",rel:"noopener noreferrer"},D=t("td",{style:{"text-align":"left"}},[t("code",null,".text.hyperlink")],-1),F=t("td",{style:{"text-align":"left"}},null,-1),V=t("td",{style:{"text-align":"left"}},null,-1),W={style:{"text-align":"left"}},Q={href:"https://atom.io/packages/language-todo",target:"_blank",rel:"noopener noreferrer"},X=t("td",{style:{"text-align":"left"}},[t("code",null,".text.todo")],-1),z=t("td",{style:{"text-align":"left"}},null,-1),K=t("td",{style:{"text-align":"left"}},null,-1),U={style:{"text-align":"left"}},Y={href:"https://atom.io/packages/language-c",target:"_blank",rel:"noopener noreferrer"},Z=t("td",{style:{"text-align":"left"}},[t("code",null,".source.c")],-1),$={style:{"text-align":"left"}},tt={href:"https://atom.io/packages/autocomplete-clang",target:"_blank",rel:"noopener noreferrer"},et=t("td",{style:{"text-align":"left"}},null,-1),lt={style:{"text-align":"left"}},ot={href:"https://atom.io/packages/language-c",target:"_blank",rel:"noopener noreferrer"},st=t("td",{style:{"text-align":"left"}},[t("code",null,".source.cpp")],-1),nt={style:{"text-align":"left"}},at={href:"https://atom.io/packages/autocomplete-clang",target:"_blank",rel:"noopener noreferrer"},rt=t("td",{style:{"text-align":"left"}},[t("code",null,"2.0.0")],-1),it={style:{"text-align":"left"}},dt={href:"https://atom.io/packages/language-clojure",target:"_blank",rel:"noopener noreferrer"},ct=t("td",{style:{"text-align":"left"}},[t("code",null,".source.clojure")],-1),_t={style:{"text-align":"left"}},ht={href:"https://atom.io/packages/proto-repl",target:"_blank",rel:"noopener noreferrer"},gt=t("td",{style:{"text-align":"left"}},null,-1),ut={style:{"text-align":"left"}},ft={href:"https://atom.io/packages/language-css",target:"_blank",rel:"noopener noreferrer"},pt=t("td",{style:{"text-align":"left"}},[t("code",null,".source.css")],-1),yt={style:{"text-align":"left"}},xt={href:"https://atom.io/packages/autocomplete-css",target:"_blank",rel:"noopener noreferrer"},mt=t("td",{style:{"text-align":"left"}},[t("code",null,"2.0.0")],-1),kt={style:{"text-align":"left"}},bt={href:"https://atom.io/packages/language-gfm",target:"_blank",rel:"noopener noreferrer"},vt=t("td",{style:{"text-align":"left"}},[t("code",null,".source.gfm")],-1),jt={style:{"text-align":"left"}},St={href:"https://atom.io/packages/autocomplete-bibtex",target:"_blank",rel:"noopener noreferrer"},Pt=t("td",{style:{"text-align":"left"}},[t("code",null,"1.1.0")],-1),Lt={style:{"text-align":"left"}},Ct={href:"https://atom.io/packages/language-git",target:"_blank",rel:"noopener noreferrer"},Tt=t("td",{style:{"text-align":"left"}},[t("code",null,".source.git-config")],-1),Rt=t("td",{style:{"text-align":"left"}},null,-1),Mt=t("td",{style:{"text-align":"left"}},null,-1),wt={style:{"text-align":"left"}},At={href:"https://atom.io/packages/language-git",target:"_blank",rel:"noopener noreferrer"},Gt=t("td",{style:{"text-align":"left"}},[t("code",null,".text.git-commit")],-1),qt=t("td",{style:{"text-align":"left"}},null,-1),Ht=t("td",{style:{"text-align":"left"}},null,-1),Et={style:{"text-align":"left"}},It={href:"https://atom.io/packages/language-git",target:"_blank",rel:"noopener noreferrer"},Nt=t("td",{style:{"text-align":"left"}},[t("code",null,".text.git-rebase")],-1),Jt=t("td",{style:{"text-align":"left"}},null,-1),Ot=t("td",{style:{"text-align":"left"}},null,-1),Bt={style:{"text-align":"left"}},Dt={href:"https://atom.io/packages/language-go",target:"_blank",rel:"noopener noreferrer"},Ft=t("td",{style:{"text-align":"left"}},[t("code",null,".text.html.gohtml")],-1),Vt=t("td",{style:{"text-align":"left"}},null,-1),Wt=t("td",{style:{"text-align":"left"}},null,-1),Qt={style:{"text-align":"left"}},Xt={href:"https://atom.io/packages/language-go",target:"_blank",rel:"noopener noreferrer"},zt=t("td",{style:{"text-align":"left"}},[t("code",null,".source.go")],-1),Kt={style:{"text-align":"left"}},Ut={href:"https://atom.io/packages/go-plus",target:"_blank",rel:"noopener noreferrer"},Yt={href:"https://atom.io/packages/autocomplete-go",target:"_blank",rel:"noopener noreferrer"},Zt=t("td",{style:{"text-align":"left"}},[t("code",null,"2.0.0")],-1),$t={style:{"text-align":"left"}},te={href:"https://atom.io/packages/language-go",target:"_blank",rel:"noopener noreferrer"},ee=t("td",{style:{"text-align":"left"}},[t("code",null,".source.gotemplate")],-1),le=t("td",{style:{"text-align":"left"}},null,-1),oe=t("td",{style:{"text-align":"left"}},null,-1),se={style:{"text-align":"left"}},ne={href:"https://atom.io/packages/language-html",target:"_blank",rel:"noopener noreferrer"},ae=t("td",{style:{"text-align":"left"}},[t("code",null,".text.html.basic")],-1),re={style:{"text-align":"left"}},ie={href:"https://atom.io/packages/autocomplete-html",target:"_blank",rel:"noopener noreferrer"},de=t("td",{style:{"text-align":"left"}},[t("code",null,"2.0.0")],-1),ce={style:{"text-align":"left"}},_e={href:"https://atom.io/packages/language-javascript",target:"_blank",rel:"noopener noreferrer"},he=t("td",{style:{"text-align":"left"}},[t("code",null,".source.js")],-1),ge={style:{"text-align":"left"}},ue={href:"https://atom.io/packages/atom-ternjs",target:"_blank",rel:"noopener noreferrer"},fe=t("td",{style:{"text-align":"left"}},[t("code",null,"2.0.0")],-1),pe={style:{"text-align":"left"}},ye={href:"https://atom.io/packages/language-java",target:"_blank",rel:"noopener noreferrer"},xe=t("td",{style:{"text-align":"left"}},[t("code",null,".source.java-properties")],-1),me=t("td",{style:{"text-align":"left"}},null,-1),ke=t("td",{style:{"text-align":"left"}},null,-1),be={style:{"text-align":"left"}},ve={href:"https://atom.io/packages/language-javascript",target:"_blank",rel:"noopener noreferrer"},je=t("td",{style:{"text-align":"left"}},[t("code",null,".source.js.regexp")],-1),Se=t("td",{style:{"text-align":"left"}},null,-1),Pe=t("td",{style:{"text-align":"left"}},null,-1),Le={style:{"text-align":"left"}},Ce={href:"https://atom.io/packages/language-java",target:"_blank",rel:"noopener noreferrer"},Te=t("td",{style:{"text-align":"left"}},[t("code",null,".text.html.jsp")],-1),Re={style:{"text-align":"left"}},Me={href:"https://atom.io/packages/autocomplete-jsp",target:"_blank",rel:"noopener noreferrer"},we=t("td",{style:{"text-align":"left"}},[t("code",null,"2.0.0")],-1),Ae={style:{"text-align":"left"}},Ge={href:"https://atom.io/packages/language-java",target:"_blank",rel:"noopener noreferrer"},qe=t("td",{style:{"text-align":"left"}},[t("code",null,".source.java")],-1),He={style:{"text-align":"left"}},Ee={href:"https://atom.io/packages/autocomplete-java-minus",target:"_blank",rel:"noopener noreferrer"},Ie=t("td",{style:{"text-align":"left"}},[t("code",null,"2.0.0")],-1),Ne={style:{"text-align":"left"}},Je={href:"https://atom.io/packages/language-java",target:"_blank",rel:"noopener noreferrer"},Oe=t("td",{style:{"text-align":"left"}},[t("code",null,".text.junit-test-report")],-1),Be=t("td",{style:{"text-align":"left"}},null,-1),De=t("td",{style:{"text-align":"left"}},null,-1),Fe={style:{"text-align":"left"}},Ve={href:"https://atom.io/packages/language-make",target:"_blank",rel:"noopener noreferrer"},We=t("td",{style:{"text-align":"left"}},[t("code",null,".source.makefile")],-1),Qe=t("td",{style:{"text-align":"left"}},null,-1),Xe=t("td",{style:{"text-align":"left"}},null,-1),ze={style:{"text-align":"left"}},Ke={href:"https://atom.io/packages/language-less",target:"_blank",rel:"noopener noreferrer"},Ue=t("td",{style:{"text-align":"left"}},[t("code",null,".source.css.less")],-1),Ye=t("td",{style:{"text-align":"left"}},null,-1),Ze=t("td",{style:{"text-align":"left"}},null,-1),$e={style:{"text-align":"left"}},tl={href:"https://atom.io/packages/language-mustache",target:"_blank",rel:"noopener noreferrer"},el=t("td",{style:{"text-align":"left"}},[t("code",null,".source.sql.mustache")],-1),ll=t("td",{style:{"text-align":"left"}},null,-1),ol=t("td",{style:{"text-align":"left"}},null,-1),sl={style:{"text-align":"left"}},nl={href:"https://atom.io/packages/language-mustache",target:"_blank",rel:"noopener noreferrer"},al=t("td",{style:{"text-align":"left"}},[t("code",null,".text.html.mustache")],-1),rl=t("td",{style:{"text-align":"left"}},null,-1),il=t("td",{style:{"text-align":"left"}},null,-1),dl={style:{"text-align":"left"}},cl={href:"https://atom.io/packages/language-objective-c",target:"_blank",rel:"noopener noreferrer"},_l=t("td",{style:{"text-align":"left"}},[t("code",null,".source.objcpp")],-1),hl={style:{"text-align":"left"}},gl={href:"https://atom.io/packages/autocomplete-clang",target:"_blank",rel:"noopener noreferrer"},ul=t("td",{style:{"text-align":"left"}},null,-1),fl={style:{"text-align":"left"}},pl={href:"https://atom.io/packages/language-objective-c",target:"_blank",rel:"noopener noreferrer"},yl=t("td",{style:{"text-align":"left"}},[t("code",null,".source.strings")],-1),xl=t("td",{style:{"text-align":"left"}},null,-1),ml=t("td",{style:{"text-align":"left"}},null,-1),kl={style:{"text-align":"left"}},bl={href:"https://atom.io/packages/language-objective-c",target:"_blank",rel:"noopener noreferrer"},vl=t("td",{style:{"text-align":"left"}},[t("code",null,".source.objc")],-1),jl={style:{"text-align":"left"}},Sl={href:"https://atom.io/packages/autocomplete-clang",target:"_blank",rel:"noopener noreferrer"},Pl=t("td",{style:{"text-align":"left"}},null,-1),Ll={style:{"text-align":"left"}},Cl={href:"https://atom.io/packages/language-property-list",target:"_blank",rel:"noopener noreferrer"},Tl=t("td",{style:{"text-align":"left"}},[t("code",null,".text.xml.plist")],-1),Rl=t("td",{style:{"text-align":"left"}},null,-1),Ml=t("td",{style:{"text-align":"left"}},null,-1),wl={style:{"text-align":"left"}},Al={href:"https://atom.io/packages/language-property-list",target:"_blank",rel:"noopener noreferrer"},Gl=t("td",{style:{"text-align":"left"}},[t("code",null,".source.plist")],-1),ql=t("td",{style:{"text-align":"left"}},null,-1),Hl=t("td",{style:{"text-align":"left"}},null,-1),El={style:{"text-align":"left"}},Il={href:"https://atom.io/packages/language-perl",target:"_blank",rel:"noopener noreferrer"},Nl=t("td",{style:{"text-align":"left"}},[t("code",null,".source.perl")],-1),Jl=t("td",{style:{"text-align":"left"}},null,-1),Ol=t("td",{style:{"text-align":"left"}},null,-1),Bl={style:{"text-align":"left"}},Dl={href:"https://atom.io/packages/language-php",target:"_blank",rel:"noopener noreferrer"},Fl=t("td",{style:{"text-align":"left"}},[t("code",null,".text.html.php")],-1),Vl=t("td",{style:{"text-align":"left"}},null,-1),Wl=t("td",{style:{"text-align":"left"}},null,-1),Ql=t("td",{style:{"text-align":"left"}},"PHP",-1),Xl=t("td",{style:{"text-align":"left"}},[t("code",null,".source.php")],-1),zl={style:{"text-align":"left"}},Kl={href:"https://github.com/php-integrator/atom-autocompletion",target:"_blank",rel:"noopener noreferrer"},Ul={href:"https://github.com/Peekmo/atom-autocomplete-php",target:"_blank",rel:"noopener noreferrer"},Yl={href:"https://atom.io/packages/autocomplete-php",target:"_blank",rel:"noopener noreferrer"},Zl=t("td",{style:{"text-align":"left"}},[t("code",null,"2.0.0")],-1),$l={style:{"text-align":"left"}},to={href:"https://atom.io/packages/language-python",target:"_blank",rel:"noopener noreferrer"},eo=t("td",{style:{"text-align":"left"}},[t("code",null,".text.python.console")],-1),lo=t("td",{style:{"text-align":"left"}},null,-1),oo=t("td",{style:{"text-align":"left"}},null,-1),so={style:{"text-align":"left"}},no={href:"https://atom.io/packages/language-python",target:"_blank",rel:"noopener noreferrer"},ao=t("td",{style:{"text-align":"left"}},[t("code",null,".text.python.traceback")],-1),ro=t("td",{style:{"text-align":"left"}},null,-1),io=t("td",{style:{"text-align":"left"}},null,-1),co={style:{"text-align":"left"}},_o={href:"https://atom.io/packages/language-python",target:"_blank",rel:"noopener noreferrer"},ho=t("td",{style:{"text-align":"left"}},[t("code",null,".source.regexp.python")],-1),go=t("td",{style:{"text-align":"left"}},null,-1),uo=t("td",{style:{"text-align":"left"}},null,-1),fo={style:{"text-align":"left"}},po={href:"https://atom.io/packages/language-python",target:"_blank",rel:"noopener noreferrer"},yo=t("td",{style:{"text-align":"left"}},[t("code",null,".source.python")],-1),xo={style:{"text-align":"left"}},mo={href:"https://atom.io/packages/autocomplete-python",target:"_blank",rel:"noopener noreferrer"},ko={href:"https://atom.io/packages/autocomplete-python-jedi",target:"_blank",rel:"noopener noreferrer"},bo=t("td",{style:{"text-align":"left"}},null,-1),vo={style:{"text-align":"left"}},jo={href:"https://atom.io/packages/language-ruby-on-rails",target:"_blank",rel:"noopener noreferrer"},So=t("td",{style:{"text-align":"left"}},[t("code",null,".source.ruby.rails.rjs")],-1),Po=t("td",{style:{"text-align":"left"}},null,-1),Lo=t("td",{style:{"text-align":"left"}},null,-1),Co={style:{"text-align":"left"}},To={href:"https://atom.io/packages/language-ruby",target:"_blank",rel:"noopener noreferrer"},Ro=t("td",{style:{"text-align":"left"}},[t("code",null,".source.ruby")],-1),Mo=t("td",{style:{"text-align":"left"}},null,-1),wo=t("td",{style:{"text-align":"left"}},null,-1),Ao={style:{"text-align":"left"}},Go={href:"https://atom.io/packages/language-ruby",target:"_blank",rel:"noopener noreferrer"},qo=t("td",{style:{"text-align":"left"}},[t("code",null,".text.html.erb")],-1),Ho=t("td",{style:{"text-align":"left"}},null,-1),Eo=t("td",{style:{"text-align":"left"}},null,-1),Io={style:{"text-align":"left"}},No={href:"https://atom.io/packages/language-ruby-on-rails",target:"_blank",rel:"noopener noreferrer"},Jo=t("td",{style:{"text-align":"left"}},[t("code",null,".text.html.ruby")],-1),Oo=t("td",{style:{"text-align":"left"}},null,-1),Bo=t("td",{style:{"text-align":"left"}},null,-1),Do={style:{"text-align":"left"}},Fo={href:"https://atom.io/packages/language-ruby-on-rails",target:"_blank",rel:"noopener noreferrer"},Vo=t("td",{style:{"text-align":"left"}},[t("code",null,".source.sql.ruby")],-1),Wo=t("td",{style:{"text-align":"left"}},null,-1),Qo=t("td",{style:{"text-align":"left"}},null,-1),Xo={style:{"text-align":"left"}},zo={href:"https://atom.io/packages/language-ruby-on-rails",target:"_blank",rel:"noopener noreferrer"},Ko=t("td",{style:{"text-align":"left"}},[t("code",null,".source.js.rails .source.js.jquery")],-1),Uo=t("td",{style:{"text-align":"left"}},null,-1),Yo=t("td",{style:{"text-align":"left"}},null,-1),Zo={style:{"text-align":"left"}},$o={href:"https://atom.io/packages/language-ruby-on-rails",target:"_blank",rel:"noopener noreferrer"},ts=t("td",{style:{"text-align":"left"}},[t("code",null,".source.ruby.rails")],-1),es=t("td",{style:{"text-align":"left"}},null,-1),ls=t("td",{style:{"text-align":"left"}},null,-1),os={style:{"text-align":"left"}},ss={href:"https://atom.io/packages/language-sass",target:"_blank",rel:"noopener noreferrer"},ns=t("td",{style:{"text-align":"left"}},[t("code",null,".source.sass")],-1),as=t("td",{style:{"text-align":"left"}},null,-1),rs=t("td",{style:{"text-align":"left"}},null,-1),is={style:{"text-align":"left"}},ds={href:"https://atom.io/packages/language-text",target:"_blank",rel:"noopener noreferrer"},cs=t("td",{style:{"text-align":"left"}},[t("code",null,".text.plain")],-1),_s=t("td",{style:{"text-align":"left"}},null,-1),hs=t("td",{style:{"text-align":"left"}},null,-1),gs={style:{"text-align":"left"}},us={href:"https://atom.io/packages/language-sass",target:"_blank",rel:"noopener noreferrer"},fs=t("td",{style:{"text-align":"left"}},[t("code",null,".source.css.scss")],-1),ps=t("td",{style:{"text-align":"left"}},null,-1),ys=t("td",{style:{"text-align":"left"}},null,-1),xs={style:{"text-align":"left"}},ms={href:"https://atom.io/packages/language-sql",target:"_blank",rel:"noopener noreferrer"},ks=t("td",{style:{"text-align":"left"}},[t("code",null,".source.sql")],-1),bs={style:{"text-align":"left"}},vs={href:"https://atom.io/packages/autocomplete-sql",target:"_blank",rel:"noopener noreferrer"},js=t("td",{style:{"text-align":"left"}},[t("code",null,"2.0.0")],-1),Ss={style:{"text-align":"left"}},Ps={href:"https://atom.io/packages/language-toml",target:"_blank",rel:"noopener noreferrer"},Ls=t("td",{style:{"text-align":"left"}},[t("code",null,".source.toml")],-1),Cs=t("td",{style:{"text-align":"left"}},null,-1),Ts=t("td",{style:{"text-align":"left"}},null,-1),Rs={style:{"text-align":"left"}},Ms={href:"https://atom.io/packages/language-xml",target:"_blank",rel:"noopener noreferrer"},ws=t("td",{style:{"text-align":"left"}},[t("code",null,".text.xml.xsl")],-1),As=t("td",{style:{"text-align":"left"}},null,-1),Gs=t("td",{style:{"text-align":"left"}},null,-1),qs={style:{"text-align":"left"}},Hs={href:"https://atom.io/packages/language-xml",target:"_blank",rel:"noopener noreferrer"},Es=t("td",{style:{"text-align":"left"}},[t("code",null,".text.xml")],-1),Is={style:{"text-align":"left"}},Ns={href:"https://github.com/pleonex/atom-autocomplete-xml",target:"_blank",rel:"noopener noreferrer"},Js=t("td",{style:{"text-align":"left"}},[t("code",null,"2.0.0")],-1),Os={style:{"text-align":"left"}},Bs={href:"https://atom.io/packages/language-yaml",target:"_blank",rel:"noopener noreferrer"},Ds=t("td",{style:{"text-align":"left"}},[t("code",null,".source.yaml")],-1),Fs=t("td",{style:{"text-align":"left"}},null,-1),Vs=t("td",{style:{"text-align":"left"}},null,-1),Ws=t("h2",{id:"providers-for-third-party-grammars",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#providers-for-third-party-grammars","aria-hidden":"true"},"#"),e(" Providers For Third-Party Grammars")],-1),Qs=t("thead",null,[t("tr",null,[t("th",{style:{"text-align":"left"}},"Grammar"),t("th",{style:{"text-align":"left"}},"Selector"),t("th",{style:{"text-align":"left"}},"Provider"),t("th",{style:{"text-align":"left"}},"API Status")])],-1),Xs={style:{"text-align":"left"}},zs={href:"https://atom.io/packages/mavensmate-atom",target:"_blank",rel:"noopener noreferrer"},Ks=t("td",{style:{"text-align":"left"}},[t("code",null,".source.apex")],-1),Us={style:{"text-align":"left"}},Ys={href:"https://atom.io/packages/mavensmate-atom",target:"_blank",rel:"noopener noreferrer"},Zs=t("td",{style:{"text-align":"left"}},[t("code",null,"1.0.0")],-1),$s={style:{"text-align":"left"}},tn={href:"https://atom.io/packages/language-asciidoc",target:"_blank",rel:"noopener noreferrer"},en=t("td",{style:{"text-align":"left"}},[t("code",null,".source.asciidoc")],-1),ln={style:{"text-align":"left"}},on={href:"https://atom.io/packages/autocomplete-asciidoc",target:"_blank",rel:"noopener noreferrer"},sn=t("td",{style:{"text-align":"left"}},[t("code",null,"2.0.0")],-1),nn={style:{"text-align":"left"}},an={href:"https://atom.io/packages/language-csharp",target:"_blank",rel:"noopener noreferrer"},rn=t("td",{style:{"text-align":"left"}},[t("code",null,".source.cs")],-1),dn={style:{"text-align":"left"}},cn={href:"https://atom.io/packages/omnisharp-atom",target:"_blank",rel:"noopener noreferrer"},_n=t("td",{style:{"text-align":"left"}},[t("code",null,"2.0.0")],-1),hn={style:{"text-align":"left"}},gn={href:"https://atom.io/packages/language-computercraft",target:"_blank",rel:"noopener noreferrer"},un=t("td",{style:{"text-align":"left"}},[t("code",null,".source.computercraft")],-1),fn={style:{"text-align":"left"}},pn={href:"https://atom.io/packages/autocomplete-computercraft",target:"_blank",rel:"noopener noreferrer"},yn=t("td",{style:{"text-align":"left"}},[t("code",null,"1.0.0")],-1),xn={style:{"text-align":"left"}},mn={href:"https://atom.io/packages/language-curry",target:"_blank",rel:"noopener noreferrer"},kn=t("td",{style:{"text-align":"left"}},[t("code",null,".source.curry")],-1),bn={style:{"text-align":"left"}},vn={href:"https://atom.io/packages/autocomplete-curry",target:"_blank",rel:"noopener noreferrer"},jn=t("td",{style:{"text-align":"left"}},[t("code",null,"4.0.0")],-1),Sn={style:{"text-align":"left"}},Pn={href:"https://github.com/radicaled/dart-tools",target:"_blank",rel:"noopener noreferrer"},Ln=t("td",{style:{"text-align":"left"}},[t("code",null,".source.dart")],-1),Cn={style:{"text-align":"left"}},Tn={href:"https://github.com/radicaled/dart-tools",target:"_blank",rel:"noopener noreferrer"},Rn=t("td",{style:{"text-align":"left"}},null,-1),Mn={style:{"text-align":"left"}},wn={href:"https://github.com/dart-atom/dartlang",target:"_blank",rel:"noopener noreferrer"},An=t("td",{style:{"text-align":"left"}},[t("code",null,".source.dart")],-1),Gn={style:{"text-align":"left"}},qn={href:"https://github.com/dart-atom/dartlang",target:"_blank",rel:"noopener noreferrer"},Hn=t("td",{style:{"text-align":"left"}},null,-1),En={style:{"text-align":"left"}},In={href:"https://atom.io/packages/language-elixir",target:"_blank",rel:"noopener noreferrer"},Nn=t("td",{style:{"text-align":"left"}},[t("code",null,".source.elixir")],-1),Jn={style:{"text-align":"left"}},On={href:"https://atom.io/packages/autocomplete-elixir",target:"_blank",rel:"noopener noreferrer"},Bn=t("td",{style:{"text-align":"left"}},[t("code",null,"2.0.0")],-1),Dn={style:{"text-align":"left"}},Fn={href:"https://atom.io/packages/language-erlang",target:"_blank",rel:"noopener noreferrer"},Vn=t("td",{style:{"text-align":"left"}},[t("code",null,".source.erlang")],-1),Wn={style:{"text-align":"left"}},Qn={href:"https://atom.io/packages/autocomplete-erlang",target:"_blank",rel:"noopener noreferrer"},Xn=t("td",{style:{"text-align":"left"}},[t("code",null,"2.0.0")],-1),zn={style:{"text-align":"left"}},Kn={href:"https://atom.io/packages/language-glsl",target:"_blank",rel:"noopener noreferrer"},Un=t("td",{style:{"text-align":"left"}},[t("code",null,".source.glsl")],-1),Yn={style:{"text-align":"left"}},Zn={href:"https://atom.io/packages/autocomplete-glsl",target:"_blank",rel:"noopener noreferrer"},$n=t("td",{style:{"text-align":"left"}},[t("code",null,"2.0.0")],-1),ta={style:{"text-align":"left"}},ea={href:"https://atom.io/packages/language-hack",target:"_blank",rel:"noopener noreferrer"},la=t("td",{style:{"text-align":"left"}},[t("code",null,".source.hack")],-1),oa={style:{"text-align":"left"}},sa={href:"https://atom.io/packages/autocomplete-hack",target:"_blank",rel:"noopener noreferrer"},na=t("td",{style:{"text-align":"left"}},[t("code",null,"2.0.0")],-1),aa={style:{"text-align":"left"}},ra={href:"https://atom.io/packages/language-haskell",target:"_blank",rel:"noopener noreferrer"},ia=t("td",{style:{"text-align":"left"}},[t("code",null,".source.haskell")],-1),da={style:{"text-align":"left"}},ca={href:"https://atom.io/packages/autocomplete-haskell",target:"_blank",rel:"noopener noreferrer"},_a=t("td",{style:{"text-align":"left"}},[t("code",null,"1.0.0")],-1),ha={style:{"text-align":"left"}},ga={href:"https://atom.io/packages/language-haskell",target:"_blank",rel:"noopener noreferrer"},ua=t("td",{style:{"text-align":"left"}},[t("code",null,".source.haskell")],-1),fa={style:{"text-align":"left"}},pa={href:"https://atom.io/packages/ide-haskell",target:"_blank",rel:"noopener noreferrer"},ya=t("td",{style:{"text-align":"left"}},[t("code",null,"1.0.0")],-1),xa={style:{"text-align":"left"}},ma={href:"https://atom.io/packages/language-haxe",target:"_blank",rel:"noopener noreferrer"},ka=t("td",{style:{"text-align":"left"}},[t("code",null,".source.haxe")],-1),ba={style:{"text-align":"left"}},va={href:"https://atom.io/packages/autocomplete-haxe",target:"_blank",rel:"noopener noreferrer"},ja=t("td",{style:{"text-align":"left"}},[t("code",null,"1.1.0")],-1),Sa={style:{"text-align":"left"}},Pa={href:"https://atom.io/packages/language-latex",target:"_blank",rel:"noopener noreferrer"},La=t("td",{style:{"text-align":"left"}},[t("code",null,".text.tex.latex")],-1),Ca={style:{"text-align":"left"}},Ta={href:"https://atom.io/packages/autocomplete-latex-cite",target:"_blank",rel:"noopener noreferrer"},Ra={href:"https://atom.io/packages/autocomplete-latex-references",target:"_blank",rel:"noopener noreferrer"},Ma={href:"https://atom.io/packages/autocomplete-glossaries",target:"_blank",rel:"noopener noreferrer"},wa=t("td",{style:{"text-align":"left"}},[t("code",null,"2.0.0")],-1),Aa={style:{"text-align":"left"}},Ga={href:"https://github.com/marko-js/atom-language-marko",target:"_blank",rel:"noopener noreferrer"},qa=t("td",{style:{"text-align":"left"}},[t("code",null,".text.marko")],-1),Ha={style:{"text-align":"left"}},Ea={href:"https://github.com/marko-js/atom-autocomplete-marko",target:"_blank",rel:"noopener noreferrer"},Ia=t("td",{style:{"text-align":"left"}},[t("code",null,"2.0.0")],-1),Na={style:{"text-align":"left"}},Ja={href:"https://github.com/alohaas/language-nunjucks",target:"_blank",rel:"noopener noreferrer"},Oa=t("td",{style:{"text-align":"left"}},[t("code",null,".source.nunjucks, .text.html.nunjucks")],-1),Ba={style:{"text-align":"left"}},Da={href:"https://github.com/puranjayjain/autocomplete-nunjucks",target:"_blank",rel:"noopener noreferrer"},Fa=t("td",{style:{"text-align":"left"}},[t("code",null,"2.0.0")],-1),Va={style:{"text-align":"left"}},Wa={href:"https://atom.io/packages/pig",target:"_blank",rel:"noopener noreferrer"},Qa=t("td",{style:{"text-align":"left"}},[t("code",null,".source.pig")],-1),Xa={style:{"text-align":"left"}},za={href:"https://atom.io/packages/pig",target:"_blank",rel:"noopener noreferrer"},Ka=t("td",{style:{"text-align":"left"}},[t("code",null,"2.0.0")],-1),Ua={style:{"text-align":"left"}},Ya={href:"https://atom.io/packages/language-kdb-q",target:"_blank",rel:"noopener noreferrer"},Za=t("td",{style:{"text-align":"left"}},[t("code",null,".source.q")],-1),$a={style:{"text-align":"left"}},tr={href:"https://atom.io/packages/autocomplete-kdb-q",target:"_blank",rel:"noopener noreferrer"},er=t("td",{style:{"text-align":"left"}},[t("code",null,"2.0.0")],-1),lr={style:{"text-align":"left"}},or={href:"https://atom.io/packages/language-rust",target:"_blank",rel:"noopener noreferrer"},sr=t("td",{style:{"text-align":"left"}},[t("code",null,".source.rust")],-1),nr={style:{"text-align":"left"}},ar={href:"https://atom.io/packages/racer",target:"_blank",rel:"noopener noreferrer"},rr=t("td",{style:{"text-align":"left"}},[t("code",null,"2.0.0")],-1),ir={style:{"text-align":"left"}},dr={href:"https://atom.io/packages/language-rdf",target:"_blank",rel:"noopener noreferrer"},cr=t("td",{style:{"text-align":"left"}},[t("code",null,".source.turtle")],-1),_r={style:{"text-align":"left"}},hr={href:"https://atom.io/packages/turtle-completer",target:"_blank",rel:"noopener noreferrer"},gr=t("td",{style:{"text-align":"left"}},[t("code",null,"2.0.0")],-1),ur={style:{"text-align":"left"}},fr={href:"https://atom.io/packages/atom-typescript",target:"_blank",rel:"noopener noreferrer"},pr=t("td",{style:{"text-align":"left"}},[t("code",null,".source.ts")],-1),yr={style:{"text-align":"left"}},xr={href:"https://atom.io/packages/atom-typescript",target:"_blank",rel:"noopener noreferrer"},mr=t("td",{style:{"text-align":"left"}},[t("code",null,"8.11.0")],-1),kr={style:{"text-align":"left"}},br={href:"https://atom.io/packages/mavensmate-atom",target:"_blank",rel:"noopener noreferrer"},vr=t("td",{style:{"text-align":"left"}},[t("code",null,".visualforce")],-1),jr={style:{"text-align":"left"}},Sr={href:"https://atom.io/packages/mavensmate-atom",target:"_blank",rel:"noopener noreferrer"},Pr=t("td",{style:{"text-align":"left"}},[t("code",null,"1.1.0")],-1),Lr={style:{"text-align":"left"}},Cr={href:"https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/wiki/Whitelisting-code-which-flags-errors",target:"_blank",rel:"noopener noreferrer"},Tr=t("td",{style:{"text-align":"left"}},[t("code",null,".php .comment")],-1),Rr={style:{"text-align":"left"}},Mr={href:"https://atom.io/packages/autocomplete-wpcs-flags",target:"_blank",rel:"noopener noreferrer"},wr=t("td",{style:{"text-align":"left"}},[t("code",null,"2.0.0")],-1),Ar=t("h2",{id:"providers-not-tied-to-a-specific-grammar",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#providers-not-tied-to-a-specific-grammar","aria-hidden":"true"},"#"),e(" Providers Not Tied To A Specific Grammar")],-1),Gr=t("thead",null,[t("tr",null,[t("th",{style:{"text-align":"left"}},"Selector"),t("th",{style:{"text-align":"left"}},"Provider"),t("th",{style:{"text-align":"left"}},"Status")])],-1),qr=t("td",{style:{"text-align":"left"}},[t("code",null,"*")],-1),Hr={style:{"text-align":"left"}},Er={href:"https://atom.io/packages/autocomplete-emojis",target:"_blank",rel:"noopener noreferrer"},Ir=t("td",{style:{"text-align":"left"}},[t("code",null,"1.0.0")],-1),Nr=t("td",{style:{"text-align":"left"}},[t("code",null,"*")],-1),Jr={style:{"text-align":"left"}},Or={href:"https://atom.io/packages/autocomplete-snippets",target:"_blank",rel:"noopener noreferrer"},Br=t("td",{style:{"text-align":"left"}},[t("code",null,"2.0.0")],-1),Dr=t("td",{style:{"text-align":"left"}},[t("code",null,"*")],-1),Fr={style:{"text-align":"left"}},Vr={href:"https://atom.io/packages/autocomplete-paths",target:"_blank",rel:"noopener noreferrer"},Wr=t("td",{style:{"text-align":"left"}},[t("code",null,"1.0.0")],-1),Qr=t("td",{style:{"text-align":"left"}},[t("code",null,"*")],-1),Xr={style:{"text-align":"left"}},zr={href:"https://atom.io/packages/atom-path-intellisense",target:"_blank",rel:"noopener noreferrer"},Kr=t("td",{style:{"text-align":"left"}},[t("code",null,"1.2.1")],-1),Ur=t("td",{style:{"text-align":"left"}},[t("code",null,"*")],-1),Yr={style:{"text-align":"left"}},Zr={href:"https://atom.io/packages/atom-ctags",target:"_blank",rel:"noopener noreferrer"},$r=t("td",{style:{"text-align":"left"}},[t("code",null,"2.0.0")],-1),ti=t("td",{style:{"text-align":"left"}},[t("code",null,".source.js, .source.jsx")],-1),ei={style:{"text-align":"left"}},li={href:"https://atom.io/packages/ide-flow",target:"_blank",rel:"noopener noreferrer"},oi=t("td",{style:{"text-align":"left"}},[t("code",null,"1.1.0")],-1),si=t("td",{style:{"text-align":"left"}},[t("code",null,".source.js, .source.jsx, .source.coffee")],-1),ni={style:{"text-align":"left"}},ai={href:"https://atom.io/packages/autocomplete-underdash",target:"_blank",rel:"noopener noreferrer"},ri=t("td",{style:{"text-align":"left"}},[t("code",null,"2.0.0")],-1),ii=t("td",{style:{"text-align":"left"}},[t("code",null,".source.css"),e(", "),t("code",null,".source.css.less"),e(", "),t("code",null,".source.sass"),e(", "),t("code",null,".source.css.scss"),e(", "),t("code",null,".source.stylus")],-1),di={style:{"text-align":"left"}},ci={href:"https://atom.io/packages/project-palette-finder",target:"_blank",rel:"noopener noreferrer"},_i=t("td",{style:{"text-align":"left"}},[t("code",null,"1.1.0")],-1),hi=t("td",{style:{"text-align":"left"}},[t("code",null,"*")],-1),gi={style:{"text-align":"left"}},ui={href:"https://atom.io/packages/you-complete-me",target:"_blank",rel:"noopener noreferrer"},fi=t("td",{style:{"text-align":"left"}},[t("code",null,"2.0.0")],-1),pi=t("td",{style:{"text-align":"left"}},[t("code",null,"English word autocompetion with the hint of explanation.")],-1),yi={style:{"text-align":"left"}},xi={href:"https://atom.io/packages/autocomplete-en-en",target:"_blank",rel:"noopener noreferrer"},mi=t("td",{style:{"text-align":"left"}},[t("code",null,"2.0.0")],-1),ki=t("p",null,"--",-1),bi=t("h2",{id:"providers-requested-by-the-community",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#providers-requested-by-the-community","aria-hidden":"true"},"#"),e(" Providers Requested By The Community")],-1),vi=t("code",null,"autocomplete-plus",-1),ji=t("code",null,"Provider",-1),Si=t("ul",null,[t("li",null,"Emmet: https://github.com/atom-community/autocomplete-plus/issues/156"),t("li",null,"LESS: https://github.com/atom-community/autocomplete-plus/issues/151")],-1),Pi=t("h2",{id:"packages-that-claim-autocomplete-but-are-not-api-1-0-0-compatible",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#packages-that-claim-autocomplete-but-are-not-api-1-0-0-compatible","aria-hidden":"true"},"#"),e(" Packages That Claim Autocomplete, But Are Not API 1.0.0 Compatible")],-1),Li={href:"https://atom.io/packages/autocomplete-plus-async",target:"_blank",rel:"noopener noreferrer"},Ci=t("li",null,"https://atom.io/packages/ios (doesn't make use of autocomplete-plus)",-1),Ti=t("li",null,"https://atom.io/packages/language-hn (see: https://github.com/ignaciocases/language-hn/issues/1 for API 1.0.0 compatibility)",-1),Ri=t("li",null,"https://atom.io/packages/rsense (see: https://github.com/rsense/atom-rsense/issues/1 for API 1.0.0 compatibility)",-1),Mi=t("h2",{id:"deprecated-providers",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#deprecated-providers","aria-hidden":"true"},"#"),e(" Deprecated Providers")],-1),wi=t("p",null,"If you are using one of these providers, please uninstall the package as it is no longer maintained.",-1),Ai={href:"https://atom.io/packages/go-plus",target:"_blank",rel:"noopener noreferrer"},Gi={href:"https://atom.io/packages/autocomplete-python",target:"_blank",rel:"noopener noreferrer"},qi=t("h2",{id:"other-forks-of-autocomplete",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#other-forks-of-autocomplete","aria-hidden":"true"},"#"),e(" Other Forks Of Autocomplete")],-1),Hi=t("ul",null,[t("li",null,"https://github.com/xumingthepoet/autocomplete-plus-elixir (never published)"),t("li",null,[e("https://atom.io/packages/autocomplete-jedi (fork of "),t("code",null,"autocomplete"),e(")")]),t("li",null,"https://atom.io/packages/rubymotion (extends default autocomplete package)")],-1);function Ei(Ii,Ni){const o=s("ExternalLinkIcon"),n=s("RouterLink");return r(),i("div",null,[h,t("table",null,[g,t("tbody",null,[u,t("tr",null,[t("td",f,[t("a",p,[e("CoffeeScript (Literate)"),l(o)])]),y,x,m]),t("tr",null,[t("td",k,[t("a",b,[e("CoffeeScript"),l(o)])]),v,j,S]),t("tr",null,[t("td",P,[t("a",L,[e("JSON"),l(o)])]),C,T,R]),t("tr",null,[t("td",M,[t("a",w,[e("Shell Session"),l(o)])]),A,G,q]),t("tr",null,[t("td",H,[t("a",E,[e("Shell Script"),l(o)])]),I,N,J]),t("tr",null,[t("td",O,[t("a",B,[e("Hyperlink"),l(o)])]),D,F,V]),t("tr",null,[t("td",W,[t("a",Q,[e("TODO"),l(o)])]),X,z,K]),t("tr",null,[t("td",U,[t("a",Y,[e("C"),l(o)])]),Z,t("td",$,[t("a",tt,[e("autocomplete-clang"),l(o)])]),et]),t("tr",null,[t("td",lt,[t("a",ot,[e("C++"),l(o)])]),st,t("td",nt,[t("a",at,[e("autocomplete-clang"),l(o)])]),rt]),t("tr",null,[t("td",it,[t("a",dt,[e("Clojure"),l(o)])]),ct,t("td",_t,[t("a",ht,[e("proto-repl"),l(o)])]),gt]),t("tr",null,[t("td",ut,[t("a",ft,[e("CSS"),l(o)])]),pt,t("td",yt,[t("a",xt,[e("autocomplete-css"),l(o)])]),mt]),t("tr",null,[t("td",kt,[t("a",bt,[e("GitHub Markdown"),l(o)])]),vt,t("td",jt,[t("a",St,[e("autocomplete-bibtex"),l(o)])]),Pt]),t("tr",null,[t("td",Lt,[t("a",Ct,[e("Git Config"),l(o)])]),Tt,Rt,Mt]),t("tr",null,[t("td",wt,[t("a",At,[e("Git Commit Message"),l(o)])]),Gt,qt,Ht]),t("tr",null,[t("td",Et,[t("a",It,[e("Git Rebase Message"),l(o)])]),Nt,Jt,Ot]),t("tr",null,[t("td",Bt,[t("a",Dt,[e("HTML (Go)"),l(o)])]),Ft,Vt,Wt]),t("tr",null,[t("td",Qt,[t("a",Xt,[e("Go"),l(o)])]),zt,t("td",Kt,[t("a",Ut,[e("go-plus"),l(o)]),e(", "),t("a",Yt,[e("autocomplete-go"),l(o)])]),Zt]),t("tr",null,[t("td",$t,[t("a",te,[e("Go Template"),l(o)])]),ee,le,oe]),t("tr",null,[t("td",se,[t("a",ne,[e("HTML"),l(o)])]),ae,t("td",re,[t("a",ie,[e("autocomplete-html"),l(o)])]),de]),t("tr",null,[t("td",ce,[t("a",_e,[e("JavaScript"),l(o)])]),he,t("td",ge,[t("a",ue,[e("atom-ternjs"),l(o)])]),fe]),t("tr",null,[t("td",pe,[t("a",ye,[e("Java Properties"),l(o)])]),xe,me,ke]),t("tr",null,[t("td",be,[t("a",ve,[e("Regular Expressions (JavaScript)"),l(o)])]),je,Se,Pe]),t("tr",null,[t("td",Le,[t("a",Ce,[e("JavaServer Pages"),l(o)])]),Te,t("td",Re,[t("a",Me,[e("autocomplete-jsp"),l(o)])]),we]),t("tr",null,[t("td",Ae,[t("a",Ge,[e("Java"),l(o)])]),qe,t("td",He,[t("a",Ee,[e("autocomplete-java-minus"),l(o)])]),Ie]),t("tr",null,[t("td",Ne,[t("a",Je,[e("JUnit Test Report"),l(o)])]),Oe,Be,De]),t("tr",null,[t("td",Fe,[t("a",Ve,[e("Makefile"),l(o)])]),We,Qe,Xe]),t("tr",null,[t("td",ze,[t("a",Ke,[e("LESS"),l(o)])]),Ue,Ye,Ze]),t("tr",null,[t("td",$e,[t("a",tl,[e("SQL (Mustache)"),l(o)])]),el,ll,ol]),t("tr",null,[t("td",sl,[t("a",nl,[e("HTML (Mustache)"),l(o)])]),al,rl,il]),t("tr",null,[t("td",dl,[t("a",cl,[e("Objective-C++"),l(o)])]),_l,t("td",hl,[t("a",gl,[e("autocomplete-clang"),l(o)])]),ul]),t("tr",null,[t("td",fl,[t("a",pl,[e("Strings File"),l(o)])]),yl,xl,ml]),t("tr",null,[t("td",kl,[t("a",bl,[e("Objective-C"),l(o)])]),vl,t("td",jl,[t("a",Sl,[e("autocomplete-clang"),l(o)])]),Pl]),t("tr",null,[t("td",Ll,[t("a",Cl,[e("Property List (XML)"),l(o)])]),Tl,Rl,Ml]),t("tr",null,[t("td",wl,[t("a",Al,[e("Property List (Old-Style)"),l(o)])]),Gl,ql,Hl]),t("tr",null,[t("td",El,[t("a",Il,[e("Perl"),l(o)])]),Nl,Jl,Ol]),t("tr",null,[t("td",Bl,[t("a",Dl,[e("PHP"),l(o)])]),Fl,Vl,Wl]),t("tr",null,[Ql,Xl,t("td",zl,[t("a",Kl,[e("php-integrator-autocomplete-plus"),l(o)]),e(", "),t("a",Ul,[e("atom-autocomplete-php"),l(o)]),e(", "),t("a",Yl,[e("autocomplete-php"),l(o)])]),Zl]),t("tr",null,[t("td",$l,[t("a",to,[e("Python Console"),l(o)])]),eo,lo,oo]),t("tr",null,[t("td",so,[t("a",no,[e("Python Traceback"),l(o)])]),ao,ro,io]),t("tr",null,[t("td",co,[t("a",_o,[e("Regular Expressions (Python)"),l(o)])]),ho,go,uo]),t("tr",null,[t("td",fo,[t("a",po,[e("Python"),l(o)])]),yo,t("td",xo,[t("a",mo,[e("autocomplete-python"),l(o)]),e(", "),t("a",ko,[e("autocomplete-python-jedi"),l(o)])]),bo]),t("tr",null,[t("td",vo,[t("a",jo,[e("Ruby on Rails (RJS)"),l(o)])]),So,Po,Lo]),t("tr",null,[t("td",Co,[t("a",To,[e("Ruby"),l(o)])]),Ro,Mo,wo]),t("tr",null,[t("td",Ao,[t("a",Go,[e("HTML (Ruby - ERB)"),l(o)])]),qo,Ho,Eo]),t("tr",null,[t("td",Io,[t("a",No,[e("HTML (Rails)"),l(o)])]),Jo,Oo,Bo]),t("tr",null,[t("td",Do,[t("a",Fo,[e("SQL (Rails)"),l(o)])]),Vo,Wo,Qo]),t("tr",null,[t("td",Xo,[t("a",zo,[e("JavaScript (Rails)"),l(o)])]),Ko,Uo,Yo]),t("tr",null,[t("td",Zo,[t("a",$o,[e("Ruby on Rails"),l(o)])]),ts,es,ls]),t("tr",null,[t("td",os,[t("a",ss,[e("Sass"),l(o)])]),ns,as,rs]),t("tr",null,[t("td",is,[t("a",ds,[e("Plain Text"),l(o)])]),cs,_s,hs]),t("tr",null,[t("td",gs,[t("a",us,[e("SCSS"),l(o)])]),fs,ps,ys]),t("tr",null,[t("td",xs,[t("a",ms,[e("SQL"),l(o)])]),ks,t("td",bs,[t("a",vs,[e("autocomplete-sql"),l(o)])]),js]),t("tr",null,[t("td",Ss,[t("a",Ps,[e("TOML"),l(o)])]),Ls,Cs,Ts]),t("tr",null,[t("td",Rs,[t("a",Ms,[e("XSL"),l(o)])]),ws,As,Gs]),t("tr",null,[t("td",qs,[t("a",Hs,[e("XML"),l(o)])]),Es,t("td",Is,[t("a",Ns,[e("autocomplete-xml"),l(o)])]),Js]),t("tr",null,[t("td",Os,[t("a",Bs,[e("YAML"),l(o)])]),Ds,Fs,Vs])])]),Ws,t("table",null,[Qs,t("tbody",null,[t("tr",null,[t("td",Xs,[t("a",zs,[e("Apex"),l(o)])]),Ks,t("td",Us,[t("a",Ys,[e("mavensmate-atom"),l(o)])]),Zs]),t("tr",null,[t("td",$s,[t("a",tn,[e("AsciiDoc"),l(o)])]),en,t("td",ln,[t("a",on,[e("autocomplete-asciidoc"),l(o)])]),sn]),t("tr",null,[t("td",nn,[t("a",an,[e("C#"),l(o)])]),rn,t("td",dn,[t("a",cn,[e("omnisharp-atom"),l(o)])]),_n]),t("tr",null,[t("td",hn,[t("a",gn,[e("ComputerCraft"),l(o)])]),un,t("td",fn,[t("a",pn,[e("autocomplete-computercraft"),l(o)])]),yn]),t("tr",null,[t("td",xn,[t("a",mn,[e("Curry"),l(o)])]),kn,t("td",bn,[t("a",vn,[e("autocomplete-curry"),l(o)])]),jn]),t("tr",null,[t("td",Sn,[t("a",Pn,[e("Dart"),l(o)])]),Ln,t("td",Cn,[t("a",Tn,[e("dart-tools"),l(o)])]),Rn]),t("tr",null,[t("td",Mn,[t("a",wn,[e("Dart"),l(o)])]),An,t("td",Gn,[t("a",qn,[e("dartlang"),l(o)])]),Hn]),t("tr",null,[t("td",En,[t("a",In,[e("Elixir"),l(o)])]),Nn,t("td",Jn,[t("a",On,[e("autocomplete-elixir"),l(o)])]),Bn]),t("tr",null,[t("td",Dn,[t("a",Fn,[e("Erlang"),l(o)])]),Vn,t("td",Wn,[t("a",Qn,[e("autocomplete-erlang"),l(o)])]),Xn]),t("tr",null,[t("td",zn,[t("a",Kn,[e("GLSL"),l(o)])]),Un,t("td",Yn,[t("a",Zn,[e("autocomplete-glsl"),l(o)])]),$n]),t("tr",null,[t("td",ta,[t("a",ea,[e("HackLang"),l(o)])]),la,t("td",oa,[t("a",sa,[e("autocomplete-hack"),l(o)])]),na]),t("tr",null,[t("td",aa,[t("a",ra,[e("Haskell"),l(o)])]),ia,t("td",da,[t("a",ca,[e("autocomplete-haskell"),l(o)])]),_a]),t("tr",null,[t("td",ha,[t("a",ga,[e("Haskell"),l(o)])]),ua,t("td",fa,[t("a",pa,[e("ide-haskell"),l(o)])]),ya]),t("tr",null,[t("td",xa,[t("a",ma,[e("Haxe"),l(o)])]),ka,t("td",ba,[t("a",va,[e("autocomplete-haxe"),l(o)])]),ja]),t("tr",null,[t("td",Sa,[t("a",Pa,[e("LaTeX"),l(o)])]),La,t("td",Ca,[t("a",Ta,[e("autocomplete-latex-cite"),l(o)]),e(", "),t("a",Ra,[e("autocomplete-latex-references"),l(o)]),e(", "),t("a",Ma,[e("autocomplete-glossaries"),l(o)])]),wa]),t("tr",null,[t("td",Aa,[t("a",Ga,[e("Marko"),l(o)])]),qa,t("td",Ha,[t("a",Ea,[e("autocomplete-marko"),l(o)])]),Ia]),t("tr",null,[t("td",Na,[t("a",Ja,[e("Nunjucks"),l(o)])]),Oa,t("td",Ba,[t("a",Da,[e("autocomplete-nunjucks"),l(o)])]),Fa]),t("tr",null,[t("td",Va,[t("a",Wa,[e("Pig"),l(o)])]),Qa,t("td",Xa,[t("a",za,[e("pig"),l(o)])]),Ka]),t("tr",null,[t("td",Ua,[t("a",Ya,[e("Q/K"),l(o)])]),Za,t("td",$a,[t("a",tr,[e("autocomplete-kdb-q"),l(o)])]),er]),t("tr",null,[t("td",lr,[t("a",or,[e("Rust"),l(o)])]),sr,t("td",nr,[t("a",ar,[e("racer"),l(o)])]),rr]),t("tr",null,[t("td",ir,[t("a",dr,[e("Turtle"),l(o)])]),cr,t("td",_r,[t("a",hr,[e("turtle-completer"),l(o)])]),gr]),t("tr",null,[t("td",ur,[t("a",fr,[e("TypeScript"),l(o)])]),pr,t("td",yr,[t("a",xr,[e("atom-typescript"),l(o)])]),mr]),t("tr",null,[t("td",kr,[t("a",br,[e("Visualforce"),l(o)])]),vr,t("td",jr,[t("a",Sr,[e("mavensmate-atom"),l(o)])]),Pr]),t("tr",null,[t("td",Lr,[t("a",Cr,[e("WordPress Coding Standard Whitelist Flags"),l(o)])]),Tr,t("td",Rr,[t("a",Mr,[e("autocomplete-wpcs-flags"),l(o)])]),wr])])]),Ar,t("table",null,[Gr,t("tbody",null,[t("tr",null,[qr,t("td",Hr,[t("a",Er,[e("autocomplete-emojis"),l(o)])]),Ir]),t("tr",null,[Nr,t("td",Jr,[t("a",Or,[e("autocomplete-snippets"),l(o)])]),Br]),t("tr",null,[Dr,t("td",Fr,[t("a",Vr,[e("autocomplete-paths"),l(o)])]),Wr]),t("tr",null,[Qr,t("td",Xr,[t("a",zr,[e("atom-path-intellisense"),l(o)])]),Kr]),t("tr",null,[Ur,t("td",Yr,[t("a",Zr,[e("atom-ctags"),l(o)])]),$r]),t("tr",null,[ti,t("td",ei,[t("a",li,[e("ide-flow"),l(o)])]),oi]),t("tr",null,[si,t("td",ni,[t("a",ai,[e("autocomplete-underdash"),l(o)])]),ri]),t("tr",null,[ii,t("td",di,[t("a",ci,[e("project-palette-finder"),l(o)])]),_i]),t("tr",null,[hi,t("td",gi,[t("a",ui,[e("you-complete-me"),l(o)])]),fi]),t("tr",null,[pi,t("td",yi,[t("a",xi,[e("autocomplete-en-en"),l(o)])]),mi])])]),ki,bi,t("p",null,[e("If you'd like to contribute and are interested in learning how to write an "),vi,e(),l(n,{to:"/docs/packages/core/autocomplete-plus/provider-api.html"},{default:d(()=>[ji]),_:1}),e(", start here:")]),Si,Pi,t("ul",null,[t("li",null,[e("https://github.com/maun/atom-rust-plus (never published, uses "),t("a",Li,[e("autocomplete-plus-async"),l(o)]),e(")")]),Ci,Ti,Ri]),Mi,wi,t("ul",null,[t("li",null,[e("https://github.com/vito/atom-autocomplete-gocode (deprecated, use "),t("a",Ai,[e("go-plus"),l(o)]),e(" instead)")]),t("li",null,[e("https://github.com/tinloaf/autocomplete-plus-python-jedi (deprecated, use "),t("a",Gi,[e("autocomplete-python"),l(o)]),e(" instead)")])]),qi,Hi])}const Oi=a(_,[["render",Ei],["__file","autocomplete-providers.html.vue"]]);export{Oi as default}; diff --git a/assets/autocomplete-providers.html.39836bcd.js b/assets/autocomplete-providers.html.39836bcd.js new file mode 100644 index 0000000000..28d50eefeb --- /dev/null +++ b/assets/autocomplete-providers.html.39836bcd.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-4ebc0363","path":"/docs/packages/core/autocomplete-plus/autocomplete-providers.html","title":"Autocomplete Providers","lang":"en-us","frontmatter":{"lang":"en-us","title":"Autocomplete Providers"},"excerpt":"","headers":[{"level":2,"title":"Built-In Providers","slug":"built-in-providers","link":"#built-in-providers","children":[]},{"level":2,"title":"Providers For Built-In Grammars","slug":"providers-for-built-in-grammars","link":"#providers-for-built-in-grammars","children":[]},{"level":2,"title":"Providers For Third-Party Grammars","slug":"providers-for-third-party-grammars","link":"#providers-for-third-party-grammars","children":[]},{"level":2,"title":"Providers Not Tied To A Specific Grammar","slug":"providers-not-tied-to-a-specific-grammar","link":"#providers-not-tied-to-a-specific-grammar","children":[]},{"level":2,"title":"Providers Requested By The Community","slug":"providers-requested-by-the-community","link":"#providers-requested-by-the-community","children":[]},{"level":2,"title":"Packages That Claim Autocomplete, But Are Not API 1.0.0 Compatible","slug":"packages-that-claim-autocomplete-but-are-not-api-1-0-0-compatible","link":"#packages-that-claim-autocomplete-but-are-not-api-1-0-0-compatible","children":[]},{"level":2,"title":"Deprecated Providers","slug":"deprecated-providers","link":"#deprecated-providers","children":[]},{"level":2,"title":"Other Forks Of Autocomplete","slug":"other-forks-of-autocomplete","link":"#other-forks-of-autocomplete","children":[]}],"git":{"updatedTime":1663970614000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":2}]},"readingTime":{"minutes":4.7,"words":1409},"filePathRelative":"docs/packages/core/autocomplete-plus/autocomplete-providers.md"}');export{e as data}; diff --git a/assets/autocomplete.ad599dce.png b/assets/autocomplete.ad599dce.png new file mode 100644 index 0000000000..877433c6b9 Binary files /dev/null and b/assets/autocomplete.ad599dce.png differ diff --git a/assets/autocomplete.b0bae406.js b/assets/autocomplete.b0bae406.js new file mode 100644 index 0000000000..8fbeaa2f55 --- /dev/null +++ b/assets/autocomplete.b0bae406.js @@ -0,0 +1 @@ +const s="/assets/autocomplete.ad599dce.png";export{s as _}; diff --git a/assets/autocomplete.html.a3130bec.js b/assets/autocomplete.html.a3130bec.js new file mode 100644 index 0000000000..e73aec0223 --- /dev/null +++ b/assets/autocomplete.html.a3130bec.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-9ad007fc","path":"/docs/launch-manual/sections/using-pulsar/sections/autocomplete.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Autocomplete","slug":"autocomplete","link":"#autocomplete","children":[]}],"git":{"updatedTime":1669229414000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":2}]},"readingTime":{"minutes":0.41,"words":122},"filePathRelative":"docs/launch-manual/sections/using-pulsar/sections/autocomplete.md"}');export{e as data}; diff --git a/assets/autocomplete.html.b253649f.js b/assets/autocomplete.html.b253649f.js new file mode 100644 index 0000000000..06c0feb146 --- /dev/null +++ b/assets/autocomplete.html.b253649f.js @@ -0,0 +1 @@ +import{_ as a}from"./autocomplete.b0bae406.js";import{_ as l,o as n,c as s,a as t,b as e,d as r,f as p,r as c}from"./app.0e1565ce.js";const i={},m=p('

    Autocomplete

    If you're still looking to save some typing time, Atom also ships with simple autocompletion functionality.

    The autocomplete system lets you view and insert possible completions in the editor using Tab or Enter.

    Autocomplete menu

    By default, the autocomplete system will look through the current open file for strings that match what you're starting to type.

    If you want more options, in the Settings panel for the autocomplete-plus package you can toggle a setting to make autocomplete-plus look for text in all your open buffers rather than just the current file.

    ',6),u={href:"https://github.com/atom/autocomplete-plus",target:"_blank",rel:"noopener noreferrer"};function h(f,d){const o=c("ExternalLinkIcon");return n(),s("div",null,[m,t("p",null,[e("The Autocomplete functionality is implemented in the "),t("a",u,[e("autocomplete-plus"),r(o)]),e(" package.")])])}const k=l(i,[["render",h],["__file","autocomplete.html.vue"]]);export{k as default}; diff --git a/assets/autocomplete.html.ec6b60fc.js b/assets/autocomplete.html.ec6b60fc.js new file mode 100644 index 0000000000..f0c5d85537 --- /dev/null +++ b/assets/autocomplete.html.ec6b60fc.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-2c165692","path":"/docs/atom-archive/using-atom/sections/autocomplete.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"Autocomplete","slug":"autocomplete","link":"#autocomplete","children":[]}],"git":{"updatedTime":1664634443000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1},{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":0.42,"words":127},"filePathRelative":"docs/atom-archive/using-atom/sections/autocomplete.md"}');export{e as data}; diff --git a/assets/autocomplete.html.f60be63b.js b/assets/autocomplete.html.f60be63b.js new file mode 100644 index 0000000000..9657ad059e --- /dev/null +++ b/assets/autocomplete.html.f60be63b.js @@ -0,0 +1 @@ +import{_ as a}from"./autocomplete.b0bae406.js";import{_ as n,o as l,c as r,a as t,b as e,d as s,f as p,r as c}from"./app.0e1565ce.js";const i={},u=p('

    Autocomplete

    If you're still looking to save some typing time, Pulsar also ships with simple autocompletion functionality.

    The autocomplete system lets you view and insert possible completions in the editor using Tab or Enter.

    Autocomplete menu

    By default, the autocomplete system will look through the current open file for strings that match what you're starting to type.

    If you want more options, in the Settings panel for the autocomplete-plus package you can toggle a setting to make autocomplete-plus look for text in all your open buffers rather than just the current file.

    ',6),m={href:"https://github.com/pulsar-edit/autocomplete-plus",target:"_blank",rel:"noopener noreferrer"};function h(d,f){const o=c("ExternalLinkIcon");return l(),r("div",null,[u,t("p",null,[e("The Autocomplete functionality is implemented in the "),t("a",m,[e("autocomplete-plus"),s(o)]),e(" package.")])])}const k=n(i,[["render",h],["__file","autocomplete.html.vue"]]);export{k as default}; diff --git a/assets/bacadra-navigation-panel.e144ab63.png b/assets/bacadra-navigation-panel.e144ab63.png new file mode 100644 index 0000000000..3691c44338 Binary files /dev/null and b/assets/bacadra-navigation-panel.e144ab63.png differ diff --git a/assets/bacadra-pdf-viewer.9f160fe1.png b/assets/bacadra-pdf-viewer.9f160fe1.png new file mode 100644 index 0000000000..3b83012697 Binary files /dev/null and b/assets/bacadra-pdf-viewer.9f160fe1.png differ diff --git a/assets/bacadra-project-files.ca067687.png b/assets/bacadra-project-files.ca067687.png new file mode 100644 index 0000000000..7df91627d9 Binary files /dev/null and b/assets/bacadra-project-files.ca067687.png differ diff --git a/assets/backend-badge-made.716d80d6.png b/assets/backend-badge-made.716d80d6.png new file mode 100644 index 0000000000..6376f22aa1 Binary files /dev/null and b/assets/backend-badge-made.716d80d6.png differ diff --git a/assets/backend-badge.f36006d1.png b/assets/backend-badge.f36006d1.png new file mode 100644 index 0000000000..ee7e3b8116 Binary files /dev/null and b/assets/backend-badge.f36006d1.png differ diff --git a/assets/basic-customization.html.d03c4748.js b/assets/basic-customization.html.d03c4748.js new file mode 100644 index 0000000000..dfc3ca1ef0 --- /dev/null +++ b/assets/basic-customization.html.d03c4748.js @@ -0,0 +1,71 @@ +import{_ as t,a as i,b as r,c as l,d as c}from"./portable-mode-folder.90669a9a.js";import{_ as p,o as d,c as u,a as e,b as s,d as o,f as a,r as h}from"./app.0e1565ce.js";const g={},m=e("h2",{id:"basic-customization",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#basic-customization","aria-hidden":"true"},"#"),s(" Basic Customization")],-1),f=e("p",null,"Now that we are feeling comfortable with just about everything built into Pulsar, let's look at how to tweak it. Perhaps there is a keybinding that you use a lot but feels wrong or a color that isn't quite right for you. Pulsar is amazingly flexible, so let's go over some of the simpler flexes it can do.",-1),y=e("h3",{id:"configuring-with-cson",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#configuring-with-cson","aria-hidden":"true"},"#"),s(" Configuring with CSON")],-1),b=e("a",{href:"#style-tweaks"},"style sheet",-1),v=e("a",{href:"../core-hacking#the-init-file"},"Init Script",-1),k={href:"https://github.com/bevry/cson#what-is-cson",target:"_blank",rel:"noopener noreferrer"},w={href:"https://json.org/",target:"_blank",rel:"noopener noreferrer"},x=a(`
    key:
    +  key: value
    +  key: value
    +  key: [value, value]
    +

    Objects are the backbone of any CSON file, and are delineated by indentation (as in the above example). A key's value can either be a String, a Number, an Object, a Boolean, null, or an Array of any of these data types.

    Warning

    Just like the more common JSON, CSON's keys can only be repeated once per object. If there are duplicate keys, then the last usage of that key overwrites all others, as if they weren't there. The same holds true for Pulsar's config files.

    Don't do this:

    # Only the second snippet will be loaded
    +'.source.js':
    +  'console.log':
    +    'prefix': 'log'
    +    'body': 'console.log(\${1:"crash"});$2'
    +'.source.js':
    +  'console.error':
    +    'prefix': 'error'
    +    'body': 'console.error(\${1:"crash"});$2'
    +

    Use this instead:

    # Both snippets will be loaded
    +'.source.js':
    +  'console.log':
    +    'prefix': 'log'
    +    'body': 'console.log(\${1:"crash"});$2'
    +  'console.error':
    +    'prefix': 'error'
    +    'body': 'console.error(\${1:"crash"});$2'
    +

    Style Tweaks

    If you want to apply quick-and-dirty personal styling changes without creating an entire theme that you intend to publish, you can add styles to the styles.less file in your LNX/MAC: ~/.pulsar - WIN: %USERPROFILE%\\.pulsar directory. You can open this file in an editor from the LNX: Edit > Stylesheet - MAC: Pulsar > Stylesheet - WIN: File > Stylesheet menu.

    For example, to change the colors of the Status Bar, you could add the following rule to your styles.less file:

    .status-bar {
    +	color: white;
    +	background-color: black;
    +}
    +

    The easiest way to see what classes are available to style is to inspect the DOM manually via the Developer Tools. We'll go over the Developer Tools in great detail in the next chapter, but for now let's take a simple look. You can open the Developer Tools by pressing LNX/WIN: Ctrl+Shift+I - MAC: Alt+Cmd+I, which will bring up the Chromium Developer Tools panel.

    Developer Tools

    With the Developer Tools, you can inspect all the elements in Pulsar. If you want to update the style of something, you can figure out what classes it has and add a Less rule to your stylesheet to modify it.

    ',10),P={class:"custom-container tip"},C=e("p",{class:"custom-container-title"},"Tip",-1),_={href:"http://www.lesscss.org",target:"_blank",rel:"noopener noreferrer"},S=e("p",null,[s("If you prefer to use CSS instead, you can do that in the same "),e("code",null,"styles.less"),s(" file, since CSS is also valid in Less.")],-1),L=a(`

    Customizing Keybindings

    Pulsar keymaps work similarly to stylesheets. Just as stylesheets use selectors to apply styles to elements, Pulsar keymaps use selectors to associate key combinations with events in specific contexts. Here's a small example, excerpted from Pulsar's built-in keymap:

    'atom-text-editor':
    +  'enter': 'editor:newline'
    +
    +'atom-text-editor[mini] input':
    +  'enter': 'core:confirm'
    +

    This keymap defines the meaning of Enter in two different contexts. In a normal editor, pressing Enter triggers the editor:newline command, which causes the editor to insert a newline. But if the same keystroke occurs inside a select list's mini-editor, it instead triggers the core:confirm command based on the binding in the more-specific selector.

    By default, keymap.cson is loaded when Pulsar is started. It will always be loaded last, giving you the chance to override bindings that are defined by Pulsar's core keymaps or third-party packages. You can open this file in an editor from the LNX: Edit > Keymap - MAC: Pulsar > Keymap - WIN: File > Keymap menu.

    You can see all the keybindings that are currently configured in your installation of Pulsar in the Keybindings tab in the Settings View.

    If you run into problems with keybindings, the Keybinding Resolver is a huge help. It can be opened with the LNX/WIN: Ctrl+. - MAC: Cmd+. key combination. It will show you what keys Pulsar saw you press and what command Pulsar executed because of that combination.

    Global Configuration Settings

    Pulsar loads configuration settings from the config.cson file in your LNX/MAC: ~/.pulsar - WIN: %USERPROFILE%\\.pulsar directory.

    '*':
    +  'core':
    +    'excludeVcsIgnoredPaths': true
    +  'editor':
    +    'fontSize': 18
    +

    The configuration is grouped into global settings under the * key and language-specific settings under scope named keys like .python.source or .html.text. Underneath that, you'll find configuration settings grouped by package name or one of the two core namespaces: core or editor.

    You can open this file in an editor from the LNX: Edit > Config - MAC: Pulsar > Config - WIN: File > Config menu.

    Configuration Key Reference

    • core
      • customFileTypes: Associations of language scope to file extensions (see Customizing Language Recognition)
      • disabledPackages: An array of package names to disable
      • excludeVcsIgnoredPaths: Don't search within files specified by .gitignore
      • ignoredNames: File names to ignore across all of Pulsar
      • projectHome: The directory where projects are assumed to be located
      • themes: An array of theme names to load, in cascading order
    • editor
      • autoIndent: Enable/disable basic auto-indent (defaults to true)
      • nonWordCharacters: A string of non-word characters to define word boundaries
      • fontSize: The editor font size
      • fontFamily: The editor font family
      • invisibles: A hash of characters Pulsar will use to render whitespace characters. Keys are whitespace character types, values are rendered characters (use value false to turn off individual whitespace character types)
        • tab: Hard tab characters
        • cr: Carriage return (for Microsoft-style line endings)
        • eol: \\n characters
        • space: Leading and trailing space characters
      • lineHeight: Height of editor lines, as a multiplier of font size
      • preferredLineLength: Identifies the length of a line (defaults to 80)
      • showInvisibles: Whether to render placeholders for invisible characters (defaults to false)
      • showIndentGuide: Show/hide indent indicators within the editor
      • showLineNumbers: Show/hide line numbers within the gutter
      • softWrap: Enable/disable soft wrapping of text within the editor
      • softWrapAtPreferredLineLength: Enable/disable soft line wrapping at preferredLineLength
      • tabLength: Number of spaces within a tab (defaults to 2)
    • fuzzyFinder
      • ignoredNames: Files to ignore only in the fuzzy-finder
    • whitespace
      • ensureSingleTrailingNewline: Whether to reduce multiple newlines to one at the end of files
      • removeTrailingWhitespace: Enable/disable stripping of whitespace at the end of lines (defaults to true)
    • wrap-guide
      • columns: Array of hashes with a pattern and column key to match the path of the current editor to a column position.

    Language Specific Configuration Settings

    You can also set several configuration settings differently for different file types. For example, you may want Pulsar to soft wrap markdown files, have two-space tabs for ruby files, and four-space tabs for python files.

    There are several settings now scoped to an editor's language. Here is the current list:

    editor.autoIndent
    +editor.autoIndentOnPaste
    +editor.invisibles
    +editor.nonWordCharacters
    +editor.preferredLineLength
    +editor.scrollPastEnd
    +editor.showIndentGuide
    +editor.showInvisibles
    +editor.softWrap
    +editor.softWrapAtPreferredLineLength
    +editor.softWrapHangingIndent
    +editor.tabLength
    +

    Language-specific Settings in the Settings View

    You can edit these config settings in the Settings View on a per-language basis. Click on "Packages" tab in the navigation bar on the left, search for the language of your choice, select it, and edit away!

    Python-specific settings

    Language-specific Settings in your Config File

    You can also edit the config.cson directly. To open your configuration file via the Command Palette, press LNX/WIN: Ctrl+Shift+P - MAC: Cmd+Shift+P type open config, and press Enter.

    Global settings are under the * key, and each language can have its own top-level key. This key is the language's scope. Language-specific settings take precedence over anything set in the global section for that language only.

    '*': # all languages unless overridden
    +  'editor':
    +    'softWrap': false
    +    'tabLength': 8
    +
    +'.source.gfm': # markdown overrides
    +  'editor':
    +    'softWrap': true
    +
    +'.source.ruby': # ruby overrides
    +  'editor':
    +    'tabLength': 2
    +
    +'.source.python': # python overrides
    +  'editor':
    +    'tabLength': 4
    +

    Finding a Language's Scope Name

    In order to write these overrides effectively, you'll need to know the scope name for the language. We've already done this for finding a scope for writing a snippet in Snippet Format, but we can quickly cover it again.

    The scope name is shown in the settings view for each language. Click on "Packages" in the navigation on the left, search for the language of your choice, select it, and you should see the scope name under the language name heading:

    Finding a language grammar

    Another way to find the scope for a specific language is to open a file of its kind and:

    • LNX/WIN: Choose "Editor: Log Cursor Scope" in the Command Palette
    • MAC: Press Alt+Cmd+P to show all scopes for the current position of the cursor. The scope mentioned top most is always the language for this kind of file, the scopes following are specific to the cursor position:

    Finding a language grammar with cursor scope

    These scopes can be especially useful to style the editor, since they can also be used as class names in your stylesheet.

    Customizing Language Recognition

    If you want Pulsar to always recognize certain file types as a specific grammar, you'll need to manually edit your config.cson file. You can open it using the Application: Open Your Config command from the Command Palette. For example, if you wanted to add the foo extension to the CoffeeScript language, you could add this to your configuration file under the *.core section:

    '*':
    +  core:
    +    customFileTypes:
    +      'source.coffee': [
    +        'foo'
    +      ]
    +

    In the example above, source.coffee is the language's scope name (see Finding a Language's Scope Name for more information) and foo is the file extension to match without the period. Adding a period to the beginning of either of these will not work.

    Controlling Where Customization is Stored to Simplify Your Workflow

    The CSON configuration files for Pulsar are stored on disk on your machine. The location for this storage is customizable. The default is to use the home directory of the user executing the application. The Pulsar Home directory will, by default, be called .pulsar and will be located in the root of the home directory of the user.

    Custom home location with an environment variable

    An environment variable can be used to make Pulsar use a different location. This can be useful for several reasons. One of these may be that multiple user accounts on a machine want to use the same Pulsar Home. The environment variable used to specify an alternate location is called ATOM_HOME. If this environment variable exists, the location specified will be used to load and store Pulsar settings.

    Taking your customization with you with Portable Mode

    In addition to using the ATOM_HOME environment variable, Pulsar can also be set to use "Portable Mode".

    Portable Mode is most useful for taking Pulsar with you, with all your custom settings and packages, from machine to machine. This may take the form of keeping Pulsar on a USB drive or a cloud storage platform that syncs folders to different machines, like Dropbox. Pulsar is in Portable Mode when there is a directory named .pulsar sibling to the directory in which the pulsar executable file lives. For example, the installed Pulsar directory can be placed into a Dropbox folder next to a .pulsar folder.

    Portable mode directory structure

    With such a setup, Pulsar will use the same Home directory with the same settings for any machine with this directory syncronized/plugged in.

    Moving to Portable Mode

    Pulsar provides a command-line parameter option for setting Portable Mode.

    $ pulsar --portable
    +

    Executing pulsar with the --portable option will take the .pulsar directory you have in the default location (~/.pulsar) and copy the relevant contents for your configuration to a new home directory in the Portable Mode location. This enables easily moving from the default location to a portable operation without losing the customization you have already set up.

    `,50);function T(I,N){const n=h("ExternalLinkIcon");return d(),u("div",null,[m,f,y,e("p",null,[s("All of Pulsar's config files (with the exception of your "),b,s(" and your "),v,s(" are written in CSON, short for "),e("a",k,[s("CoffeeScript Object Notation"),o(n)]),s(". Just like its namesake JSON, "),e("a",w,[s("JavaScript Object Notation"),o(n)]),s(", CSON is a text format for storing structured data in the form of simple objects made up of key-value pairs.")]),x,e("div",P,[C,e("p",null,[s("If you are unfamiliar with Less, it is a basic CSS preprocessor that makes some things in CSS a bit easier. You can learn more about it at "),e("a",_,[s("lesscss.org"),o(n)]),s(".")]),S]),L])}const A=p(g,[["render",T],["__file","basic-customization.html.vue"]]);export{A as default}; diff --git a/assets/basic-customization.html.d526d0ac.js b/assets/basic-customization.html.d526d0ac.js new file mode 100644 index 0000000000..9fbb8c66e8 --- /dev/null +++ b/assets/basic-customization.html.d526d0ac.js @@ -0,0 +1,71 @@ +import{_ as r}from"./menubar.df752f94.js";import{_ as l,a as c,b as p,c as d,d as u}from"./portable-mode-folder.90669a9a.js";import{_ as h,o as m,c as g,a as e,b as s,d as n,w as f,f as o,r as t}from"./app.0e1565ce.js";const y={},b=e("h3",{id:"basic-customization",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#basic-customization","aria-hidden":"true"},"#"),s(" Basic Customization")],-1),v=e("p",null,"Now that we are feeling comfortable with just about everything built into Atom, let's look at how to tweak it. Perhaps there is a keybinding that you use a lot but feels wrong or a color that isn't quite right for you. Atom is amazingly flexible, so let's go over some of the simpler flexes it can do.",-1),k=e("h4",{id:"configuring-with-cson",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#configuring-with-cson","aria-hidden":"true"},"#"),s(" Configuring with CSON")],-1),w=e("a",{href:"#style-tweaks"},"style sheet",-1),x=e("a",{href:"/hacking-atom/sections/the-init-file"},"Init Script",-1),_={href:"https://github.com/bevry/cson#what-is-cson",target:"_blank",rel:"noopener noreferrer"},S={href:"https://json.org/",target:"_blank",rel:"noopener noreferrer"},A=o(`
    key:
    +  key: value
    +  key: value
    +  key: [value, value]
    +

    Objects are the backbone of any CSON file, and are delineated by indentation (as in the above example). A key's value can either be a String, a Number, an Object, a Boolean, null, or an Array of any of these data types.

    Warning

    Just like the more common JSON, CSON's keys can only be repeated once per object. If there are duplicate keys, then the last usage of that key overwrites all others, as if they weren't there. The same holds true for Atom's config files.

    Don't do this:

    # Only the second snippet will be loaded
    +'.source.js':
    +  'console.log':
    +    'prefix': 'log'
    +    'body': 'console.log(\${1:"crash"});$2'
    +'.source.js':
    +  'console.error':
    +    'prefix': 'error'
    +    'body': 'console.error(\${1:"crash"});$2'
    +

    Use this instead:

    # Both snippets will be loaded
    +'.source.js':
    +  'console.log':
    +    'prefix': 'log'
    +    'body': 'console.log(\${1:"crash"});$2'
    +  'console.error':
    +    'prefix': 'error'
    +    'body': 'console.error(\${1:"crash"});$2'
    +

    Style Tweaks

    If you want to apply quick-and-dirty personal styling changes without creating an entire theme that you intend to publish, you can add styles to the styles.less file in your ~/.atom%USERPROFILE%\\.atom directory. You can open this file in an editor from the Atom > StylesheetFile > StylesheetEdit > Stylesheet menu.

    `,5),C=e("p",null,[e("img",{src:r,alt:"Stylesheet",title:"Stylesheet..."})],-1),T=o(`

    For example, to change the colors of the Status Bar, you could add the following rule to your styles.less file:

    .status-bar {
    +	color: white;
    +	background-color: black;
    +}
    +

    The easiest way to see what classes are available to style is to inspect the DOM manually via the Developer Tools. We'll go over the Developer Tools in great detail in the next chapter, but for now let's take a simple look. You can open the Developer Tools by pressing Alt+Cmd+ICtrl+Shift+I, which will bring up the Chromium Developer Tools panel.

    Developer Tools

    With the Developer Tools, you can inspect all the elements in Atom. If you want to update the style of something, you can figure out what classes it has and add a Less rule to your stylesheet to modify it.

    ',5),z={class:"custom-container tip"},I=e("p",{class:"custom-container-title"},"Tip",-1),L={href:"http://www.lesscss.org",target:"_blank",rel:"noopener noreferrer"},P=e("p",null,[s("If you prefer to use CSS instead, you can do that in the same "),e("code",null,"styles.less"),s(" file, since CSS is also valid in Less.")],-1),F=o(`

    Customizing Keybindings

    Atom keymaps work similarly to stylesheets. Just as stylesheets use selectors to apply styles to elements, Atom keymaps use selectors to associate key combinations with events in specific contexts. Here's a small example, excerpted from Atom's built-in keymap:

    'atom-text-editor':
    +  'enter': 'editor:newline'
    +
    +'atom-text-editor[mini] input':
    +  'enter': 'core:confirm'
    +

    This keymap defines the meaning of Enter in two different contexts. In a normal editor, pressing Enter triggers the editor:newline command, which causes the editor to insert a newline. But if the same keystroke occurs inside a select list's mini-editor, it instead triggers the core:confirm command based on the binding in the more-specific selector.

    By default, keymap.cson is loaded when Atom is started. It will always be loaded last, giving you the chance to override bindings that are defined by Atom's core keymaps or third-party packages. You can open this file in an editor from the Atom > KeymapFile > KeymapEdit > Keymap menu.

    You can see all the keybindings that are currently configured in your installation of Atom in the Keybindings tab in the Settings View.

    If you run into problems with keybindings, the Keybinding Resolver is a huge help. It can be opened with the Cmd+.Ctrl+. key combination. It will show you what keys Atom saw you press and what command Atom executed because of that combination.

    Global Configuration Settings

    Atom loads configuration settings from the config.cson file in your ~/.atom%USERPROFILE%\\.atom directory.

    '*':
    +  'core':
    +    'excludeVcsIgnoredPaths': true
    +  'editor':
    +    'fontSize': 18
    +

    The configuration is grouped into global settings under the * key and language-specific settings under scope named keys like .python.source or .html.text. Underneath that, you'll find configuration settings grouped by package name or one of the two core namespaces: core or editor.

    You can open this file in an editor from the Atom > ConfigFile > ConfigEdit > Config menu.

    Configuration Key Reference
    • core
      • customFileTypes: Associations of language scope to file extensions (see Customizing Language Recognition)
      • disabledPackages: An array of package names to disable
      • excludeVcsIgnoredPaths: Don't search within files specified by .gitignore
      • ignoredNames: File names to ignore across all of Atom
      • projectHome: The directory where projects are assumed to be located
      • themes: An array of theme names to load, in cascading order
    • editor
      • autoIndent: Enable/disable basic auto-indent (defaults to true)
      • nonWordCharacters: A string of non-word characters to define word boundaries
      • fontSize: The editor font size
      • fontFamily: The editor font family
      • invisibles: A hash of characters Atom will use to render whitespace characters. Keys are whitespace character types, values are rendered characters (use value false to turn off individual whitespace character types)
        • tab: Hard tab characters
        • cr: Carriage return (for Microsoft-style line endings)
        • eol: \\n characters
        • space: Leading and trailing space characters
      • lineHeight: Height of editor lines, as a multiplier of font size
      • preferredLineLength: Identifies the length of a line (defaults to 80)
      • showInvisibles: Whether to render placeholders for invisible characters (defaults to false)
      • showIndentGuide: Show/hide indent indicators within the editor
      • showLineNumbers: Show/hide line numbers within the gutter
      • softWrap: Enable/disable soft wrapping of text within the editor
      • softWrapAtPreferredLineLength: Enable/disable soft line wrapping at preferredLineLength
      • tabLength: Number of spaces within a tab (defaults to 2)
    • fuzzyFinder
      • ignoredNames: Files to ignore only in the fuzzy-finder
    • whitespace
      • ensureSingleTrailingNewline: Whether to reduce multiple newlines to one at the end of files
      • removeTrailingWhitespace: Enable/disable stripping of whitespace at the end of lines (defaults to true)
    • wrap-guide
      • columns: Array of hashes with a pattern and column key to match the path of the current editor to a column position.

    Language Specific Configuration Settings

    You can also set several configuration settings differently for different file types. For example, you may want Atom to soft wrap markdown files, have two-space tabs for ruby files, and four-space tabs for python files.

    There are several settings now scoped to an editor's language. Here is the current list:

    editor.autoIndent
    +editor.autoIndentOnPaste
    +editor.invisibles
    +editor.nonWordCharacters
    +editor.preferredLineLength
    +editor.scrollPastEnd
    +editor.showIndentGuide
    +editor.showInvisibles
    +editor.softWrap
    +editor.softWrapAtPreferredLineLength
    +editor.softWrapHangingIndent
    +editor.tabLength
    +
    Language-specific Settings in the Settings View

    You can edit these config settings in the Settings View on a per-language basis. Click on "Packages" tab in the navigation bar on the left, search for the language of your choice, select it, and edit away!

    Python-specific settings

    Language-specific Settings in your Config File

    You can also edit the config.cson directly. To open your configuration file via the Command Palette, press Cmd+Shift+PCtrl+Shift+P type open config, and press Enter.

    Global settings are under the * key, and each language can have its own top-level key. This key is the language's scope. Language-specific settings take precedence over anything set in the global section for that language only.

    '*': # all languages unless overridden
    +  'editor':
    +    'softWrap': false
    +    'tabLength': 8
    +
    +'.source.gfm': # markdown overrides
    +  'editor':
    +    'softWrap': true
    +
    +'.source.ruby': # ruby overrides
    +  'editor':
    +    'tabLength': 2
    +
    +'.source.python': # python overrides
    +  'editor':
    +    'tabLength': 4
    +
    Finding a Language's Scope Name

    In order to write these overrides effectively, you'll need to know the scope name for the language. We've already done this for finding a scope for writing a snippet in Snippet Format, but we can quickly cover it again.

    The scope name is shown in the settings view for each language. Click on "Packages" in the navigation on the left, search for the language of your choice, select it, and you should see the scope name under the language name heading:

    Finding a language grammar

    Another way to find the scope for a specific language is to open a file of its kind and press Alt+Cmd+Pchoose "Editor: Log Cursor Scope" in the Command Palette to show all scopes for the current position of the cursor. The scope mentioned top most is always the language for this kind of file, the scopes following are specific to the cursor position:

    Finding a language grammar with cursor scope

    These scopes can be especially useful to style the editor, since they can also be used as class names in your stylesheet.

    Customizing Language Recognition

    If you want Atom to always recognize certain file types as a specific grammar, you'll need to manually edit your config.cson file. You can open it using the Application: Open Your Config command from the Command Palette. For example, if you wanted to add the foo extension to the CoffeeScript language, you could add this to your configuration file under the *.core section:

    '*':
    +  core:
    +    customFileTypes:
    +      'source.coffee': [
    +        'foo'
    +      ]
    +

    In the example above, source.coffee is the language's scope name (see Finding a Language's Scope Name for more information) and foo is the file extension to match without the period. Adding a period to the beginning of either of these will not work.

    Controlling Where Customization is Stored to Simplify Your Workflow

    The CSON configuration files for Atom are stored on disk on your machine. The location for this storage is customizable. The default is to use the home directory of the user executing the application. The Atom Home directory will, by default, be called .atom and will be located in the root of the home directory of the user.

    Custom home location with an environment variable

    An environment variable can be used to make Atom use a different location. This can be useful for several reasons. One of these may be that multiple user accounts on a machine want to use the same Atom Home. The environment variable used to specify an alternate location is called ATOM_HOME. If this environment variable exists, the location specified will be used to load and store Atom settings.

    Taking your customization with you with Portable Mode

    In addition to using the ATOM_HOME environment variable, Atom can also be set to use "Portable Mode".

    Portable Mode is most useful for taking Atom with you, with all your custom setting and packages, from machine to machine. This may take the form of keeping Atom on a USB drive or a cloud storage platform that syncs folders to different machines, like Dropbox. Atom is in Portable Mode when there is a directory named .atom sibling to the directory in which the atom executable file lives. For example, the installed Atom directory can be placed into a Dropbox folder next to a .atom folder.

    Portable mode directory structure

    With such a setup, Atom will use the same Home directory with the same settings for any machine with this directory syncronized/plugged in.

    Moving to Portable Mode

    Atom provides a command-line parameter option for setting Portable Mode.

    $ atom --portable
    +

    Executing atom with the --portable option will take the .atom directory you have in the default location (~/.atom) and copy the relevant contents for your configuration to a new home directory in the Portable Mode location. This enables easily moving from the default location to a portable operation without losing the customization you have already set up.

    `,49);function N(O,E){const a=t("ExternalLinkIcon"),i=t("Tabs");return m(),g("div",null,[b,v,k,e("p",null,[s("All of Atom's config files (with the exception of your "),w,s(" and your "),x,s(") are written in CSON, short for "),e("a",_,[s("CoffeeScript Object Notation"),n(a)]),s(". Just like its namesake JSON, "),e("a",S,[s("JavaScript Object Notation"),n(a)]),s(", CSON is a text format for storing structured data in the form of simple objects made up of key-value pairs.")]),A,n(i,{id:"35",data:[{title:"Mac"}],"tab-id":"basic-customization"},{tab0:f(({title:q,value:W,isActive:M})=>[C]),_:1}),T,e("div",z,[I,e("p",null,[s("If you are unfamiliar with Less, it is a basic CSS preprocessor that makes some things in CSS a bit easier. You can learn more about it at "),e("a",L,[s("lesscss.org"),n(a)]),s(".")]),P]),F])}const Y=h(y,[["render",N],["__file","basic-customization.html.vue"]]);export{Y as default}; diff --git a/assets/basic-customization.html.ea54ccd8.js b/assets/basic-customization.html.ea54ccd8.js new file mode 100644 index 0000000000..257c548f7b --- /dev/null +++ b/assets/basic-customization.html.ea54ccd8.js @@ -0,0 +1 @@ +const i=JSON.parse('{"key":"v-61e2f142","path":"/docs/atom-archive/using-atom/sections/basic-customization.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"Basic Customization","slug":"basic-customization","link":"#basic-customization","children":[]}],"git":{"updatedTime":1664634443000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1},{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":7.47,"words":2240},"filePathRelative":"docs/atom-archive/using-atom/sections/basic-customization.md"}');export{i as data}; diff --git a/assets/basic-customization.html.f10a503f.js b/assets/basic-customization.html.f10a503f.js new file mode 100644 index 0000000000..828935eb08 --- /dev/null +++ b/assets/basic-customization.html.f10a503f.js @@ -0,0 +1 @@ +const i=JSON.parse('{"key":"v-55d9a5b4","path":"/docs/launch-manual/sections/using-pulsar/sections/basic-customization.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Basic Customization","slug":"basic-customization","link":"#basic-customization","children":[{"level":3,"title":"Configuring with CSON","slug":"configuring-with-cson","link":"#configuring-with-cson","children":[]},{"level":3,"title":"Style Tweaks","slug":"style-tweaks","link":"#style-tweaks","children":[]},{"level":3,"title":"Customizing Keybindings","slug":"customizing-keybindings","link":"#customizing-keybindings","children":[]},{"level":3,"title":"Global Configuration Settings","slug":"global-configuration-settings","link":"#global-configuration-settings","children":[]},{"level":3,"title":"Language Specific Configuration Settings","slug":"language-specific-configuration-settings","link":"#language-specific-configuration-settings","children":[]},{"level":3,"title":"Customizing Language Recognition","slug":"customizing-language-recognition","link":"#customizing-language-recognition","children":[]},{"level":3,"title":"Controlling Where Customization is Stored to Simplify Your Workflow","slug":"controlling-where-customization-is-stored-to-simplify-your-workflow","link":"#controlling-where-customization-is-stored-to-simplify-your-workflow","children":[]}]}],"git":{"updatedTime":1669229414000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":5}]},"readingTime":{"minutes":7.25,"words":2175},"filePathRelative":"docs/launch-manual/sections/using-pulsar/sections/basic-customization.md"}');export{i as data}; diff --git a/assets/blog-guide.html.01a5daf0.js b/assets/blog-guide.html.01a5daf0.js new file mode 100644 index 0000000000..2bb037ba72 --- /dev/null +++ b/assets/blog-guide.html.01a5daf0.js @@ -0,0 +1 @@ +import{_ as l,o as d,c as r,a as t,b as e,d as o,w as c,f as s,r as a}from"./app.0e1565ce.js";const h={},u=t("h2",{id:"blog-guide",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#blog-guide","aria-hidden":"true"},"#"),e(" Blog guide")],-1),g={href:"https://pulsar-edit.dev/article/",target:"_blank",rel:"noopener noreferrer"},p={href:"https://vuepress-theme-hope.github.io/v2/blog/",target:"_blank",rel:"noopener noreferrer"},m={href:"https://github.com/pulsar-edit/pulsar-edit.github.io",target:"_blank",rel:"noopener noreferrer"},b=t("h3",{id:"writing-a-new-post",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#writing-a-new-post","aria-hidden":"true"},"#"),e(" Writing a new post")],-1),f=s('
  • Create a new .md file in pulsar-edit.github.io/docs/blog.
    • This file should be named YYYYMMDD-<author>-<title>.md e.g 20221031-CreativeUsername-ThisIsMyBlogPost.md
  • The metadata displayed on the website is dependent on a number of items that are configured in the YAML frontmatter of the file. You may in theory omit any of these except the title field but it's strongly recommend that you use title, author, date, category and tag as the minimum as the others will default to false.
    • Frontmatter items supported currently are:
      • title - String: The displayed title of the post, consider this as H1
      • author - String: The name of the author to be displayed.
      • date - Date (ISO 8601): Allows display of date as well as enabling sorting on the timeline, set to the same as your filename date but with hyphens (e.g. 2022-10-31).
      • category - String (multiline): Enables filtering by category, this should be based on the subject of the post e.g. release, dev log, announcement. This is a multiline field if you want to set more than one category.
      • tag - String (multiline): Enables filtering by tags, this should be based on the content of the post and areas it touches on e.g. website, editor, config.
      • sticky - Bool: Enables "pinning" on the
      • star - Bool: Enables use of the star category for any important articles we want to remain visible.
      • article - Bool: You probably won't want to use this but setting this to false will exclude this page from appearing. This is set on the "example" blog post intentionally.
  • An excerpt can be added to the post by creating an html comment <!-- more -->. Anything above the comment will be treated as the excerpt and anything underneath will be the content of the post.
    • We are looking at implementing an auto excerpt feature but this doesn't seem to be working on the theme at the moment, this file will be updated if and when it is available.
  • ',3),w={href:"https://vuepress-theme-hope.github.io/v2/md-enhance/config.html#vueplayground",target:"_blank",rel:"noopener noreferrer"},y=t("li",null,[e("Images are supported with the standard image syntax ("),t("code",null,"![myImage](./assets/myImage.png)"),e(") and should be located in the "),t("code",null,"blog/assets"),e(" directory. You may also use standard html "),t("code",null,""),e(" tags, particularly if you wish to control the displayed size of the image.")],-1),_=t("li",null,"Create a PR to the repo and make it obvious it is a blog post, by including [BLOG] in the title of your PR. Please don't submit it alongside any website changes.",-1),v={href:"https://github.com/pulsar-edit/pulsar-edit.github.io/blob/main/docs/blog/20221112-Daeraxa-ExamplePost.md",target:"_blank",rel:"noopener noreferrer"},x=t("h3",{id:"testing-locally",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#testing-locally","aria-hidden":"true"},"#"),e(" Testing locally")],-1),T=s('

    Website "Blog" page(s)

    The website itself has a number of features which the aforementioned frontmatter fields can influence.

    There is a "selector" at the top of the site which currently reads All, Star, Slides, Encrypted. The only ones we use are the first two and currently the theme doesn't support configuration of this. If necessary we may be able to hide it with CSS.
    Any article with Star set to true will be shown in the star category.

    On the right bar are 4 counters/links and filters for Articles, Category, Tag and Timeline.
    Category and Tag are used to filter articles depending on the categories submitted in the respective frontmatter fields. The Timeline allows views of blog posts over time according to the dates set in the date frontmatter field.

    Questions? Suggestions

    Just ask in Discord or GH Discussions and ping the @documentation team.

    This is very much barebones default config so please let us know if you have any suggestions as to how we can improve it.

    ',7);function k(S,B){const i=a("ExternalLinkIcon"),n=a("RouterLink");return d(),r("div",null,[u,t("p",null,[e("This is a guide on how to add a blog post to the website which will be shown on "),t("a",g,[e("https://pulsar-edit.dev/article/"),o(i)]),e(".")]),t("p",null,[e("We are using the "),t("a",p,[e("Vuepress Blog Plugin"),o(i)]),e(" which comes as part of our Vuepress Hope Theme with some light configuration to suit our purposes.")]),t("p",null,[e("This is all implemented in the main "),t("a",m,[e("website repository"),o(i)]),e(".")]),b,t("ul",null,[f,t("li",null,[e("The rest of the post is just standard Markdown - we are currently only supporting the standard features as per the "),t("a",w,[e("MDEnhance"),o(i)]),e(" plugin but if we need more features such as GFM then please discuss and we can look at adding it to the website.")]),y,_]),t("p",null,[e("See "),t("a",v,[e("example post"),o(i)]),e(" with everything above.")]),x,t("p",null,[e("See "),o(n,{to:"/docs/resources/website/#building-the-website"},{default:c(()=>[e("building")]),_:1})]),T])}const E=l(h,[["render",k],["__file","blog-guide.html.vue"]]);export{E as default}; diff --git a/assets/blog-guide.html.8cda1ca4.js b/assets/blog-guide.html.8cda1ca4.js new file mode 100644 index 0000000000..e77bf958cd --- /dev/null +++ b/assets/blog-guide.html.8cda1ca4.js @@ -0,0 +1 @@ +const e=JSON.parse(`{"key":"v-c8081340","path":"/docs/resources/website/sections/blog-guide.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"

    # Blog guide

    \\n

    This is a guide on how to add a blog post to the website which will be shown on\\nhttps://pulsar-edit.dev/article/.

    \\n

    We are using the Vuepress Blog Plugin\\nwhich comes as part of our Vuepress Hope Theme with some light configuration\\nto suit our purposes.

    \\n

    This is all implemented in the main website repository.

    \\n

    # Writing a new post

    \\n
      \\n
    • Create a new .md file in pulsar-edit.github.io/docs/blog.\\n
        \\n
      • This file should be named YYYYMMDD-<author>-<title>.md e.g 20221031-CreativeUsername-ThisIsMyBlogPost.md
      • \\n
      \\n
    • \\n
    • The metadata displayed on the website is dependent on a number of items that\\nare configured in the YAML frontmatter of the file. You may in theory omit any of these except the title\\nfield but it's strongly recommend that you use title, author, date, category\\nand tag as the minimum as the others will default to false.\\n
        \\n
      • Frontmatter items supported currently are:\\n
          \\n
        • title - String: The displayed title of the post, consider this as H1
        • \\n
        • author - String: The name of the author to be displayed.
        • \\n
        • date - Date (ISO 8601): Allows display of date as well as enabling\\nsorting on the timeline, set to the same as your filename date but with\\nhyphens (e.g. 2022-10-31).
        • \\n
        • category - String (multiline): Enables filtering by category, this should be based on the\\nsubject of the post e.g. release, dev log, announcement. This is a\\nmultiline field if you want to set more than one category.
        • \\n
        • tag - String (multiline): Enables filtering by tags, this should be based on the content of\\nthe post and areas it touches on e.g. website, editor, config.
        • \\n
        • sticky - Bool: Enables "pinning" on the
        • \\n
        • star - Bool: Enables use of the star category for any important articles\\nwe want to remain visible.
        • \\n
        • article - Bool: You probably won't want to use this but setting this to false\\nwill exclude this page from appearing. This is set on the "example" blog\\npost intentionally.
        • \\n
        \\n
      • \\n
      \\n
    • \\n
    • An excerpt can be added to the post by creating an html comment \`
    • \\n
    \\n","headers":[{"level":2,"title":"Blog guide","slug":"blog-guide","link":"#blog-guide","children":[{"level":3,"title":"Writing a new post","slug":"writing-a-new-post","link":"#writing-a-new-post","children":[]},{"level":3,"title":"Testing locally","slug":"testing-locally","link":"#testing-locally","children":[]},{"level":3,"title":"Website \\"Blog\\" page(s)","slug":"website-blog-page-s","link":"#website-blog-page-s","children":[]},{"level":3,"title":"Questions? Suggestions","slug":"questions-suggestions","link":"#questions-suggestions","children":[]}]}],"git":{"updatedTime":1668606927000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":3}]},"readingTime":{"minutes":2.51,"words":754},"filePathRelative":"docs/resources/website/sections/blog-guide.md"}`);export{e as data}; diff --git a/assets/bookmarks.33aaf900.png b/assets/bookmarks.33aaf900.png new file mode 100644 index 0000000000..ffe7ce5bb2 Binary files /dev/null and b/assets/bookmarks.33aaf900.png differ diff --git a/assets/bookmarks.736c4e14.js b/assets/bookmarks.736c4e14.js new file mode 100644 index 0000000000..8fcfa1e474 --- /dev/null +++ b/assets/bookmarks.736c4e14.js @@ -0,0 +1 @@ +const s="/assets/bookmarks.33aaf900.png";export{s as _}; diff --git a/assets/bugsplat.ec5f7075.png b/assets/bugsplat.ec5f7075.png new file mode 100644 index 0000000000..efd99fcb3e Binary files /dev/null and b/assets/bugsplat.ec5f7075.png differ diff --git a/assets/building-pulsar.html.806a23cc.js b/assets/building-pulsar.html.806a23cc.js new file mode 100644 index 0000000000..0b82cc4d9c --- /dev/null +++ b/assets/building-pulsar.html.806a23cc.js @@ -0,0 +1,21 @@ +import{_ as u,o as p,c as b,a as e,b as n,d as a,w as i,f as c,r as d}from"./app.0e1565ce.js";const h={},m=e("h2",{id:"building-pulsar",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#building-pulsar","aria-hidden":"true"},"#"),n(" Building Pulsar")],-1),v=e("p",null,"If you want to investigate a bug, implement a new feature in Pulsar's core or just want to tinker then you will need to build and run Pulsar from source.",-1),g={href:"https://github.com/pulsar-edit/pulsar",target:"_blank",rel:"noopener noreferrer"},_=e("h3",{id:"requirements-and-dependencies",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#requirements-and-dependencies","aria-hidden":"true"},"#"),n(" Requirements and dependencies")],-1),f=e("p",null,"To build Pulsar you will need to meet some basic requirements:",-1),k={href:"https://github.com/pulsar-edit/pulsar/blob/master/.nvmrc",target:"_blank",rel:"noopener noreferrer"},y={href:"https://github.com/nvm-sh/nvm",target:"_blank",rel:"noopener noreferrer"},x=e("li",null,[n("yarn (enable with "),e("code",null,"corepack enable"),n(")")],-1),w=e("li",null,"Git",-1),P=e("li",null,"Python",-1),S=e("li",null,"C++ toolchain",-1),I={href:"https://wiki.gnome.org/Projects/Libsecret",target:"_blank",rel:"noopener noreferrer"},A=e("p",null,"For OS or distribution specific instructions see below:",-1),N=e("h4",{id:"ubuntu-debian",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#ubuntu-debian","aria-hidden":"true"},"#"),n(" Ubuntu/Debian")],-1),O=e("div",{class:"language-bash ext-sh line-numbers-mode"},[e("pre",{class:"language-bash"},[e("code",null,[e("span",{class:"token comment"},"# Install development packages"),n(` +`),e("span",{class:"token function"},"apt"),n(),e("span",{class:"token function"},"install"),n(` build-essential libxkbfile-dev libsecret-1-dev libx11-dev +`)])]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),T=e("h4",{id:"fedora-rhel",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#fedora-rhel","aria-hidden":"true"},"#"),n(" Fedora/RHEL")],-1),q=e("div",{class:"language-bash ext-sh line-numbers-mode"},[e("pre",{class:"language-bash"},[e("code",null,[e("span",{class:"token comment"},"# Install development packages"),n(` +dnf `),e("span",{class:"token parameter variable"},"--assumeyes"),n(),e("span",{class:"token function"},"install"),n(),e("span",{class:"token function"},"make"),n(` gcc gcc-c++ glibc-devel git-core libsecret-devel rpmdevtools libX11-devel libxkbfile-devel nss atk gdk-pixbuf2 gtk3 mesa-dri-drivers +`)])]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),L=e("h4",{id:"arch",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#arch","aria-hidden":"true"},"#"),n(" Arch")],-1),B=e("div",{class:"language-bash ext-sh line-numbers-mode"},[e("pre",{class:"language-bash"},[e("code",null,[e("span",{class:"token comment"},"# Install the development packges"),n(` +pacman `),e("span",{class:"token parameter variable"},"-S"),n(` base-devel libxkbfile libsecret libx11 libxcrypt-compat +`)])]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),j=e("h4",{id:"opensuse",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#opensuse","aria-hidden":"true"},"#"),n(" OpenSUSE")],-1),C=e("div",{class:"language-bash ext-sh line-numbers-mode"},[e("pre",{class:"language-bash"},[e("code",null,[e("span",{class:"token comment"},"# Install development packages"),n(` +`),e("span",{class:"token function"},"zypper"),n(),e("span",{class:"token keyword"},"in"),n(),e("span",{class:"token parameter variable"},"-t"),n(` pattern devel_basis +`),e("span",{class:"token function"},"zypper"),n(),e("span",{class:"token keyword"},"in"),n(` libX11-devel libxkbfile-devel libsecret-devel +`)])]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),U=e("div",{class:"language-bash ext-sh line-numbers-mode"},[e("pre",{class:"language-bash"},[e("code",null,`TODO +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"})])],-1),E={href:"https://visualstudio.microsoft.com/downloads/",target:"_blank",rel:"noopener noreferrer"},F=c(`

    Building and running the application

    To build the application so you can start hacking on the core you will need to download the source code to your local machine and cd to the pulsar directory:

    git clone https://github.com/pulsar-edit/pulsar.git && cd pulsar
    +
    `,3),M=e("code",null,"nvm",-1),V=e("code",null,"yarn",-1),z={href:"https://github.com/pulsar-edit/pulsar/blob/master/.nvmrc",target:"_blank",rel:"noopener noreferrer"},R=c(`
    nvm install
    +corepack enable
    +

    If Node.js is already installed, run the following to make sure the correct version of Node.js is being used (see requirements):

    nvm use
    +node -v
    +

    Run the following to initialize and update the submodules:

    git submodule init && git submodule update
    +

    Now install and build Pulsar & ppm:

    yarn install
    +yarn build
    +yarn build:apm
    +

    Start Pulsar!

    yarn start
    +

    These instructions will also build ppm (Pulsar Package Manager) but it will require some additional configuration for use.

    Building binaries

    The following will allow you to build Pulsar as a stand alone binary or installer. After running you will find your your built application in pulsar/binaries.

    The build script will automatically build for your system's CPU architecture, for example building on an x86_64 CPU will produce binaries for x86_64, building on arm64 will only produce binaries for arm64.

    It is not possible to "cross-build" for different OSs. For Linux binaries you must build from a Linux machine, macOS binaries must be built from macOS etc. Your OS is detected automatically and the script will build the correct binaries for it.

    `,14),D=e("p",null,[n("By default running "),e("code",null,"yarn dist"),n(" will attempt to create "),e("code",null,"appimage"),n(" (for most Linux distributions), "),e("code",null,"deb"),n(" (for Debian or Ubuntu based distributions) and "),e("code",null,"rpm"),n(" (for Red Hat or Fedora based distributions) binaries but you can select the actual target you want to build by appending the above targets to the command. e.g.:")],-1),H=e("ul",null,[e("li",null,[e("code",null,"yarn dist appimage")]),e("li",null,[e("code",null,"yarn dist deb")]),e("li",null,[e("code",null,"yarn dist rpm")]),e("li",null,[e("code",null,"yarn dist targz")])],-1),W=e("p",null,[e("code",null,"yarn dist"),n(" will create a "),e("code",null,"dmg"),n(" installer, there are currently no additional targets for macOS.")],-1),X=e("p",null,"As noted above this builds for your current CPU architecture. i.e. on an Intel Mac this will create Intel binaries, on Apple silicon (M1, M2 etc.) this will create Apple silicon binaries.",-1),G=e("p",null,[n("By default running "),e("code",null,"yarn dist"),n(" will attempt to create an "),e("code",null,"NSIS"),n(" installer as well as a "),e("code",null,"Portable"),n(" executable which does not require installation. If you only wish to build one then you can specify it by appending the above targets to the command e.g.:")],-1),Y=e("ul",null,[e("li",null,[e("code",null,"yarn dist nsis")]),e("li",null,[e("code",null,"yarn dist portable")])],-1);function J(K,Q){const s=d("ExternalLinkIcon"),r=d("Tabs");return p(),b("div",null,[m,v,e("p",null,[n("The Pulsar application code can be found in the "),e("a",g,[n("pulsar-edit/pulsar"),a(s)]),n(" repository.")]),_,f,e("ul",null,[e("li",null,[n("Node.js (version specified in "),e("a",k,[n("pulsar/.nvmrc"),a(s)]),n(", recommended installation is via "),e("a",y,[n("nvm"),a(s)]),n(")")]),x,w,P,S,e("li",null,[e("a",I,[n("Libsecret"),a(s)]),n(" development headers")])]),A,a(r,{id:"50",data:[{title:"Linux"},{title:"macOS"},{title:"Windows"}],"tab-id":"core-hacking"},{tab0:i(({title:l,value:t,isActive:o})=>[N,O,T,q,L,B,j,C]),tab1:i(({title:l,value:t,isActive:o})=>[U]),tab2:i(({title:l,value:t,isActive:o})=>[e("p",null,[n("Firstly install "),e("a",E,[n("Visual Studio"),a(s)]),n(" from Microsoft.")])]),_:1}),F,e("p",null,[n("Install Node.js (using "),M,n(" - see above) and enable corepack (for "),V,n("). This will install the version of Node.js specified in "),e("a",z,[n("pulsar/.nvmrc"),a(s)]),n(":")]),R,a(r,{id:"120",data:[{title:"Linux"},{title:"macOS"},{title:"Windows"}],"tab-id":"core-hacking"},{tab0:i(({title:l,value:t,isActive:o})=>[D,H]),tab1:i(({title:l,value:t,isActive:o})=>[W,X]),tab2:i(({title:l,value:t,isActive:o})=>[G,Y]),_:1})])}const $=u(h,[["render",J],["__file","building-pulsar.html.vue"]]);export{$ as default}; diff --git a/assets/building-pulsar.html.b0e5a0ea.js b/assets/building-pulsar.html.b0e5a0ea.js new file mode 100644 index 0000000000..59ce8ca150 --- /dev/null +++ b/assets/building-pulsar.html.b0e5a0ea.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-e880b502","path":"/docs/launch-manual/sections/core-hacking/sections/building-pulsar.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Building Pulsar","slug":"building-pulsar","link":"#building-pulsar","children":[{"level":3,"title":"Requirements and dependencies","slug":"requirements-and-dependencies","link":"#requirements-and-dependencies","children":[]},{"level":3,"title":"Building and running the application","slug":"building-and-running-the-application","link":"#building-and-running-the-application","children":[]},{"level":3,"title":"Building binaries","slug":"building-binaries","link":"#building-binaries","children":[]}]}],"git":{"updatedTime":1673050223000,"contributors":[{"name":"Andrew Smith","email":"92001731+CatPerson136@users.noreply.github.com","commits":9},{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":5}]},"readingTime":{"minutes":2.21,"words":662},"filePathRelative":"docs/launch-manual/sections/core-hacking/sections/building-pulsar.md"}');export{e as data}; diff --git a/assets/building.html.84c6f242.js b/assets/building.html.84c6f242.js new file mode 100644 index 0000000000..932b456c48 --- /dev/null +++ b/assets/building.html.84c6f242.js @@ -0,0 +1,6 @@ +import{_ as o,o as s,c as r,a as e,b as t,d as a,f as i,r as d}from"./app.0e1565ce.js";const l={},h=e("h2",{id:"building-the-website",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#building-the-website","aria-hidden":"true"},"#"),t(" Building the website")],-1),c=e("h3",{id:"prerequisites",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#prerequisites","aria-hidden":"true"},"#"),t(" Prerequisites")],-1),u={href:"https://github.com/nvm-sh/nvm",target:"_blank",rel:"noopener noreferrer"},p=e("li",null,"git",-1),b={href:"https://pnpm.io/",target:"_blank",rel:"noopener noreferrer"},m=e("code",null,"corepack enable",-1),f=e("h3",{id:"clone-the-repository-and-install",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#clone-the-repository-and-install","aria-hidden":"true"},"#"),t(" Clone the repository and install")],-1),g={href:"https://github.com/pulsar-edit/pulsar-edit.github.io",target:"_blank",rel:"noopener noreferrer"},v=i(`
    git clone https://github.com/pulsar-edit/pulsar-edit.github.io
    +
    +cd pulsar-edit.github.io
    +
    +pnpm install
    +

    Running the website

    Once installed there are a number of scripts available to help you develop and build the site. Just prefix each command with pnpm. e,g, pnpm dev.

    dev

    Starts a watch task that will rebuild VuePress whenever a change has been made to the included Markdown and JavaScript files. Additionally, it launches the development server to test the results in the browser.

    build

    Creates an optimized production build.

    format

    `,8),_={href:"https://prettier.io",target:"_blank",rel:"noopener noreferrer"},w=i('

    Note: This task will run automatically on every commit, so it can be ignored in most cases

    lint

    Lints all Markdown files in the repository.

    Note: This task will run automatically on every commit, so it can be ignored in most cases

    Notes

    Whilst dev does run a watch task, not everything will be updated and some changes will require you to shut down the server and start it again. For example adding @include files to another file will not rebuild automatically.

    If you wish to add new files from another repository via alias or @include then you will need to run pnpm update to get the latest version of the repository - the pnpm-lock.yml file will also be updated and must be part of any commit.

    ',7);function y(k,x){const n=d("ExternalLinkIcon");return s(),r("div",null,[h,c,e("ul",null,[e("li",null,[t("Node.js - at least version 16 (recommended installation via "),e("a",u,[t("nvm"),a(n)]),t(").")]),p,e("li",null,[e("a",b,[t("pnpm"),a(n)]),t(" - simply run "),m])]),f,e("p",null,[t("The website repository is "),e("a",g,[t("https://github.com/pulsar-edit/pulsar-edit.github.io"),a(n)]),t(". Other assets are stored on other repositories but these will be downloaded automatically.")]),v,e("p",null,[t("Runs "),e("a",_,[t("Prettier"),a(n)]),t(" over all Markdown files included in the repository to ensure consistent formatting.")]),w])}const V=o(l,[["render",y],["__file","building.html.vue"]]);export{V as default}; diff --git a/assets/building.html.d07905be.js b/assets/building.html.d07905be.js new file mode 100644 index 0000000000..581ad89b7e --- /dev/null +++ b/assets/building.html.d07905be.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-fa6566c6","path":"/docs/resources/website/sections/building.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Building the website","slug":"building-the-website","link":"#building-the-website","children":[{"level":3,"title":"Prerequisites","slug":"prerequisites","link":"#prerequisites","children":[]},{"level":3,"title":"Clone the repository and install","slug":"clone-the-repository-and-install","link":"#clone-the-repository-and-install","children":[]},{"level":3,"title":"Running the website","slug":"running-the-website","link":"#running-the-website","children":[]},{"level":3,"title":"Notes","slug":"notes","link":"#notes","children":[]}]}],"git":{"updatedTime":1668371341000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":2}]},"readingTime":{"minutes":0.97,"words":292},"filePathRelative":"docs/resources/website/sections/building.md"}');export{e as data}; diff --git a/assets/checklist.83a0eba5.png b/assets/checklist.83a0eba5.png new file mode 100644 index 0000000000..aeb55b6c8e Binary files /dev/null and b/assets/checklist.83a0eba5.png differ diff --git a/assets/choco-pulsar.4fa6b61c.png b/assets/choco-pulsar.4fa6b61c.png new file mode 100644 index 0000000000..ef99345937 Binary files /dev/null and b/assets/choco-pulsar.4fa6b61c.png differ diff --git a/assets/choco-versions.c7569503.png b/assets/choco-versions.c7569503.png new file mode 100644 index 0000000000..5b5a2abf29 Binary files /dev/null and b/assets/choco-versions.c7569503.png differ diff --git a/assets/chocolatey.6923f762.js b/assets/chocolatey.6923f762.js new file mode 100644 index 0000000000..314cd9cf52 --- /dev/null +++ b/assets/chocolatey.6923f762.js @@ -0,0 +1 @@ +const s="/assets/chocolatey.c690a6ba.png";export{s as _}; diff --git a/assets/chocolatey.c690a6ba.png b/assets/chocolatey.c690a6ba.png new file mode 100644 index 0000000000..00bb825ed5 Binary files /dev/null and b/assets/chocolatey.c690a6ba.png differ diff --git a/assets/cleaning.f5279602.png b/assets/cleaning.f5279602.png new file mode 100644 index 0000000000..6f73ff4178 Binary files /dev/null and b/assets/cleaning.f5279602.png differ diff --git a/assets/command-palette.6ea4da90.png b/assets/command-palette.6ea4da90.png new file mode 100644 index 0000000000..cfc8e62446 Binary files /dev/null and b/assets/command-palette.6ea4da90.png differ diff --git a/assets/common-issues.html.95d1715c.js b/assets/common-issues.html.95d1715c.js new file mode 100644 index 0000000000..0b1ee46370 --- /dev/null +++ b/assets/common-issues.html.95d1715c.js @@ -0,0 +1 @@ +import{_ as r,o as d,c as i,a as o,b as e,d as n,w as c,r as s}from"./app.0e1565ce.js";const l={},u=o("h2",{id:"common-issues",tabindex:"-1"},[o("a",{class:"header-anchor",href:"#common-issues","aria-hidden":"true"},"#"),e(" Common Issues")],-1),h=o("h3",{id:"macos-error-app-is-damaged-and-can-t-be-opened",tabindex:"-1"},[o("a",{class:"header-anchor",href:"#macos-error-app-is-damaged-and-can-t-be-opened","aria-hidden":"true"},"#"),e(' macOS error "'),o("em",null,"App is damaged and can\u2019t be opened"),e('"')],-1),p=o("code",null,"xattr -cr /Applications/Pulsar.app/",-1),m={href:"https://appletoolbox.com/app-is-damaged-cannot-be-opened-mac/",target:"_blank",rel:"noopener noreferrer"},_=o("h3",{id:"pulsar-does-not-launch-gpu-process-isn-t-usable-goodbye",tabindex:"-1"},[o("a",{class:"header-anchor",href:"#pulsar-does-not-launch-gpu-process-isn-t-usable-goodbye","aria-hidden":"true"},"#"),e(' Pulsar does not launch "'),o("em",null,"GPU process isn't usable. Goodbye"),e('"')],-1),b=o("code",null,"--no-sandbox",-1),g={href:"https://github.com/pulsar-edit/pulsar/issues/174",target:"_blank",rel:"noopener noreferrer"};function f(x,y){const t=s("RouterLink"),a=s("ExternalLinkIcon");return d(),i("div",null,[u,h,o("p",null,[e("The binary is likely from before macOS binaries started being signed. A up-to-date and signed binary may be downloaded from "),n(t,{to:"/download.html"},{default:c(()=>[e("the download page")]),_:1}),e(". The unsigned binary can be made to run by running "),p,e(" in the terminal. See "),o("a",m,[e("here"),n(a)]),e(" for more information.")]),_,o("p",null,[e("You may need to launch the application with the argument "),b,e(" to get around this issue. This is something "),o("a",g,[e("under investigation"),n(a)]),e(".")])])}const w=r(l,[["render",f],["__file","common-issues.html.vue"]]);export{w as default}; diff --git a/assets/common-issues.html.dfde4f04.js b/assets/common-issues.html.dfde4f04.js new file mode 100644 index 0000000000..8738817db0 --- /dev/null +++ b/assets/common-issues.html.dfde4f04.js @@ -0,0 +1 @@ +const e=JSON.parse(`{"key":"v-76c0c524","path":"/docs/launch-manual/sections/faq/sections/common-issues.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Common Issues","slug":"common-issues","link":"#common-issues","children":[{"level":3,"title":"macOS error \\"App is damaged and can\u2019t be opened\\"","slug":"macos-error-app-is-damaged-and-can-t-be-opened","link":"#macos-error-app-is-damaged-and-can-t-be-opened","children":[]},{"level":3,"title":"Pulsar does not launch \\"GPU process isn't usable. Goodbye\\"","slug":"pulsar-does-not-launch-gpu-process-isn-t-usable-goodbye","link":"#pulsar-does-not-launch-gpu-process-isn-t-usable-goodbye","children":[]}]}],"git":{"updatedTime":1676533339000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":3},{"name":"Meadowsys","email":"blazeykirin@gmail.com","commits":1}]},"readingTime":{"minutes":0.37,"words":110},"filePathRelative":"docs/launch-manual/sections/faq/sections/common-issues.md"}`);export{e as data}; diff --git a/assets/community.aacaf0a9.png b/assets/community.aacaf0a9.png new file mode 100644 index 0000000000..f3416640b2 Binary files /dev/null and b/assets/community.aacaf0a9.png differ diff --git a/assets/community.html.1cb2fcef.js b/assets/community.html.1cb2fcef.js new file mode 100644 index 0000000000..9a8809e084 --- /dev/null +++ b/assets/community.html.1cb2fcef.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-67f865c9","path":"/community.html","title":"Community areas","lang":"en-US","frontmatter":{"title":"Community areas","path":"/community/","sitemap":{"priority":0.9,"changefreq":"weekly"}},"excerpt":"","headers":[],"git":{"updatedTime":1694883547000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":4},{"name":"confused-Techie","email":"dev@lhbasics.com","commits":1}]},"readingTime":{"minutes":0.32,"words":97},"filePathRelative":"community.md"}');export{e as data}; diff --git a/assets/community.html.4ddab81c.js b/assets/community.html.4ddab81c.js new file mode 100644 index 0000000000..046672cb3e --- /dev/null +++ b/assets/community.html.4ddab81c.js @@ -0,0 +1 @@ +import{_ as o,o as r,c as l,a as e,b as s,d as n,r as a}from"./app.0e1565ce.js";const i={},c=e("p",null,"Here you will find a number of community links to discuss Pulsar, get help, suggest features and enhancements and otherwise interact with the team and community.",-1),d={href:"https://github.com/orgs/pulsar-edit/discussions",target:"_blank",rel:"noopener noreferrer"},_=e("i",{class:"fa-solid fa-comments"},null,-1),u={href:"https://discord.gg/7aEbB9dGRT",target:"_blank",rel:"noopener noreferrer"},f=e("i",{class:"fa-brands fa-discord"},null,-1),h={href:"https://www.reddit.com/r/pulsaredit/",target:"_blank",rel:"noopener noreferrer"},m=e("i",{class:"fa-brands fa-reddit"},null,-1),p={href:"https://fosstodon.org/@pulsaredit",target:"_blank",rel:"noopener noreferrer"},b=e("i",{class:"fa-brands fa-mastodon"},null,-1),g={href:"https://lemmy.ml/c/pulsaredit",target:"_blank",rel:"noopener noreferrer"},k=e("i",{class:"fa-solid fa-users"},null,-1);function y(w,x){const t=a("ExternalLinkIcon");return r(),l("div",null,[c,e("ul",null,[e("li",null,[e("a",d,[_,s(" - GitHub Discussions"),n(t)])]),e("li",null,[e("a",u,[f,s(" - Discord"),n(t)])]),e("li",null,[e("a",h,[m,s(" - Reddit"),n(t)])]),e("li",null,[e("a",p,[b,s(" - Mastodon"),n(t)])]),e("li",null,[e("a",g,[k,s(" - Lemmy"),n(t)])])])])}const E=o(i,[["render",y],["__file","community.html.vue"]]);export{E as default}; diff --git a/assets/computer.f3068a14.png b/assets/computer.f3068a14.png new file mode 100644 index 0000000000..224afda4b4 Binary files /dev/null and b/assets/computer.f3068a14.png differ diff --git a/assets/configuration-api.html.1ed2295a.js b/assets/configuration-api.html.1ed2295a.js new file mode 100644 index 0000000000..4622e37b7e --- /dev/null +++ b/assets/configuration-api.html.1ed2295a.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-6ff500c4","path":"/docs/atom-archive/behind-atom/sections/configuration-api.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"Configuration API","slug":"configuration-api","link":"#configuration-api","children":[]}],"git":{"updatedTime":1668309800000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":3},{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":0.98,"words":294},"filePathRelative":"docs/atom-archive/behind-atom/sections/configuration-api.md"}');export{e as data}; diff --git a/assets/configuration-api.html.688150f0.js b/assets/configuration-api.html.688150f0.js new file mode 100644 index 0000000000..1baa59c4f0 --- /dev/null +++ b/assets/configuration-api.html.688150f0.js @@ -0,0 +1,21 @@ +import{_ as i,o as c,c as p,a as n,b as s,d as e,e as t,f as o,r as l}from"./app.0e1565ce.js";const u={},r=o(`

    Configuration API

    Reading Config Settings

    If you are writing a package that you want to make configurable, you'll need to read config settings via the atom.config global. You can read the current value of a namespaced config key with atom.config.get:

    // read a value with \`config.get\`
    +if (atom.config.get("editor.showInvisibles")) {
    +	this.showInvisibles();
    +}
    +

    Or you can subscribe via atom.config.observe to track changes from any view object.

    const {View} = require('space-pen')
    +
    +class MyView extends View {
    +  function attached() {
    +    this.fontSizeObserveSubscription =
    +      atom.config.observe('editor.fontSize', (newValue, {previous}) => {
    +        this.adjustFontSize(newValue)
    +      })
    +  }
    +
    +  function detached() {
    +    this.fontSizeObserveSubscription.dispose()
    +  }
    +}
    +

    The atom.config.observe method will call the given callback immediately with the current value for the specified key path, and it will also call it in the future whenever the value of that key path changes. If you only want to invoke the callback the next time the value changes, use atom.config.onDidChange instead.

    `,7),d={href:"https://atom.io/docs/api/latest/Disposable",target:"_blank",rel:"noopener noreferrer"},k=n("code",null,"Disposable",-1),h=n("code",null,"@fontSizeObserveSubscription",-1),m={href:"https://atom.io/docs/api/latest/CompositeDisposable",target:"_blank",rel:"noopener noreferrer"},v=n("code",null,"CompositeDisposable",-1),g=o(`

    Writing Config Settings

    The atom.config database is populated on startup from LNX/MAC: ~/.pulsar/config.cson - WIN: %USERPROFILE%\\.pulsar\\config.cson but you can programmatically write to it with atom.config.set:

    // basic key update
    +atom.config.set("core.showInvisibles", true);
    +
    `,3),f={href:"https://atom.io/docs/api/latest/Config",target:"_blank",rel:"noopener noreferrer"};function b(w,y){const a=l("ExternalLinkIcon");return c(),p("div",null,[r,n("p",null,[s("Subscription methods return "),n("a",d,[k,e(a)]),t("TODO: There is no Pulsar API documented yet so keeping link to Atom until we have this"),s(" objects that can be used to unsubscribe. Note in the example above how we save the subscription to the "),h,s(" instance variable and dispose of it when the view is detached. To group multiple subscriptions together, you can add them all to a "),n("a",m,[v,e(a)]),s(" that you dispose when the view is detached.")]),g,n("p",null,[s("If you're exposing package configuration via specific key paths, you'll want to associate them with a schema in your package's main module. Read more about schemas in the "),n("a",f,[s("Config API documentation"),e(a)]),s(". "),t("TODO: There is no Pulsar API documented yet so keeping link to Atom until we have this")])])}const x=i(u,[["render",b],["__file","configuration-api.html.vue"]]);export{x as default}; diff --git a/assets/configuration-api.html.d4ff21e0.js b/assets/configuration-api.html.d4ff21e0.js new file mode 100644 index 0000000000..926b461f08 --- /dev/null +++ b/assets/configuration-api.html.d4ff21e0.js @@ -0,0 +1 @@ +const i=JSON.parse('{"key":"v-4861ba81","path":"/docs/launch-manual/sections/behind-pulsar/sections/configuration-api.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Configuration API","slug":"configuration-api","link":"#configuration-api","children":[{"level":3,"title":"Reading Config Settings","slug":"reading-config-settings","link":"#reading-config-settings","children":[]},{"level":3,"title":"Writing Config Settings","slug":"writing-config-settings","link":"#writing-config-settings","children":[]}]}],"git":{"updatedTime":1668711072000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":1.06,"words":318},"filePathRelative":"docs/launch-manual/sections/behind-pulsar/sections/configuration-api.md"}');export{i as data}; diff --git a/assets/configuration-api.html.e432c45d.js b/assets/configuration-api.html.e432c45d.js new file mode 100644 index 0000000000..6e9c068d7b --- /dev/null +++ b/assets/configuration-api.html.e432c45d.js @@ -0,0 +1,21 @@ +import{_ as o,o as i,c,a as n,b as s,d as e,f as t,r as p}from"./app.0e1565ce.js";const l={},u=t(`

    Configuration API

    Reading Config Settings

    If you are writing a package that you want to make configurable, you'll need to read config settings via the atom.config global. You can read the current value of a namespaced config key with atom.config.get:

    // read a value with \`config.get\`
    +if (atom.config.get("editor.showInvisibles")) {
    +	this.showInvisibles();
    +}
    +

    Or you can subscribe via atom.config.observe to track changes from any view object.

    const {View} = require('space-pen')
    +
    +class MyView extends View {
    +  function attached() {
    +    this.fontSizeObserveSubscription =
    +      atom.config.observe('editor.fontSize', (newValue, {previous}) => {
    +        this.adjustFontSize(newValue)
    +      })
    +  }
    +
    +  function detached() {
    +    this.fontSizeObserveSubscription.dispose()
    +  }
    +}
    +

    The atom.config.observe method will call the given callback immediately with the current value for the specified key path, and it will also call it in the future whenever the value of that key path changes. If you only want to invoke the callback the next time the value changes, use atom.config.onDidChange instead.

    `,7),r={href:"https://atom.io/docs/api/latest/Disposable",target:"_blank",rel:"noopener noreferrer"},d=n("code",null,"Disposable",-1),k=n("code",null,"@fontSizeObserveSubscription",-1),h={href:"https://atom.io/docs/api/latest/CompositeDisposable",target:"_blank",rel:"noopener noreferrer"},v=n("code",null,"CompositeDisposable",-1),m=t(`

    Writing Config Settings

    The atom.config database is populated on startup from ~/.atom/config.cson%USERPROFILE%\\.atom\\config.cson, but you can programmatically write to it with atom.config.set:

    // basic key update
    +atom.config.set("core.showInvisibles", true);
    +
    `,3),g={href:"https://atom.io/docs/api/latest/Config",target:"_blank",rel:"noopener noreferrer"};function f(b,w){const a=p("ExternalLinkIcon");return i(),c("div",null,[u,n("p",null,[s("Subscription methods return "),n("a",r,[d,e(a)]),s(" objects that can be used to unsubscribe. Note in the example above how we save the subscription to the "),k,s(" instance variable and dispose of it when the view is detached. To group multiple subscriptions together, you can add them all to a "),n("a",h,[v,e(a)]),s(" that you dispose when the view is detached.")]),m,n("p",null,[s("If you're exposing package configuration via specific key paths, you'll want to associate them with a schema in your package's main module. Read more about schemas in the "),n("a",g,[s("Config API documentation"),e(a)]),s(".")])])}const y=o(l,[["render",f],["__file","configuration-api.html.vue"]]);export{y as default}; diff --git a/assets/configuration-files.html.cb664208.js b/assets/configuration-files.html.cb664208.js new file mode 100644 index 0000000000..e97174fff0 --- /dev/null +++ b/assets/configuration-files.html.cb664208.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-c2b13dfe","path":"/docs/resources/website/sections/configuration-files.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Configuration files","slug":"configuration-files","link":"#configuration-files","children":[{"level":3,"title":"config.js","slug":"config-js","link":"#config-js","children":[]},{"level":3,"title":"navbar.js","slug":"navbar-js","link":"#navbar-js","children":[]},{"level":3,"title":"sidebar.js","slug":"sidebar-js","link":"#sidebar-js","children":[]},{"level":3,"title":"Theme","slug":"theme","link":"#theme","children":[]}]}],"git":{"updatedTime":1668371349000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":2}]},"readingTime":{"minutes":2.12,"words":635},"filePathRelative":"docs/resources/website/sections/configuration-files.md"}');export{e as data}; diff --git a/assets/configuration-files.html.d70a7efb.js b/assets/configuration-files.html.d70a7efb.js new file mode 100644 index 0000000000..ac5256b800 --- /dev/null +++ b/assets/configuration-files.html.d70a7efb.js @@ -0,0 +1 @@ +import{_ as s,o as i,c as l,a as e,b as t,d as n,r}from"./app.0e1565ce.js";const a={},h=e("h2",{id:"configuration-files",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#configuration-files","aria-hidden":"true"},"#"),t(" Configuration files")],-1),c=e("p",null,[t("Nearly everything regarding the configuration of the website itself is controlled via the files found in the "),e("code",null,"/docs/.vuepress"),t(" directory.")],-1),d=e("p",null,"Currently we have three main configuration files.",-1),u=e("h3",{id:"config-js",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#config-js","aria-hidden":"true"},"#"),t(),e("code",null,"config.js")],-1),f=e("p",null,"This is the main configuration file for the website. This controls everything from the available settings, additional VuePress plugins, website description and various other elements to control various settings and plugins.",-1),_={href:"https://v2.vuepress.vuejs.org/reference/config.html",target:"_blank",rel:"noopener noreferrer"},p={href:"https://vuepress-theme-hope.github.io/v2/config/",target:"_blank",rel:"noopener noreferrer"},b=e("p",null,[t("This file is broken down to to keep it tidy, the below files are imported to "),e("code",null,"config.js"),t(" to extend the configuration file without making it unwieldy.")],-1),m=e("h3",{id:"navbar-js",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#navbar-js","aria-hidden":"true"},"#"),t(),e("code",null,"navbar.js")],-1),g=e("p",null,"This controls the layout for the links in the top middle of the page and is always displayed.",-1),v=e("p",null,"Items that go here are ones that we always want to be shown and should always be available for quick navigation.",-1),w=e("p",null,"Each object can have a number of different values. The main ones we use are:",-1),y=e("li",null,[e("code",null,"text"),t(": This sets the text for the label.")],-1),k=e("code",null,"icon",-1),T={href:"https://fontawesome.com/",target:"_blank",rel:"noopener noreferrer"},x=e("code",null,"fa-",-1),j={href:"https://fontawesome.com/icons/house?s=solid&f=classic",target:"_blank",rel:"noopener noreferrer"},V=e("code",null,"solid fa-house",-1),C=e("li",null,[e("code",null,"link"),t(": This controls where the link will actually take you. This can be a relative reference internal to the website or can be a URL to an external site.")],-1),F=e("li",null,[e("code",null,"children"),t(": Allows you to specify an array of child objects which will appear as a dropdown on mouseover. Use of this disables the "),e("code",null,"link"),t(" value. Each child can be defined as a full object as described here or can simply be a relative link from which the text will be set by the YAML title.")],-1),L={href:"https://v2.vuepress.vuejs.org/reference/default-theme/config.html#navbar",target:"_blank",rel:"noopener noreferrer"},A={href:"https://vuepress-theme-hope.github.io/v2/config/theme/layout.html#navbar-config",target:"_blank",rel:"noopener noreferrer"},E=e("h3",{id:"sidebar-js",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#sidebar-js","aria-hidden":"true"},"#"),t(),e("code",null,"sidebar.js")],-1),I=e("p",null,[t("This control what is displayed in the sidebar on the left of the website. It is not displayed globally, only on directories which are set within the sidebar, currently we only have "),e("code",null,"docs"),t(" configured.")],-1),N=e("p",null,[t("Like "),e("code",null,"navbar.js"),t(" each sidebare item is configured as an object with a number of different values.")],-1),H=e("li",null,[e("code",null,"text"),t(": This sets the text for the label.")],-1),P=e("li",null,[e("code",null,"link"),t(": Controls the relative link for navigating the documents within the section")],-1),U=e("code",null,"icon",-1),B={href:"https://fontawesome.com/",target:"_blank",rel:"noopener noreferrer"},M=e("code",null,"fa-",-1),O={href:"https://fontawesome.com/icons/house?s=solid&f=classic",target:"_blank",rel:"noopener noreferrer"},Y=e("code",null,"solid fa-house",-1),q=e("li",null,[e("code",null,"prefix"),t(": This adds a file path prefix to the item so its children do not need to specify the full path.")],-1),D=e("code",null,"collapsable",-1),R=e("em",null,"Note",-1),W=e("code",null,"collapsible",-1),z={href:"https://github.com/vuepress-theme-hope/vuepress-theme-hope/issues/2377",target:"_blank",rel:"noopener noreferrer"},G=e("li",null,[e("code",null,"children"),t(": Takes an array of objects configured as above. Can also be set as a simple relative link in which case the title will be the YAML title of the document it links to.")],-1),J={href:"https://v2.vuepress.vuejs.org/reference/default-theme/config.html#sidebar",target:"_blank",rel:"noopener noreferrer"},K={href:"https://vuepress-theme-hope.github.io/v2/config/theme/layout.html#sidebar-config",target:"_blank",rel:"noopener noreferrer"},Q=e("h3",{id:"theme",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#theme","aria-hidden":"true"},"#"),t(" Theme")],-1),S=e("p",null,[t("Within the "),e("code",null,"styles/"),t(" directory you will find the .scss file for controlling various aspects of the website's theme.")],-1),X=e("p",null,[e("strong",null,[e("em",null,"TODO")]),t(": This will be updated when we actually modify these files extensively.")],-1);function Z($,ee){const o=r("ExternalLinkIcon");return i(),l("div",null,[h,c,d,u,f,e("p",null,[t("For a full reference you can look at the documentation for "),e("a",_,[t("VuePress"),n(o)]),t(" and the "),e("a",p,[t("Hope Theme"),n(o)]),t(".")]),b,m,g,v,w,e("ul",null,[y,e("li",null,[k,t(": Used to prefix an icon to the item. The theme supports the free "),e("a",T,[t("FontAwesome"),n(o)]),t(" font natively. To add an icon you need to specify its name without the first "),x,t(" e.g. "),e("a",j,[t("fa-house"),n(o)]),t(" would be specified as "),V,t(".")]),C,F]),e("p",null,[t("For a full reference you can look at the documentation for "),e("a",L,[t("VuePress"),n(o)]),t(" and the "),e("a",A,[t("Hope Theme"),n(o)]),t(".")]),E,I,N,e("ul",null,[H,P,e("li",null,[U,t(": Used to prefix an icon to the item. The theme supports the free "),e("a",B,[t("FontAwesome"),n(o)]),t(" font natively. To add an icon you need to specify its name without the first "),M,t(" e.g. "),e("a",O,[t("fa-house"),n(o)]),t(" would be specified as "),Y,t(".")]),q,e("li",null,[D,t(": (sic) Controls whether the item can be collapsed. "),R,t(": This a breaking change on a future version of the Hope Theme so will need to be renamed "),W,t(" when updated, see: "),e("a",z,[t("Issue"),n(o)]),t(".")]),G]),e("p",null,[t("For a full reference you can look at the documentation for "),e("a",J,[t("VuePress"),n(o)]),t(" and the "),e("a",K,[t("Hope Theme"),n(o)]),t(".")]),Q,S,X])}const oe=s(a,[["render",Z],["__file","configuration-files.html.vue"]]);export{oe as default}; diff --git a/assets/context-menu.3bf0cb3c.png b/assets/context-menu.3bf0cb3c.png new file mode 100644 index 0000000000..ab459a5866 Binary files /dev/null and b/assets/context-menu.3bf0cb3c.png differ diff --git a/assets/contributing-to-official-atom-packages.html.961e0ab9.js b/assets/contributing-to-official-atom-packages.html.961e0ab9.js new file mode 100644 index 0000000000..a487dea5a4 --- /dev/null +++ b/assets/contributing-to-official-atom-packages.html.961e0ab9.js @@ -0,0 +1 @@ +const t=JSON.parse('{"key":"v-00ff85c6","path":"/docs/atom-archive/hacking-atom/sections/contributing-to-official-atom-packages.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"Contributing to Official Atom Packages","slug":"contributing-to-official-atom-packages","link":"#contributing-to-official-atom-packages","children":[]}],"git":{"updatedTime":1664050274000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":2}]},"readingTime":{"minutes":1.24,"words":372},"filePathRelative":"docs/atom-archive/hacking-atom/sections/contributing-to-official-atom-packages.md"}');export{t as data}; diff --git a/assets/contributing-to-official-atom-packages.html.d63ab9ab.js b/assets/contributing-to-official-atom-packages.html.d63ab9ab.js new file mode 100644 index 0000000000..a42e4056de --- /dev/null +++ b/assets/contributing-to-official-atom-packages.html.d63ab9ab.js @@ -0,0 +1,6 @@ +import{_ as d,o as c,c as s,a as n,b as e,d as o,w as r,f as l,r as a}from"./app.0e1565ce.js";const m={},u=n("h3",{id:"contributing-to-official-atom-packages",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#contributing-to-official-atom-packages","aria-hidden":"true"},"#"),e(" Contributing to Official Atom Packages")],-1),p={href:"https://github.com/atom/atom",target:"_blank",rel:"noopener noreferrer"},h=n("h4",{id:"hacking-on-packages",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#hacking-on-packages","aria-hidden":"true"},"#"),e(" Hacking on Packages")],-1),g=n("h5",{id:"cloning",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#cloning","aria-hidden":"true"},"#"),e(" Cloning")],-1),v=n("code",null,"apm install",-1),f=l(`

    For example, if you want to make changes to the tree-view package, fork the repo on your github account, then clone it:

    $ git clone git@github.com:<em>your-username</em>/tree-view.git
    +

    Next install all the dependencies:

    $ cd tree-view
    +$ apm install
    +> Installing modules \u2713
    +

    Now you can link it to development mode so when you run an Atom window with atom --dev, you will use your fork instead of the built in package:

    $ apm link -d
    +
    Running in Development Mode

    Editing a package in Atom is a bit of a circular experience: you're using Atom to modify itself. What happens if you temporarily break something? You don't want the version of Atom you're using to edit to become useless in the process. For this reason, you'll only want to load packages in development mode while you are working on them. You'll perform your editing in stable mode, only switching to development mode to test your changes.

    To open a development mode window, use the "Application: Open Dev" command. You can also run dev mode from the command line with atom --dev.

    To load your package in development mode, create a symlink to it in ~/.atom/dev/packages. This occurs automatically when you clone the package with apm develop. You can also run apm link --dev and apm unlink --dev from the package directory to create and remove dev-mode symlinks.

    Installing Dependencies

    You'll want to keep dependencies up to date by running apm update after pulling any upstream changes.

    `,12);function k(b,y){const i=a("ExternalLinkIcon"),t=a("RouterLink");return c(),s("div",null,[u,n("p",null,[e("If you think you know which package is causing the issue you are reporting, feel free to open up the issue in that specific repository instead. When in doubt just open the issue on the "),n("a",p,[e("atom/atom"),o(i)]),e(" repository but be aware that it may get closed and reopened in the proper package's repository.")]),h,g,n("p",null,[e("The first step is creating your own clone. For some packages, you may also need to install the "),o(t,{to:"/hacking-atom/sections/hacking-on-atom-core/#building"},{default:r(()=>[e("requirements necessary for building Atom")]),_:1}),e(" in order to run "),v,e(".")]),f])}const w=d(m,[["render",k],["__file","contributing-to-official-atom-packages.html.vue"]]);export{w as default}; diff --git a/assets/contributing-to-official-pulsar-packages.html.26ef0088.js b/assets/contributing-to-official-pulsar-packages.html.26ef0088.js new file mode 100644 index 0000000000..c569e920d0 --- /dev/null +++ b/assets/contributing-to-official-pulsar-packages.html.26ef0088.js @@ -0,0 +1 @@ +const a=JSON.parse('{"key":"v-13ba24f3","path":"/docs/launch-manual/sections/core-hacking/sections/contributing-to-official-pulsar-packages.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Contributing to Official Pulsar Packages","slug":"contributing-to-official-pulsar-packages","link":"#contributing-to-official-pulsar-packages","children":[{"level":3,"title":"Hacking on Packages","slug":"hacking-on-packages","link":"#hacking-on-packages","children":[]}]}],"git":{"updatedTime":1670805853000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":2}]},"readingTime":{"minutes":1.25,"words":375},"filePathRelative":"docs/launch-manual/sections/core-hacking/sections/contributing-to-official-pulsar-packages.md"}');export{a as data}; diff --git a/assets/contributing-to-official-pulsar-packages.html.be326d7d.js b/assets/contributing-to-official-pulsar-packages.html.be326d7d.js new file mode 100644 index 0000000000..a5d83e6e81 --- /dev/null +++ b/assets/contributing-to-official-pulsar-packages.html.be326d7d.js @@ -0,0 +1,6 @@ +import{_ as o,o as s,c as i,a,b as e,d as t,f as r,r as l}from"./app.0e1565ce.js";const c={},d=a("h2",{id:"contributing-to-official-pulsar-packages",tabindex:"-1"},[a("a",{class:"header-anchor",href:"#contributing-to-official-pulsar-packages","aria-hidden":"true"},"#"),e(" Contributing to Official Pulsar Packages")],-1),p={href:"https://github.com/pulsar-edit/pulsar",target:"_blank",rel:"noopener noreferrer"},u=r(`

    Hacking on Packages

    Cloning

    The first step is creating your own clone. For some packages, you may also need to install the requirements necessary for building Pulsar in order to run pulsar -p install.

    For example, if you want to make changes to the tree-view package, fork the repo on your GitHub account, then clone it:

    $ git clone https://github.com/pulsar-edit/tree-view.git
    +

    Next install all the dependencies:

    $ cd tree-view
    +$ pulsar -p install
    +> Installing modules \u2713
    +

    Now you can link it to development mode so when you run an Pulsar window with pulsar -p --dev, you will use your fork instead of the built in package:

    $ pulsar -p link -d
    +

    Running in Development Mode

    Editing a package in Pulsar is a bit of a circular experience: you're using Pulsar to modify itself. What happens if you temporarily break something? You don't want the version of Pulsar you're using to edit to become useless in the process. For this reason, you'll only want to load packages in development mode while you are working on them. You'll perform your editing in stable mode, only switching to development mode to test your changes.

    To open a development mode window, use the Application: Open Dev command. You can also run dev mode from the command line with pulsar --dev.

    To load your package in development mode, create a symlink to it in LNX/MAC: ~/.pulsar/dev/packages - WIN: %USERPROFILE%\\.pulsar\\dev\\packages. This occurs automatically when you clone the package with pulsar -p develop. You can also run pulsar -p link --dev and pulsar -p unlink --dev from the package directory to create and remove dev-mode symlinks.

    Installing Dependencies

    You'll want to keep dependencies up to date by running pulsar -p update after pulling any upstream changes.

    `,15);function h(g,m){const n=l("ExternalLinkIcon");return s(),i("div",null,[d,a("p",null,[e("If you discover a bug or issue with an official Pulsar package then feel free to open up the issue in that specific repository instead. When in doubt just open the issue on the "),a("a",p,[e("pulsar-edit/pulsar"),t(n)]),e(" repository but be aware that it may get transferred to the proper package's repository.")]),u])}const f=o(c,[["render",h],["__file","contributing-to-official-pulsar-packages.html.vue"]]);export{f as default}; diff --git a/assets/converting-from-textmate.html.25b3df05.js b/assets/converting-from-textmate.html.25b3df05.js new file mode 100644 index 0000000000..312cca245f --- /dev/null +++ b/assets/converting-from-textmate.html.25b3df05.js @@ -0,0 +1,3 @@ +import{_ as r,o as i,c as s,a as t,b as e,d as o,f as a,r as h}from"./app.0e1565ce.js";const c={},l=t("h3",{id:"converting-from-textmate",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#converting-from-textmate","aria-hidden":"true"},"#"),e(" Converting from TextMate")],-1),d={href:"https://macromates.com",target:"_blank",rel:"noopener noreferrer"},m=t("h4",{id:"converting-a-textmate-grammar-bundle",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#converting-a-textmate-grammar-bundle","aria-hidden":"true"},"#"),e(" Converting a TextMate Grammar Bundle")],-1),u=t("p",null,"Converting a TextMate bundle will allow you to use its editor preferences, snippets, and colorization inside Atom.",-1),p={href:"https://en.wikipedia.org/wiki/R_(programming_language)",target:"_blank",rel:"noopener noreferrer"},g={href:"https://github.com/textmate",target:"_blank",rel:"noopener noreferrer"},f=a(`

    You can convert the R bundle with the following command:

    $ apm init --package language-r --convert https://github.com/textmate/r.tmbundle
    +

    You can now change directory into language-r to see the converted bundle. Once you link your package with the apm link command, your new package is ready to use. Launch Atom and open a .r file in the editor to see it in action!

    Converting a TextMate Syntax Theme

    `,4),_={href:"https://macromates.com",target:"_blank",rel:"noopener noreferrer"},v=t("h5",{id:"differences",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#differences","aria-hidden":"true"},"#"),e(" Differences")],-1),b={href:"https://en.wikipedia.org/wiki/Property_list",target:"_blank",rel:"noopener noreferrer"},x={href:"https://en.wikipedia.org/wiki/Cascading_Style_Sheets",target:"_blank",rel:"noopener noreferrer"},w={href:"http://lesscss.org",target:"_blank",rel:"noopener noreferrer"},y=t("p",null,"The utility that converts the theme first parses the theme's plist file and then creates comparable CSS rules and properties that will style Atom similarly.",-1),T=t("h5",{id:"convert-the-theme",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#convert-the-theme","aria-hidden":"true"},"#"),e(" Convert the Theme")],-1),k={href:"http://wiki.macromates.com/Themes/UserSubmittedThemes",target:"_blank",rel:"noopener noreferrer"},M=a(`

    Now, let's say you've downloaded the theme to ~/Downloads/MyTheme.tmTheme, you can convert the theme with the following command:

    $ apm init --theme my-theme --convert ~/Downloads/MyTheme.tmTheme
    +

    You can then change directory to my-theme to see the converted theme.

    Activate the Theme

    Once your theme is installed you can enable it by launching Atom and opening the Settings View with the Atom > PreferencesFile > PreferencesEdit > Preferences menu item. Then select the "Themes" tab on the left side navigation. Finally, choose "My Theme" from the "Syntax Theme" dropdown menu to enable your new theme.

    Your theme is now enabled, open an editor to see it in action!

    `,6);function S(A,C){const n=h("ExternalLinkIcon");return i(),s("div",null,[l,t("p",null,[e("It's possible that you have themes or grammars from "),t("a",d,[e("TextMate"),o(n)]),e(" that you like and use and would like to convert to Atom. If so, you're in luck because there are tools to help with the conversion.")]),m,u,t("p",null,[e("Let's convert the TextMate bundle for the "),t("a",p,[e("R"),o(n)]),e(" programming language. You can find other existing TextMate bundles "),t("a",g,[e("on GitHub"),o(n)]),e(".")]),f,t("p",null,[e("This section will go over how to convert a "),t("a",_,[e("TextMate"),o(n)]),e(" theme to an Atom theme.")]),v,t("p",null,[e("TextMate themes use "),t("a",b,[e("plist"),o(n)]),e(" files while Atom themes use "),t("a",x,[e("CSS"),o(n)]),e(" or "),t("a",w,[e("Less"),o(n)]),e(" to style the UI and syntax in the editor.")]),y,T,t("p",null,[e("Download the theme you wish to convert, you can browse existing TextMate themes on the "),t("a",k,[e("TextMate website"),o(n)]),e(".")]),M])}const I=r(c,[["render",S],["__file","converting-from-textmate.html.vue"]]);export{I as default}; diff --git a/assets/converting-from-textmate.html.3a2f1029.js b/assets/converting-from-textmate.html.3a2f1029.js new file mode 100644 index 0000000000..42894c33ad --- /dev/null +++ b/assets/converting-from-textmate.html.3a2f1029.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-38bce125","path":"/docs/atom-archive/hacking-atom/sections/converting-from-textmate.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"Converting from TextMate","slug":"converting-from-textmate","link":"#converting-from-textmate","children":[]}],"git":{"updatedTime":1664050274000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":2}]},"readingTime":{"minutes":1.31,"words":394},"filePathRelative":"docs/atom-archive/hacking-atom/sections/converting-from-textmate.md"}');export{e as data}; diff --git a/assets/converting-from-textmate.html.3ede2cec.js b/assets/converting-from-textmate.html.3ede2cec.js new file mode 100644 index 0000000000..f55ab39860 --- /dev/null +++ b/assets/converting-from-textmate.html.3ede2cec.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-f3d252b4","path":"/docs/launch-manual/sections/core-hacking/sections/converting-from-textmate.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Converting from TextMate","slug":"converting-from-textmate","link":"#converting-from-textmate","children":[{"level":3,"title":"Converting a TextMate Grammar Bundle","slug":"converting-a-textmate-grammar-bundle","link":"#converting-a-textmate-grammar-bundle","children":[]},{"level":3,"title":"Converting a TextMate Syntax Theme","slug":"converting-a-textmate-syntax-theme","link":"#converting-a-textmate-syntax-theme","children":[]}]}],"git":{"updatedTime":1670466847000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":1.23,"words":369},"filePathRelative":"docs/launch-manual/sections/core-hacking/sections/converting-from-textmate.md"}');export{e as data}; diff --git a/assets/converting-from-textmate.html.5e63d586.js b/assets/converting-from-textmate.html.5e63d586.js new file mode 100644 index 0000000000..c0ab4693b6 --- /dev/null +++ b/assets/converting-from-textmate.html.5e63d586.js @@ -0,0 +1,3 @@ +import{_ as r,o as s,c as i,a as t,b as e,d as a,f as o,r as h}from"./app.0e1565ce.js";const l={},c=t("h2",{id:"converting-from-textmate",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#converting-from-textmate","aria-hidden":"true"},"#"),e(" Converting from TextMate")],-1),d={href:"https://macromates.com",target:"_blank",rel:"noopener noreferrer"},m=t("h3",{id:"converting-a-textmate-grammar-bundle",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#converting-a-textmate-grammar-bundle","aria-hidden":"true"},"#"),e(" Converting a TextMate Grammar Bundle")],-1),u=t("p",null,"Converting a TextMate bundle will allow you to use its editor preferences, snippets, and colorization inside Pulsar.",-1),p={href:"https://en.wikipedia.org/wiki/R_(programming_language)",target:"_blank",rel:"noopener noreferrer"},g={href:"https://github.com/textmate",target:"_blank",rel:"noopener noreferrer"},f=o(`

    You can convert the R bundle with the following command:

    $ pulsar -p init --package language-r --convert https://github.com/textmate/r.tmbundle
    +

    You can now change directory into language-r to see the converted bundle. Once you link your package with the pulsar -p link command, your new package is ready to use. Launch Pulsar and open a .r file in the editor to see it in action!

    Converting a TextMate Syntax Theme

    `,4),v={href:"https://macromates.com",target:"_blank",rel:"noopener noreferrer"},_=t("h4",{id:"differences",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#differences","aria-hidden":"true"},"#"),e(" Differences")],-1),b={href:"https://en.wikipedia.org/wiki/Property_list",target:"_blank",rel:"noopener noreferrer"},x={href:"https://en.wikipedia.org/wiki/Cascading_Style_Sheets",target:"_blank",rel:"noopener noreferrer"},y={href:"http://lesscss.org",target:"_blank",rel:"noopener noreferrer"},w=o(`

    The utility that converts the theme first parses the theme's plist file and then creates comparable CSS rules and properties that will style Pulsar similarly.

    Convert the Theme

    Download the theme you wish to convert.

    Now, let's say you've downloaded the theme to ~/Downloads/MyTheme.tmTheme, you can convert the theme with the following command:

    $ pulsar -p init --theme my-theme --convert ~/Downloads/MyTheme.tmTheme
    +

    You can then change directory to my-theme to see the converted theme.

    Activate the Theme

    Once your theme is installed you can enable it by launching Pulsar and opening the Settings View with the LNX: Edit > Preferences - MAC: Pulsar > Preferences - WIN: File > Preferences menu item. Then select the "Themes" tab on the left side navigation. Finally, choose "My Theme" from the "Syntax Theme" dropdown menu to enable your new theme.

    Your theme is now enabled, open an editor to see it in action!

    `,9);function k(T,M){const n=h("ExternalLinkIcon");return s(),i("div",null,[c,t("p",null,[e("It's possible that you have themes or grammars from "),t("a",d,[e("TextMate"),a(n)]),e(" that you like and use and would like to convert to Pulsar. If so, you're in luck because there are tools to help with the conversion.")]),m,u,t("p",null,[e("Let's convert the TextMate bundle for the "),t("a",p,[e("R"),a(n)]),e(" programming language. You can find other existing TextMate bundles "),t("a",g,[e("on GitHub"),a(n)]),e(".")]),f,t("p",null,[e("This section will go over how to convert a "),t("a",v,[e("TextMate"),a(n)]),e(" theme to an Pulsar theme.")]),_,t("p",null,[e("TextMate themes use "),t("a",b,[e("plist"),a(n)]),e(" files while Pulsar themes use "),t("a",x,[e("CSS"),a(n)]),e(" or "),t("a",y,[e("Less"),a(n)]),e(" to style the UI and syntax in the editor.")]),w])}const C=r(l,[["render",k],["__file","converting-from-textmate.html.vue"]]);export{C as default}; diff --git a/assets/cpu-profile-done.c24435bc.js b/assets/cpu-profile-done.c24435bc.js new file mode 100644 index 0000000000..2c0ea28106 --- /dev/null +++ b/assets/cpu-profile-done.c24435bc.js @@ -0,0 +1 @@ +const s="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAKIAAAA0CAIAAACGr3QSAAAAAXNSR0IArs4c6QAAAAlwSFlzAAAWJQAAFiUBSVIk8AAAD41JREFUeAHtnNt33DaShwGQBLtbasm62Y41tifJJpnZnJx93Zf9/8/ZMy/zkniTiR3fLVn3vvEGYD8QMt2SZUfRhd0TGYei0CAAFuqHAgqFAuX9h1+Kz+HPzoF4rhropqghLqd+TsdJbnKGSPO0iUwV/RwVaj55EMDj3sB5Jp2ffnpmkZuZOC/SHACTztHvEue6zionjBSFlLlUFXjLE4IaOaedS52LnbNSZFKWUlkhbJ3tRNabCezJVs8LzIEq4EmsXTFms8o71k5ktBdF23EyUpEV7hhp57sET9eNWanKJWNKJbeSZD9KxlIGmE+28fMvMS8wB2lWyLGxG1XxbTZcK4pRlOwk+k2S7kdRrpBpP8UoYWPhlipzryxvV5N+UQ11HLlFo1URRWWN6aykuZlEmggDTE3zez1jJrTNC8w1N3ynW7Dmbln8fZzdN6IU+VDk+2pwkMj9JJlEntrEVgvGrlTVRimWndBCvKnMWEZHKj6KokmoaKb3gHEAOBBCnB46E4ADAfMCMyyAO1xGKaMkkVSIFSFuCzGxYpi7/bzIVAHRiRMLTvSFWBK+W2SwzwnG7UJJU7epZW4GsgOokScJpUF0hes4h5KRCBdZyVC0r+RIegpDzpaJnBeYAYiWo3DtxvFT2/mp14tH479aD2enhrMHg2oOkQ2iwzUW4pUQv3T1L+nCy0RnSrXMviAr4Q516IMLwq0Js2nMbVOtVsWSsT0Xb8f6H6n+NY6H72CeLthCfI5gprWVlJWQKFwTFeX8rOUbEWHEQ7inAzJEQDgyKYaROoiiQURGx19rwdXLPbT9JQeWJgVR6/rCrFu7afLbZbZamCUjelY+6916quPXjEyCmciHlrvjfMHsVSzhusLeKSd3rJdj/3vqXkff3xIh1p3YKPKudahvLTMPwtD/l6z5ocq+KkYb2WSxFKlhFeApp1+iN0Ahw3hiKhJJqRv4nv7WYvMCc8CS1TALqluV2SgqJmZ4FNLP7Ps8IgMz9Kpxy9Z0rfXTc4vrZjAGp66rHhSj74eTu4VYFCIMJwzggXIyeOKtlfVyMDTkzOZcK+Sz6l6nGxVaztx2y5g1ky9Xgsn4d4kjQ1eIfiXWTEHBuGZty0yMrV3MWNd52T316gZpKyRjNVfQv043/vp/z5E0BxWmb6slU4ExlE3LxIesgKdcIE1milDwyPkFFSmn2P1h2StKCe+RVoGi49Vco7pqxJpxG8nmIpPzw4zXJAC+LdpONHFeYA5EsfzoIRymgjuNKJyg94MfZCPzgqkWrKX4B8+vPaFUyX66uC0zDK6DKH0SJdJk3w7G9yqx8G4Mv3Yifu8F8wUzPd13/1qX8kLwe9TznGxcGMB95DwFzlHnObMoIRlvxko9S3om7i4IeRhHj1W8WKq1fLxW+QkFqgiByHA/Z+VXm22+YK4XSCrzFuxj1nwauMA4Mud+AaYo3mbg7XTKgVSPkvSpj8uC1Z0Qm1KN1fEQ3SY9n3hX2zA3sDURmNUENqMOo3hLd57qcVT4QY8ZDnU66GKhSMgPtGg0mMCwkDzV4o3uHMYxxUNVIWfzChJDqel3NS+9cCTUhklnR/p+SQi695o3APyOYnHhl16sYNswByoBIPAFWIjDr/csi2Mpuqa/uj0ZfT3JN51fMgXbyDRICO4AE5gUj7vJL93+r7r7No7hOPVPZ2vecjHunL/Ucf96167zF2wnZ3swB9kK+8QdZ1PjkL1SyImSmVQKG6Flj9khkUOlftbdgWQrYrSTT1a8dcmbsgMrgxxPIrEXqRdp99e09yZB9hU2ZFbPbEQeYhUXkp+8hT1p+Mhb0I8K9rg+6AeX5zK9arpjUWFI+TD98u+6cA3twQyJsBzrx5oxd6riTlnEwh5GyetYv0g0qHyXTxZstaeS3Sjei+OfO53Xie73+n1rSceaqI0XTtwHxlE0Yj9KqYGKB5Gi66yYar2qWD0PVfxj2kUtul2Ud0y1Yv1ux77Sb5kLkqSMolOQXJhx/14F24MZjEGJTZulqtwssm/HAzaaDiK13uku2Z529ofxUb8qD2L9Kuk+T9PdKGErYjvSW7iIWJ1iMqz3Lth1zpWs2MWSEnMKbgVrBjtUdq+crJTFYRJjFR8r+bAoNovxalWC8/N0weneQRwzzjMk3ECk24M5dH9U055za2VxvxQbbElV9st89F08gvUsNDF0lEVxIIrXWrxOO2+Tzm6k91Q0VNFhFJnYUwu0iPVSVa1as27Ydc7u5tkXpV1xXlkbFFW/3MEWca8Qa/Vu9IEftCe7SRKf3v749xLIS1HbKsxIEgCMlDpI9H6cY6RkZcmO8goCiImjtvWTB011rRB3i+ytznaSdDfpvEr0U5XuKW/NXLTmYZXfK4v1Mlsv8/VCrNc70+jkjBZU0i18hB1MuhSq+FCJQ50MVMJ+140N7cEchsqxil7oFJV4LNWbyXizLNett/gH0yAwgBA0LdfG6tsFwp0XIv9XqszyOtoZCja+Qf8z3P1mYsP+D3f0cIpQkBA2M9jEZP2KHG9p8aS78ChdfJUkaHYAfzND2zCj67I3XMoU7feAebcaP8xGX+Z+AAcDRBl5pUME8IJEkljmdtlUibHkIfLXzP5HjWuwh1CEAMyhJxE5EuI3LV7o3rbWL+POc60HtcfBZ5hb6uUgAWxI81utj7yjiH6dIMm7OvcwT3e6gDd3YOMesITKEG9SmvTQAGwmCPG/tPjfpbXfkg5bk4wB+Ojw0tAJWmrnnL1mmrFtkAavuVjdTqRkRQRIibDDKDbeVcSHaTAaLPHEFix5gwuQ99IVtnbdmMY4FKRPgPQg1s+Tzm9ak0gLuQPzTQ4zbn4Ng+sZgy52JSMqlVBV15joeLI+0W9uLNJtS3OQv3DH5uVRMW4B9+waZtKDUF4MD4oHmBec6RmLl9bxcY139V6m8ouRNCelZiPNDK1ckROAsYTfdeW15SshhUqY6lmq3bJVH38S4azD4Oltnlw3NrQnzYHLSLA/ImUtJkzcBwDjm3y8Yo9n0EvCgLDWs4CgQs5tINn7So0k1pV4FHlFrE1PsRNtCWrkiaRWf7QHM80CaTDmwMT9svguH9zLs1uVh2T1cmP1KYaBNAaT/x7mfx/lQL2l41/14m+pt5CPPtu0TzHryn+CMX2ao46bYJwNfhhl9503jNDRWP7yqAkXm0Gnx2SMptT8FyfySmxVVa86kOoW+xwY4JqTlRd7S0NkE5l+L60IcktiuEI2znk6+X4p2JS9KhqaCj8WaVWaIYLJ+EE5/jrL7tYHZBhXpzly+WYHpodOQ21M+Ui2K8QkGz5JOlsnVuYf48kF08Orzy5cNyy0NNBGw9sMbcPMTjAn3vYS9prdlqkVMS/i3haN/AX3gYu1P3QRFs34VnKxGVUpr+UhSRMlBlHMphZ5PgXGH3xxqCqsF4AN7Q9vbEamejPN6/zN61jdpZJzNzYTEpJIZ+lf+APcniSu6w6twgwjDiP1z3TxcdJhEUXz2LNA036Q4yiSfVN5JfmSMLBX8UKJxx39c3dxL0q0Hyxrg0kU7US4Jlx9AE76KM3RklP4ohBuxWuX3l7brB2SypLIAIabQ84OKq6+Tr6tnbfJ86eCOTSGLeRXmJddAndoLn0fl4+hVNjCNgcFuhiBYe1Uy4Fn+joTq+P6hXiZRo+6/R873Z3I71zCR7jv7WhUe6XeI+EMVc+a+6a8g+MDTvfCldKtFxN22BiZfBtrWrvFZDOPIpsOpCi9NMujKH4WJ9uRPzzHCORpq+/XdGtVmt+1gWb6wQ0WYPVkq+ql1uu2O/bD2MUDbOLCIf5l2nsRa6oF1XrRXD8AYL94vsoQZtm+qb4f7/1tUnBSBG7yDm29/xojU5BmUhD3bw6GD9SwCkt4Vnpd/Xhp5f9U9EjK7TrnnwTm42bU8hT6Lin0d/as9lWMl8heLEa1nYTEMwP8CkCF+6k89BtsXgesoGK9EyellL6e49fVeYMsnyp2qZ8OpFNR4fD0sPCnsRvdgvRpIrHxofwzTNHkwIe9SVF1zW7qdJ1A/maEvxRFHyl8rZV/5J0nk2EHG1YcW93SyVs8rk8+Db+Ou8hZj5q0sDe1E+MK6I0hwRLSPL2+CPTjKxEkm7cEUrnD2ebiZ7imyWAka0pNp19HfAaDdmAEjQn9HV5gM9GWmfp4VCVDIwohwvn/FbwEoxi+8FkZRkXSmzwNX0hhz4tDsPh0FvWHhXjUvK7JdlURZgWqmqj4WSfVLl8u/XDtwydfWWMrjzr9pzHdWqIzEq5b2mYAc92u4xvApPU3gf5S5l9k/rArCup0CFgy4n01GXQM6ovCeYipjvTwKGSGscFvhNOnXxYTPDv5eAEjBOz7JM+nX/WH49TMvMAXjP7RWf1JO1jpP1l1rqAqpSZxxGcqWPt9bJI6V03nyzQbmI8Rqr8CtmzM1/nku9HgrvV7iMyvXJ5l7/o4mYH5YS5Wyoz4ovU/G4yJkB/3TZjO1Ihn2d8mAw494P3JXMDEH8xe1wE2miRMzmT0PD5G6rwov2ta6IXXQdsp9GcDcyCCRmprb1flf432v88dqynYhO8HyhTyCuQIKPwDSLTWu9izai6Sws8QeERm1iR7NePuCHFLiP8sGLoH+zHOX3xeKAou+O9KXMt/GhIAJnL+8Icyn7/aM3POBuYgi7jR9429bYoHuQMhJHJHiCdasBZacHwSSmzUKxOGcfBulFiaAU9Bl/yYunaFeKvFTpKwu1yOq80aabyDvyizLTy9E3Xdq1JkkatNzM4E8tOJs4E50ITmxUYkPtuYOYFtW4hHXfXPhVvAk1jBV1q+Hx58VXhvQISbEAY3uggXmgsA0yd+7PWf+ZM40aqtxuqoGOVfOd8nNrJsPe7s8YXANua+0KD5vc8SZrgCukdRAlpbTrxI/KG3n5Meh94QjoMKBzExzEYbVYmbAJ9oCRMgiZny3te7On7SWfiJM1QxDoPywMao2YUcDbMxHWFX60bZnl/2t0WZnMn3tMNMxmEZ1Oxlr2mXDOCcXPVf6lMK1Ynm+w8xWcNH/8izaHEy4YtMQCz4YiMzLk7XQDvyRaK8XoDSYfuWDzRVy5WhlxzGitrw2y1q7+w5H1SvG+7ZwBzm5rBOZocHhEjBboVNAzyawZne4HcD0NQ47VjZtFZ0OK5+FCug9Q8+yE934fQNNdAj0LFDHp/xuhk53/XPeNCGORiDgpYEKg3GARgvgrWkghnQqlrRAXuUZx9qzjb4EQn5vcjX/ab+//nmOTAbmI+xqZFC4LzM1QLXYFYnHIsgifQAZD0kNo9O/K5Tfc46c5PzwzzNoxsV+X/qbiSVpvxXqQAAAABJRU5ErkJggg==",n="/assets/settings.f822be64.png",t="/assets/package-settings.ad8a3253.png",A="/assets/keybinding-resolver.befdd67c.png",a="/assets/fonts-in-use.d1baabe5.png",p="/assets/exception-notification.f8f467e7.png",e="/assets/devtools-error.d36c506a.png",M="/assets/timecop.1c72c1e3.png",r="/assets/cpu-profile-start.013307c2.png",o="/assets/cpu-profile-done.fca74677.png";export{s as _,n as a,t as b,M as c,A as d,a as e,p as f,e as g,r as h,o as i}; diff --git a/assets/cpu-profile-done.fca74677.png b/assets/cpu-profile-done.fca74677.png new file mode 100644 index 0000000000..f434068c0d Binary files /dev/null and b/assets/cpu-profile-done.fca74677.png differ diff --git a/assets/cpu-profile-start.013307c2.png b/assets/cpu-profile-start.013307c2.png new file mode 100644 index 0000000000..f28991dfa9 Binary files /dev/null and b/assets/cpu-profile-start.013307c2.png differ diff --git a/assets/creating-a-fork-of-a-core-package-in-atom-atom.html.367b286d.js b/assets/creating-a-fork-of-a-core-package-in-atom-atom.html.367b286d.js new file mode 100644 index 0000000000..d432fbff32 --- /dev/null +++ b/assets/creating-a-fork-of-a-core-package-in-atom-atom.html.367b286d.js @@ -0,0 +1 @@ +const a=JSON.parse('{"key":"v-752e2619","path":"/docs/atom-archive/hacking-atom/sections/creating-a-fork-of-a-core-package-in-atom-atom.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Creating a Fork of a Core Package in atom/atom","slug":"creating-a-fork-of-a-core-package-in-atom-atom","link":"#creating-a-fork-of-a-core-package-in-atom-atom","children":[{"level":3,"title":"Creating Your New Package","slug":"creating-your-new-package","link":"#creating-your-new-package","children":[]},{"level":3,"title":"Merging Upstream Changes into Your Package","slug":"merging-upstream-changes-into-your-package","link":"#merging-upstream-changes-into-your-package","children":[]}]}],"git":{"updatedTime":1668310114000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":4},{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":2}]},"readingTime":{"minutes":1.54,"words":462},"filePathRelative":"docs/atom-archive/hacking-atom/sections/creating-a-fork-of-a-core-package-in-atom-atom.md"}');export{a as data}; diff --git a/assets/creating-a-fork-of-a-core-package-in-atom-atom.html.8f3be404.js b/assets/creating-a-fork-of-a-core-package-in-atom-atom.html.8f3be404.js new file mode 100644 index 0000000000..aa496a4126 --- /dev/null +++ b/assets/creating-a-fork-of-a-core-package-in-atom-atom.html.8f3be404.js @@ -0,0 +1,6 @@ +import{_ as s,o as c,c as l,a as e,b as a,d as o,w as i,f as m,r}from"./app.0e1565ce.js";const p={},d=e("h2",{id:"creating-a-fork-of-a-core-package-in-atom-atom",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#creating-a-fork-of-a-core-package-in-atom-atom","aria-hidden":"true"},"#"),a(" Creating a Fork of a Core Package in atom/atom")],-1),u={href:"https://github.com/atom/atom/tree/master/packages",target:"_blank",rel:"noopener noreferrer"},g=e("code",null,"packages",-1),h={class:"custom-container tip"},k=e("p",{class:"custom-container-title"},"Tips",-1),f=e("strong",null,"Tip:",-1),_=e("h3",{id:"creating-your-new-package",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#creating-your-new-package","aria-hidden":"true"},"#"),a(" Creating Your New Package")],-1),y={href:"https://github.com/atom/atom/tree/master/packages/one-light-ui",target:"_blank",rel:"noopener noreferrer"},b={href:"https://github.com/atom/atom/archive/master.zip",target:"_blank",rel:"noopener noreferrer"},v=m(`
  • Unzip the file to a temporary location (for example /tmp/atomC:\\TEMP\\atom)

  • Copy the contents of the desired package into a working directory for your fork

    $ <span class='platform-mac platform-linux'>cp -R /tmp/atom/packages/one-light-ui ~/src/one-light-ui-plus</span><span class='platform-windows'>xcopy C:\\TEMP\\atom\\packages\\one-light-ui C:\\src\\one-light-ui-plus /E /H /K</span>
    +
  • Create a local repository and commit the initial contents

    $ cd ~/src/one-light-ui-plus
    +$ git init
    +$ git commit -am "Import core Atom package"
    +
  • Update the name property in package.json to give your package a unique name

  • Make the other customizations that you have in mind

  • Commit your changes

    $ git commit -am "Apply initial customizations"
    +
  • `,6),w={href:"https://help.github.com/articles/create-a-repo/",target:"_blank",rel:"noopener noreferrer"},x=e("li",null,[e("p",null,"Follow the instructions in the github.com UI to push your code to your new online repository")],-1),C=e("h3",{id:"merging-upstream-changes-into-your-package",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#merging-upstream-changes-into-your-package","aria-hidden":"true"},"#"),a(" Merging Upstream Changes into Your Package")],-1);function A(T,I){const t=r("ExternalLinkIcon"),n=r("RouterLink");return c(),l("div",null,[d,e("p",null,[a("Several of Atom's core packages are maintained in the "),e("a",u,[g,a(" directory of the atom/atom repository"),o(t)]),a(". If you would like to use one of these packages as a starting point for your own package, please follow the steps below.")]),e("div",h,[k,e("p",null,[f,a(" In most cases, we recommend "),o(n,{to:"/docs/atom-archive/hacking-atom/package-word-count/#package-generator"},{default:i(()=>[a("generating a brand new package")]),_:1}),a(" or a "),o(n,{to:"/docs/atom-archive/hacking-atom/creating-a-theme/#creating-a-syntax-theme"},{default:i(()=>[a("brand new theme")]),_:1}),a(" as the starting point for your creation. The guide below applies only to situations where you want to create a package that closely resembles a core Atom package.")])]),_,e("p",null,[a("For the sake of this guide, let's assume that you want to start with the current code in the "),e("a",y,[a("one-light-ui"),o(t)]),a(' package, make some customizations to it, and publish your new package under the name "one-light-ui-plus".')]),e("ol",null,[e("li",null,[e("p",null,[a("Download the "),e("a",b,[a("current contents of the atom/atom repository as a zip file"),o(t)])])]),v,e("li",null,[e("p",null,[e("a",w,[a("Create a public repository on github.com"),o(t)]),a(" for your new package")])]),x,e("li",null,[e("p",null,[a("Follow the steps in the "),o(n,{to:"/docs/atom-archive/hacking-atom/sections/publishing/"},{default:i(()=>[a("Publishing guide")]),_:1}),a(" to publish your new package")])])]),C,e("p",null,[a("The code in the original package will continue to evolve over time, either to fix bugs or to add new enhancements. You may want to incorporate some or all of those updates into your package. To do so, you can follow "),o(n,{to:"/docs/atom-archive/hacking-atom/sections/maintaining-a-fork-of-a-core-package-in-atom-atom/#step-by-step-guide"},{default:i(()=>[a("these steps")]),_:1}),a(" for merging upstream changes into your package.")])])}const z=s(p,[["render",A],["__file","creating-a-fork-of-a-core-package-in-atom-atom.html.vue"]]);export{z as default}; diff --git a/assets/creating-a-fork-of-a-core-package.html.1312dc3f.js b/assets/creating-a-fork-of-a-core-package.html.1312dc3f.js new file mode 100644 index 0000000000..48efd73ce8 --- /dev/null +++ b/assets/creating-a-fork-of-a-core-package.html.1312dc3f.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-6ef9d234","path":"/docs/launch-manual/sections/core-hacking/sections/creating-a-fork-of-a-core-package.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Creating a Fork of a Core Package","slug":"creating-a-fork-of-a-core-package","link":"#creating-a-fork-of-a-core-package","children":[{"level":3,"title":"Creating Your New Package","slug":"creating-your-new-package","link":"#creating-your-new-package","children":[]},{"level":3,"title":"Merging Upstream Changes into Your Package","slug":"merging-upstream-changes-into-your-package","link":"#merging-upstream-changes-into-your-package","children":[]}]}],"git":{"updatedTime":1670864815000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":2}]},"readingTime":{"minutes":1.57,"words":470},"filePathRelative":"docs/launch-manual/sections/core-hacking/sections/creating-a-fork-of-a-core-package.md"}');export{e as data}; diff --git a/assets/creating-a-fork-of-a-core-package.html.e80cef32.js b/assets/creating-a-fork-of-a-core-package.html.e80cef32.js new file mode 100644 index 0000000000..9d5709ec15 --- /dev/null +++ b/assets/creating-a-fork-of-a-core-package.html.e80cef32.js @@ -0,0 +1,11 @@ +import{_ as u,o as d,c as h,a as e,b as a,d as t,e as g,w as n,f as o,r as c}from"./app.0e1565ce.js";const m={},k=e("h2",{id:"creating-a-fork-of-a-core-package",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#creating-a-fork-of-a-core-package","aria-hidden":"true"},"#"),a(" Creating a Fork of a Core Package")],-1),b={href:"https://github.com/pulsar-edit/pulsar/tree/master/packages",target:"_blank",rel:"noopener noreferrer"},f=e("code",null,"packages",-1),_=o('

    Tip

    In most cases, we recommend generating a brand new package or a brand new theme as the starting point for your creation. The guide below applies only to situations where you want to create a package that closely resembles a core Pulsar package.

    Creating Your New Package

    ',2),v={href:"https://github.com/pulsar-edit/pulsar/tree/master/packages/one-light-ui",target:"_blank",rel:"noopener noreferrer"},y={href:"https://github.com/pulsar-edit/pulsar/archive/master.zip",target:"_blank",rel:"noopener noreferrer"},w=o("
  • Unzip the file to a temporary location (for example LNX/MAC: /tmp/pulsar - WIN: C:\\TEMP\\pulsar)

  • Copy the contents of the desired package into a working directory for your fork

  • ",2),C=e("div",{class:"language-bash ext-sh line-numbers-mode"},[e("pre",{class:"language-bash"},[e("code",null,[a("$ "),e("span",{class:"token function"},"cp"),a(),e("span",{class:"token parameter variable"},"-R"),a(` /tmp/pulsar/packages/one-light-ui ~/src/one-light-ui-plus +$ `),e("span",{class:"token builtin class-name"},"cd"),a(` ~/src/one-light-ui-plus +`)])]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),x=e("div",{class:"language-bash ext-sh line-numbers-mode"},[e("pre",{class:"language-bash"},[e("code",null,[a("$ "),e("span",{class:"token function"},"cp"),a(),e("span",{class:"token parameter variable"},"-R"),a(` /tmp/pulsar/packages/one-light-ui ~/src/one-light-ui-plus +$ `),e("span",{class:"token builtin class-name"},"cd"),a(` ~/src/one-light-ui-plus + +`)])]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),P=e("div",{class:"language-powershell ext-powershell line-numbers-mode"},[e("pre",{class:"language-powershell"},[e("code",null,[a("$ "),e("span",{class:"token function"},"Copy-Item"),a(),e("span",{class:"token operator"},"-"),a("Path "),e("span",{class:"token string"},'"C:\\TEMP\\pulsar\\packages\\one-light-ui"'),a(),e("span",{class:"token operator"},"-"),a("Destination "),e("span",{class:"token string"},'"C:\\src\\one-light-ui-plus"'),a(),e("span",{class:"token operator"},"-"),a("Recurse "),e("span",{class:"token operator"},"-"),a(`Force +$ cd C:\\src\\one-light-ui-plus +`)])]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),I=o(`
    1. Create a local repository and commit the initial contents
    $ git init
    +$ git commit -am "Import core Pulsar package"
    +
    1. Update the name property in package.json to give your package a unique name

    2. Make the other customizations that you have in mind

    3. Commit your changes

    $ git commit -am "Apply initial customizations"
    +
    `,4),T={start:"8"},$={href:"https://help.github.com/articles/create-a-repo/",target:"_blank",rel:"noopener noreferrer"},N=e("li",null,[e("p",null,"Follow the instructions in the github.com UI to push your code to your new online repository")],-1),A=e("li",null,[e("p",null,[a("Follow the steps in the "),e("a",{href:"#publishing"},"Publishing guide"),a(" to publish your new package")])],-1),z=e("h3",{id:"merging-upstream-changes-into-your-package",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#merging-upstream-changes-into-your-package","aria-hidden":"true"},"#"),a(" Merging Upstream Changes into Your Package")],-1),F=e("p",null,[a("The code in the original package will continue to evolve over time, either to fix bugs or to add new enhancements. You may want to incorporate some or all of those updates into your package. To do so, you can follow "),e("a",{href:"#maintaining-a-fork-of-a-core-package"},"these steps"),a(" for merging upstream changes into your package.")],-1);function q(E,L){const s=c("ExternalLinkIcon"),p=c("Tabs");return d(),h("div",null,[k,e("p",null,[a("Several of Pulsar's core packages are maintained in the "),e("a",b,[f,a(" directory of the pulsar-edit/pulsar repository"),t(s)]),a(". If you would like to use one of these packages as a starting point for your own package, please follow the steps below.")]),_,g("Could this be made better with GH CLI?"),e("p",null,[a("For the sake of this guide, let's assume that you want to start with the current code in the "),e("a",v,[a("one-light-ui"),t(s)]),a(' package, make some customizations to it, and publish your new package under the name "one-light-ui-plus".')]),e("ol",null,[e("li",null,[e("p",null,[a("Download the "),e("a",y,[a("current contents of the pulsar-edit/pulsar repository as a zip file"),t(s)])])]),w]),t(p,{id:"35",data:[{title:"Linux"},{title:"macOS"},{title:"Windows"}],"tab-id":"core-hacking"},{tab0:n(({title:i,value:r,isActive:l})=>[C]),tab1:n(({title:i,value:r,isActive:l})=>[x]),tab2:n(({title:i,value:r,isActive:l})=>[P]),_:1}),I,e("ol",T,[e("li",null,[e("p",null,[e("a",$,[a("Create a public repository on github.com"),t(s)]),a(" for your new package")])]),N,A]),z,F])}const V=u(m,[["render",q],["__file","creating-a-fork-of-a-core-package.html.vue"]]);export{V as default}; diff --git a/assets/creating-a-grammar.html.207ba73b.js b/assets/creating-a-grammar.html.207ba73b.js new file mode 100644 index 0000000000..6fae4e8470 --- /dev/null +++ b/assets/creating-a-grammar.html.207ba73b.js @@ -0,0 +1 @@ +const a=JSON.parse('{"key":"v-7741ac96","path":"/docs/atom-archive/hacking-atom/sections/creating-a-grammar.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"Creating a Grammar","slug":"creating-a-grammar","link":"#creating-a-grammar","children":[]}],"git":{"updatedTime":1664050274000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":2}]},"readingTime":{"minutes":7.29,"words":2186},"filePathRelative":"docs/atom-archive/hacking-atom/sections/creating-a-grammar.md"}');export{a as data}; diff --git a/assets/creating-a-grammar.html.5cf23f52.js b/assets/creating-a-grammar.html.5cf23f52.js new file mode 100644 index 0000000000..6f04f85c32 --- /dev/null +++ b/assets/creating-a-grammar.html.5cf23f52.js @@ -0,0 +1,94 @@ +import{_ as o,o as i,c as r,a as e,b as n,d as a,f as t,r as l}from"./app.0e1565ce.js";const c={},p=e("h3",{id:"creating-a-grammar",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#creating-a-grammar","aria-hidden":"true"},"#"),n(" Creating a Grammar")],-1),d={href:"http://tree-sitter.github.io/tree-sitter",target:"_blank",rel:"noopener noreferrer"},u={href:"https://en.wikipedia.org/wiki/Abstract_syntax_tree",target:"_blank",rel:"noopener noreferrer"},h=e("em",null,"syntax trees",-1),m=t('

    This syntax tree gives Atom a comprehensive understanding of the structure of your code, which has several benefits:

    1. Syntax highlighting will not break because of formatting changes.
    2. Code folding will work regardless of how your code is indented.
    3. Editor features can operate on the syntax tree. For instance, the Select Larger Syntax Node and Select Smaller Syntax Node allow you to select conceptually larger and smaller chunks of your code.
    4. Community packages can use the syntax tree to manipulate code intelligently.

    Tree-sitter grammars are relatively new. Many languages in Atom are still supported by TextMate grammars, though we intend to phase these out over time.

    If you're adding support for a new language, you're in the right place!

    Getting Started

    There are two components required to use Tree-sitter in Atom: a parser and a grammar file.

    The Parser

    ',7),g={href:"https://en.wikipedia.org/wiki/Context-free_grammar",target:"_blank",rel:"noopener noreferrer"},v={href:"http://tree-sitter.github.io/tree-sitter/creating-parsers",target:"_blank",rel:"noopener noreferrer"},f={href:"https://github.com/tree-sitter",target:"_blank",rel:"noopener noreferrer"},k={href:"https://npmjs.com",target:"_blank",rel:"noopener noreferrer"},b=e("code",null,"name",-1),y=e("code",null,"version",-1),_=e("code",null,"package.json",-1),x=t(`
    {
    +  "name": "tree-sitter-mylanguage",
    +  "version": "0.0.1",
    +  // ...
    +}
    +

    then run the command npm publish.

    The Package

    Once you have a Tree-sitter parser that is available on npm, you can use it in your Atom package. Packages with grammars are, by convention, always named starting with language. You'll need a folder with a package.json, a grammars subdirectory, and a single json or cson file in the grammars directory, which can be named anything.

    language-mylanguage
    +\u251C\u2500\u2500 LICENSE
    +\u251C\u2500\u2500 README.md
    +\u251C\u2500\u2500 grammars
    +\u2502   \u2514\u2500\u2500 mylanguage.cson
    +\u2514\u2500\u2500 package.json
    +

    The Grammar File

    The mylanguage.cson file specifies how Atom should use the parser you created.

    Basic Fields

    It starts with some required fields:

    name: 'My Language'
    +scopeName: 'mylanguage'
    +type: 'tree-sitter'
    +parser: 'tree-sitter-mylanguage'
    +
    `,10),w=e("li",null,[e("code",null,"scopeName"),n(" - A unique, stable identifier for the language. Atom users will use this in configuration files if they want to specify custom configuration based on the language.")],-1),T=e("li",null,[e("code",null,"name"),n(" - A human readable name for the language.")],-1),q=e("code",null,"parser",-1),S={href:"https://nodejs.org/api/modules.html#modules_require",target:"_blank",rel:"noopener noreferrer"},j=e("code",null,"require()",-1),A=e("li",null,[e("code",null,"type"),n(" - This should have the value "),e("code",null,"tree-sitter"),n(" to indicate to Atom that this is a Tree-sitter grammar and not a "),e("a",{href:"../creating-a-legacy-textmate-grammar"},"TextMate grammar"),n(".")],-1),C=t(`

    Language Recognition

    Next, the file should contain some fields that indicate to Atom when this language should be used. These fields are all optional.

    • fileTypes - An array of filename suffixes. The grammar will be used for files whose names end with one of these suffixes. Note that the suffix may be an entire filename.
    • firstLineRegex - A regex pattern that will be tested against the first line of the file. The grammar will be used if this regex matches.
    • contentRegex - A regex pattern that will be tested against the contents of the file in order to break ties in cases where multiple grammars matched the file using the above two criteria. If the contentRegex matches, this grammar will be preferred over another grammar with no contentRegex. If the contentRegex does not match, a grammar with no contentRegex will be preferred over this one.

    Syntax Highlighting

    The HTML classes that Atom uses for syntax highlighting do not correspond directly to nodes in the syntax tree. Instead, Tree-sitter grammar files specify scope mappings that specify which classes should be applied to which syntax nodes. The scopes object controls these scope mappings. Its keys are CSS selectors that select nodes in the syntax tree. Its values can be of several different types.

    Here is a simple example:

    scopes:
    +  'call_expression > identifier': 'entity.name.function'
    +

    This entry means that, in the syntax tree, any identifier node whose parent is a call_expression should be highlighted using three classes: syntax--entity, syntax--name, and syntax--function.

    `,8),I={href:"https://developer.mozilla.org/en-US/docs/Web/CSS/Child_selectors",target:"_blank",rel:"noopener noreferrer"},N=e("code",null,">",-1),E=e("code",null,"'call_expression identifier'",-1),H=e("code",null,"identifier",-1),L=e("code",null,"call_expression",-1),M=t(`
    Advanced Selectors

    The keys of the scopes object can also contain multiple CSS selectors, separated by commas, similar to CSS files. The triple-quote syntax in CSON makes it convenient to write keys like this on multiple lines:

    scopes:
    +  '''
    +  function_declaration > identifier,
    +  call_expression > identifier,
    +  call_expression > field_expression > field_identifier
    +  ''': 'entity.name.function'
    +
    `,3),R={href:"https://developer.mozilla.org/en-US/docs/Web/CSS/:nth-child",target:"_blank",rel:"noopener noreferrer"},F=e("code",null,":nth-child",-1),P=e("code",null,"identifier",-1),z=e("code",null,"singleton_method",-1),B=t(`
    scopes:
    +  'singleton_method > identifier:nth-child(3)': 'entity.name.function'
    +
    `,1),G=e("em",null,"anonymous",-1),J=e("code",null,"(",-1),O=e("code",null,":",-1),U={href:"http://tree-sitter.github.io/tree-sitter/using-parsers#named-vs-anonymous-nodes",target:"_blank",rel:"noopener noreferrer"},V=t('
    scopes:\n  '''\n    "*",\n    "/",\n    "+",\n    "-"\n  ''': 'keyword.operator'\n
    Text-based Mappings

    You can also apply different classes to a syntax node based on its text. Here are some examples:

    scopes:\n\n  # Apply the classes `syntax--builtin` and `syntax--variable` to all\n  # `identifier` nodes whose text is `require`.\n  'identifier': {exact: 'require', scopes: 'builtin.variable'},\n\n  # Apply the classes `syntax--type` and `syntax--integer` to all\n  # `primitive_type` nodes whose text starts with `int` or `uint`.\n  'primitive_type': {match: /^u?int/, scopes: 'type.integer'},\n\n  # Apply the classes `syntax--builtin`, `syntax--class`, and\n  # `syntax--name` to `constant` nodes with the text `Array`,\n  # `Hash` and `String`. For all other `constant` nodes, just\n  # apply the classes `syntax--class` and `syntax--name`.\n  'constant': [\n    {match: '^(Array|Hash|String)$', scopes: 'builtin.class.name'},\n    'class.name'\n  ]\n

    In total there are four types of values that can be associated with selectors in scopes:

    • Strings - Each class name in the dot-separated string will be prefixed with syntax-- and applied to the selected node.
    • Objects with the keys exact and scopes - If the node's text equals the exact string, the scopes string will be used as described above.
    • Objects with the keys match and scopes - If the node's text matches the match regex pattern, the scopes string will be used as described above.
    • Arrays - The elements of the array will be processed from beginning to end. The first element that matches the selected node will be used as describe above.
    Specificity
    ',7),W=e("code",null,"scopes",-1),Y={href:"https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity",target:"_blank",rel:"noopener noreferrer"},$=e("code",null,"exact",-1),D=e("code",null,"match",-1),Z=e("em",null,"not",-1),K=e("code",null,"exact",-1),Q=e("code",null,"match",-1),X=t(`
    scopes:
    +  'call_expression > identifier': 'entity.name.function'
    +
    +  # If we did not include the second selector here, then this rule
    +  # would not apply to identifiers inside of call_expressions,
    +  # because the selector \`call_expression > identifier\` is more
    +  # specific than the selector \`identifier\`.
    +  'identifier, call_expression > identifier': [
    +    {exact: 'require', scopes: 'builtin.variable'},
    +    {match: '^[A-Z]', scopes: 'constructor'},
    +  ]
    +

    Language Injection

    Sometimes, a source file can contain code written in several different languages. Tree-sitter grammars support this situation using a two-part process called language injection. First, an 'outer' language must define an injection point - a set of syntax nodes whose text can be parsed using a different language, along with some logic for guessing the name of the other language that should be used. Second, an 'inner' language must define an injectionRegex - a regex pattern that will be tested against the language name provided by the injection point.

    `,3),ee={href:"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#Tagged_templates",target:"_blank",rel:"noopener noreferrer"},ne=t(`
    // HTML in a template literal
    +const htmlContent = html\`<div>Hello \${name}</div>\`;
    +

    The tree-sitter-javascript parser parses this tagged template literal as a call_expression with two children: an identifier and a template_literal:

    (call_expression
    +  (identifier)
    +  (template_literal
    +    (interpolation
    +      (identifier))))
    +

    Here is an injection point that would allow syntax highlighting inside of template literals:

    atom.grammars.addInjectionPoint("source.js", {
    +	type: "call_expression",
    +
    +	language(callExpression) {
    +		const { firstChild } = callExpression;
    +		if (firstChild.type === "identifier") {
    +			return firstChild.text;
    +		}
    +	},
    +
    +	content(callExpression) {
    +		const { lastChild } = callExpression;
    +		if (lastChild.type === "template_string") {
    +			return lastChild;
    +		}
    +	},
    +});
    +

    The language callback would then be called with every call_expression node in the syntax tree. In the example above, it would retrieve the first child of the call_expression, which is an identifier with the name "html". The callback would then return the string "html".

    The content callback would then be called with the same call_expression node and return the template_string node within the call_expression node.

    In order to parse the HTML within the template string, the HTML grammar file would need to specify an injectionRegex:

    injectionRegex: 'html|HTML'
    +

    Code Folding

    The next field in the grammar file, folds, controls code folding. Its value is an array of fold pattern objects. Fold patterns are used to decide whether or not a syntax node can be folded, and if so, where the fold should start and end. Here are some example fold patterns:

    folds: [
    +
    +  # All \`comment\` nodes are foldable. By default, the fold starts at
    +  # the end of the node's first line, and ends at the beginning
    +  # of the node's last line.
    +  {
    +    type: 'comment'
    +  }
    +
    +  # \`if_statement\` nodes are foldable if they contain an anonymous
    +  # "then" token and either an \`elif_clause\` or \`else_clause\` node.
    +  # The fold starts at the end of the "then" token and ends at the
    +  # \`elif_clause\` or \`else_clause\`.
    +  {
    +    type: 'if_statement',
    +    start: {type: '"then"'}
    +    end: {type: ['elif_clause', 'else_clause']}
    +  }
    +
    +  # Any node that starts with an anonymous "(" token and ends with
    +  # an anonymous ")" token is foldable. The fold starts after the
    +  # "(" and ends before the ")".
    +  {
    +    start: {type: '"("', index: 0},
    +    end: {type: '")"', index: -1}
    +  }
    +]
    +

    Fold patterns can have one or more of the following fields:

    • type - A string or array of strings. In order to be foldable according to this pattern, a syntax node's type must match one of these strings.
    • start - An object that is used to identify a child node after which the fold should start. The object can have one or both of the following fields:
      • type - A string or array of strings. To start a fold, a child node's type must match one of these strings.
      • index - a number that's used to select a specific child according to its index. Negative values are interpreted as indices relative the last child, so that -1 means the last child.
    • end - An object that is used to identify a child node before which the fold should end. It has the same structure as the start object.

    Comments

    The last field in the grammar file, comments, controls the behavior of Atom's Editor: Toggle Line Comments command. Its value is an object with a start field and an optional end field. The start field is a string that should be prepended to or removed from lines in order to comment or un-comment them.

    In JavaScript, it looks like this:

    comments:
    +  start: '// '
    +

    The end field should be used for languages that only support block comments, not line comments. If present, it will be appended to or removed from the end of the last selected line in order to comment or un-comment the selection.

    In CSS, it would look like this:

    comments:
    +  start: '/* '
    +  end: ' */'
    +

    Example Packages

    More examples of all of these features can be found in the Tree-sitter grammars bundled with Atom:

    `,23),se={href:"https://github.com/atom/language-shellscript",target:"_blank",rel:"noopener noreferrer"},ae={href:"https://github.com/atom/language-c",target:"_blank",rel:"noopener noreferrer"},te={href:"https://github.com/atom/language-go",target:"_blank",rel:"noopener noreferrer"},oe={href:"https://github.com/atom/language-html",target:"_blank",rel:"noopener noreferrer"},ie={href:"https://github.com/atom/language-javascript",target:"_blank",rel:"noopener noreferrer"},re={href:"https://github.com/atom/language-python",target:"_blank",rel:"noopener noreferrer"},le={href:"https://github.com/atom/language-ruby",target:"_blank",rel:"noopener noreferrer"},ce={href:"https://github.com/atom/language-typescript",target:"_blank",rel:"noopener noreferrer"};function pe(de,ue){const s=l("ExternalLinkIcon");return i(),r("div",null,[p,e("p",null,[n("Atom's syntax highlighting and code folding system is powered by "),e("a",d,[n("Tree-sitter"),a(s)]),n(". Tree-sitter parsers create and maintain full "),e("a",u,[h,a(s)]),n(" representing your code.")]),m,e("p",null,[n("Tree-sitter generates parsers based on "),e("a",g,[n("context-free grammars"),a(s)]),n(" that are typically written in JavaScript. The generated parsers are C libraries that can be used in other applications as well as Atom.")]),e("p",null,[n("They can also be developed and tested at the command line, separately from Atom. Tree-sitter has "),e("a",v,[n("its own documentation page"),a(s)]),n(" on how to create these parsers. The "),e("a",f,[n("Tree-sitter GitHub organization"),a(s)]),n(" also contains a lot of example parsers that you can learn from, each in its own repository.")]),e("p",null,[n("Once you have created a parser, you need to publish it to "),e("a",k,[n("the NPM registry"),a(s)]),n(" to use it in Atom. To do this, make sure you have a "),b,n(" and "),y,n(" in your parser's "),_,n(":")]),x,e("ul",null,[w,T,e("li",null,[q,n(" - The name of the parser node module that will be used for parsing. This string will be passed directly to "),e("a",S,[j,a(s)]),n(" in order to load the parser.")]),A]),C,e("p",null,[n("Note that in this selector, we're using the "),e("a",I,[n("immediate child combinator"),a(s)]),n(" ("),N,n("). Arbitrary descendant selectors without this combinator (for example "),E,n(", which would match any "),H,n(" occurring anywhere within a "),L,n(") are currently not supported.")]),M,e("p",null,[n("You can use the "),e("a",R,[F,n(" pseudo-class"),a(s)]),n(" to select nodes based on their order within their parent. For example, this example selects "),P,n(" nodes which are the fourth (zero-indexed) child of a "),z,n(" node.")]),B,e("p",null,[n("Finally, you can use double-quoted strings in the selectors to select "),G,n(" tokens in the syntax tree, like "),J,n(" and "),O,n(". See "),e("a",U,[n("the Tree-sitter documentation"),a(s)]),n(" for more information about named vs anonymous tokens.")]),V,e("p",null,[n("If multiple selectors in the "),W,n(" object match a node, the node's classes will be decided based on the "),e("a",Y,[n("most specific"),a(s)]),n(" selector. Note that the "),$,n(" and "),D,n(" rules do "),Z,n(" affect specificity, so you may need to supply the same "),K,n(" or "),Q,n(" rules for multiple selectors to ensure that they take precedence over other selectors. You can use the same selector multiple times in a scope mapping, within different comma-separated keys:")]),X,e("p",null,[n("For example, in JavaScript, "),e("a",ee,[n("tagged template literals"),a(s)]),n(" sometimes contain code written in a different language, and the name of the language is often used in the 'tag' function, as shown in this example:")]),ne,e("ul",null,[e("li",null,[e("a",se,[n("Bash"),a(s)])]),e("li",null,[e("a",ae,[n("C"),a(s)])]),e("li",null,[e("a",te,[n("Go"),a(s)])]),e("li",null,[e("a",oe,[n("HTML"),a(s)])]),e("li",null,[e("a",ie,[n("JavaScript"),a(s)])]),e("li",null,[e("a",re,[n("Python"),a(s)])]),e("li",null,[e("a",le,[n("Ruby"),a(s)])]),e("li",null,[e("a",ce,[n("TypeScript"),a(s)])])])])}const me=o(c,[["render",pe],["__file","creating-a-grammar.html.vue"]]);export{me as default}; diff --git a/assets/creating-a-grammar.html.741fc286.js b/assets/creating-a-grammar.html.741fc286.js new file mode 100644 index 0000000000..4800aaab36 --- /dev/null +++ b/assets/creating-a-grammar.html.741fc286.js @@ -0,0 +1,94 @@ +import{_ as o,o as i,c as r,e as l,a as e,b as n,d as a,f as t,r as c}from"./app.0e1565ce.js";const p={},d=e("h2",{id:"creating-a-grammar",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#creating-a-grammar","aria-hidden":"true"},"#"),n(" Creating a Grammar")],-1),u={href:"http://tree-sitter.github.io/tree-sitter",target:"_blank",rel:"noopener noreferrer"},h={href:"https://en.wikipedia.org/wiki/Abstract_syntax_tree",target:"_blank",rel:"noopener noreferrer"},m=e("em",null,"syntax trees",-1),g=t('

    This syntax tree gives Pulsar a comprehensive understanding of the structure of your code, which has several benefits:

    1. Syntax highlighting will not break because of formatting changes.
    2. Code folding will work regardless of how your code is indented.
    3. Editor features can operate on the syntax tree. For instance, the Select Larger Syntax Node and Select Smaller Syntax Node allow you to select conceptually larger and smaller chunks of your code.
    4. Community packages can use the syntax tree to manipulate code intelligently.

    Tree-sitter grammars are relatively new. Many languages in Pulsar are still supported by TextMate grammars, though we intend to phase these out over time.

    If you're adding support for a new language, you're in the right place!

    Getting Started

    There are two components required to use Tree-sitter in Pulsar: a parser and a grammar file.

    The Parser

    ',7),v={href:"https://en.wikipedia.org/wiki/Context-free_grammar",target:"_blank",rel:"noopener noreferrer"},k={href:"http://tree-sitter.github.io/tree-sitter/creating-parsers",target:"_blank",rel:"noopener noreferrer"},f={href:"https://github.com/tree-sitter",target:"_blank",rel:"noopener noreferrer"},b={href:"https://npmjs.com",target:"_blank",rel:"noopener noreferrer"},y=e("code",null,"name",-1),_=e("code",null,"version",-1),x=e("code",null,"package.json",-1),w=t(`
    {
    +  "name": "tree-sitter-mylanguage",
    +  "version": "0.0.1",
    +  // ...
    +}
    +

    then run the command npm publish.

    The Package

    Once you have a Tree-sitter parser that is available on npm, you can use it in your Pulsar package. Packages with grammars are, by convention, always named starting with language. You'll need a folder with a package.json, a grammars subdirectory, and a single json or cson file in the grammars directory, which can be named anything.

    language-mylanguage
    +\u251C\u2500\u2500 LICENSE
    +\u251C\u2500\u2500 README.md
    +\u251C\u2500\u2500 grammars
    +\u2502   \u2514\u2500\u2500 mylanguage.cson
    +\u2514\u2500\u2500 package.json
    +

    The Grammar File

    The mylanguage.cson file specifies how Pulsar should use the parser you created.

    Basic Fields

    It starts with some required fields:

    name: 'My Language'
    +scopeName: 'mylanguage'
    +type: 'tree-sitter'
    +parser: 'tree-sitter-mylanguage'
    +
    `,10),T=e("li",null,[e("code",null,"scopeName"),n(" - A unique, stable identifier for the language. Pulsar users will use this in configuration files if they want to specify custom configuration based on the language.")],-1),q=e("li",null,[e("code",null,"name"),n(" - A human readable name for the language.")],-1),S=e("code",null,"parser",-1),j={href:"https://nodejs.org/api/modules.html#modules_require",target:"_blank",rel:"noopener noreferrer"},C=e("code",null,"require()",-1),I=e("li",null,[e("code",null,"type"),n(" - This should have the value "),e("code",null,"tree-sitter"),n(" to indicate to Pulsar that this is a Tree-sitter grammar and not a "),e("a",{href:"#creating-a-legacy-textmate-grammar"},"TextMate grammar"),n(".")],-1),A=t(`

    Language Recognition

    Next, the file should contain some fields that indicate to Pulsar when this language should be used. These fields are all optional.

    • fileTypes - An array of filename suffixes. The grammar will be used for files whose names end with one of these suffixes. Note that the suffix may be an entire filename.
    • firstLineRegex - A regex pattern that will be tested against the first line of the file. The grammar will be used if this regex matches.
    • contentRegex - A regex pattern that will be tested against the contents of the file in order to break ties in cases where multiple grammars matched the file using the above two criteria. If the contentRegex matches, this grammar will be preferred over another grammar with no contentRegex. If the contentRegex does not match, a grammar with no contentRegex will be preferred over this one.

    Syntax Highlighting

    The HTML classes that Pulsar uses for syntax highlighting do not correspond directly to nodes in the syntax tree. Instead, Tree-sitter grammar files specify scope mappings that specify which classes should be applied to which syntax nodes. The scopes object controls these scope mappings. Its keys are CSS selectors that select nodes in the syntax tree. Its values can be of several different types.

    Here is a simple example:

    scopes:
    +  'call_expression > identifier': 'entity.name.function'
    +

    This entry means that, in the syntax tree, any identifier node whose parent is a call_expression should be highlighted using three classes: syntax--entity, syntax--name, and syntax--function.

    `,8),P={href:"https://developer.mozilla.org/en-US/docs/Web/CSS/Child_selectors",target:"_blank",rel:"noopener noreferrer"},N=e("code",null,">",-1),E=e("code",null,"'call_expression identifier'",-1),H=e("code",null,"identifier",-1),L=e("code",null,"call_expression",-1),M=t(`

    Advanced Selectors

    The keys of the scopes object can also contain multiple CSS selectors, separated by commas, similar to CSS files. The triple-quote syntax in CSON makes it convenient to write keys like this on multiple lines:

    scopes:
    +  '''
    +  function_declaration > identifier,
    +  call_expression > identifier,
    +  call_expression > field_expression > field_identifier
    +  ''': 'entity.name.function'
    +
    `,3),R={href:"https://developer.mozilla.org/en-US/docs/Web/CSS/:nth-child",target:"_blank",rel:"noopener noreferrer"},F=e("code",null,":nth-child",-1),z=e("code",null,"identifier",-1),B=e("code",null,"singleton_method",-1),G=t(`
    scopes:
    +  'singleton_method > identifier:nth-child(3)': 'entity.name.function'
    +
    `,1),J=e("em",null,"anonymous",-1),O=e("code",null,"(",-1),V=e("code",null,":",-1),U={href:"http://tree-sitter.github.io/tree-sitter/using-parsers#named-vs-anonymous-nodes",target:"_blank",rel:"noopener noreferrer"},W=t('
    scopes:\n  '''\n    "*",\n    "/",\n    "+",\n    "-"\n  ''': 'keyword.operator'\n

    Text-based Mappings

    You can also apply different classes to a syntax node based on its text. Here are some examples:

    scopes:\n\n  # Apply the classes `syntax--builtin` and `syntax--variable` to all\n  # `identifier` nodes whose text is `require`.\n  'identifier': {exact: 'require', scopes: 'builtin.variable'},\n\n  # Apply the classes `syntax--type` and `syntax--integer` to all\n  # `primitive_type` nodes whose text starts with `int` or `uint`.\n  'primitive_type': {match: /^u?int/, scopes: 'type.integer'},\n\n  # Apply the classes `syntax--builtin`, `syntax--class`, and\n  # `syntax--name` to `constant` nodes with the text `Array`,\n  # `Hash` and `String`. For all other `constant` nodes, just\n  # apply the classes `syntax--class` and `syntax--name`.\n  'constant': [\n    {match: '^(Array|Hash|String)$', scopes: 'builtin.class.name'},\n    'class.name'\n  ]\n

    In total there are four types of values that can be associated with selectors in scopes:

    • Strings - Each class name in the dot-separated string will be prefixed with syntax-- and applied to the selected node.
    • Objects with the keys exact and scopes - If the node's text equals the exact string, the scopes string will be used as described above.
    • Objects with the keys match and scopes - If the node's text matches the match regex pattern, the scopes string will be used as described above.
    • Arrays - The elements of the array will be processed from beginning to end. The first element that matches the selected node will be used as describe above.

    Specificity

    ',7),Y=e("code",null,"scopes",-1),$={href:"https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity",target:"_blank",rel:"noopener noreferrer"},D=e("code",null,"exact",-1),Z=e("code",null,"match",-1),K=e("em",null,"not",-1),Q=e("code",null,"exact",-1),X=e("code",null,"match",-1),ee=t(`
    scopes:
    +  'call_expression > identifier': 'entity.name.function'
    +
    +  # If we did not include the second selector here, then this rule
    +  # would not apply to identifiers inside of call_expressions,
    +  # because the selector \`call_expression > identifier\` is more
    +  # specific than the selector \`identifier\`.
    +  'identifier, call_expression > identifier': [
    +    {exact: 'require', scopes: 'builtin.variable'},
    +    {match: '^[A-Z]', scopes: 'constructor'},
    +  ]
    +

    Language Injection

    Sometimes, a source file can contain code written in several different languages. Tree-sitter grammars support this situation using a two-part process called language injection. First, an 'outer' language must define an injection point - a set of syntax nodes whose text can be parsed using a different language, along with some logic for guessing the name of the other language that should be used. Second, an 'inner' language must define an injectionRegex - a regex pattern that will be tested against the language name provided by the injection point.

    `,3),ne={href:"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#Tagged_templates",target:"_blank",rel:"noopener noreferrer"},se=t(`
    // HTML in a template literal
    +const htmlContent = html\`<div>Hello \${name}</div>\`;
    +

    The tree-sitter-javascript parser parses this tagged template literal as a call_expression with two children: an identifier and a template_literal:

    (call_expression
    +  (identifier)
    +  (template_literal
    +    (interpolation
    +      (identifier))))
    +

    Here is an injection point that would allow syntax highlighting inside of template literals:

    atom.grammars.addInjectionPoint("source.js", {
    +	type: "call_expression",
    +
    +	language(callExpression) {
    +		const { firstChild } = callExpression;
    +		if (firstChild.type === "identifier") {
    +			return firstChild.text;
    +		}
    +	},
    +
    +	content(callExpression) {
    +		const { lastChild } = callExpression;
    +		if (lastChild.type === "template_string") {
    +			return lastChild;
    +		}
    +	},
    +});
    +

    The language callback would then be called with every call_expression node in the syntax tree. In the example above, it would retrieve the first child of the call_expression, which is an identifier with the name "html". The callback would then return the string "html".

    The content callback would then be called with the same call_expression node and return the template_string node within the call_expression node.

    In order to parse the HTML within the template string, the HTML grammar file would need to specify an injectionRegex:

    injectionRegex: 'html|HTML'
    +

    Code Folding

    The next field in the grammar file, folds, controls code folding. Its value is an array of fold pattern objects. Fold patterns are used to decide whether or not a syntax node can be folded, and if so, where the fold should start and end. Here are some example fold patterns:

    folds: [
    +
    +  # All \`comment\` nodes are foldable. By default, the fold starts at
    +  # the end of the node's first line, and ends at the beginning
    +  # of the node's last line.
    +  {
    +    type: 'comment'
    +  }
    +
    +  # \`if_statement\` nodes are foldable if they contain an anonymous
    +  # "then" token and either an \`elif_clause\` or \`else_clause\` node.
    +  # The fold starts at the end of the "then" token and ends at the
    +  # \`elif_clause\` or \`else_clause\`.
    +  {
    +    type: 'if_statement',
    +    start: {type: '"then"'}
    +    end: {type: ['elif_clause', 'else_clause']}
    +  }
    +
    +  # Any node that starts with an anonymous "(" token and ends with
    +  # an anonymous ")" token is foldable. The fold starts after the
    +  # "(" and ends before the ")".
    +  {
    +    start: {type: '"("', index: 0},
    +    end: {type: '")"', index: -1}
    +  }
    +]
    +

    Fold patterns can have one or more of the following fields:

    • type - A string or array of strings. In order to be foldable according to this pattern, a syntax node's type must match one of these strings.
    • start - An object that is used to identify a child node after which the fold should start. The object can have one or both of the following fields:
      • type - A string or array of strings. To start a fold, a child node's type must match one of these strings.
      • index - a number that's used to select a specific child according to its index. Negative values are interpreted as indices relative the last child, so that -1 means the last child.
    • end - An object that is used to identify a child node before which the fold should end. It has the same structure as the start object.

    Comments

    The last field in the grammar file, comments, controls the behavior of Pulsar's Editor: Toggle Line Comments command. Its value is an object with a start field and an optional end field. The start field is a string that should be prepended to or removed from lines in order to comment or uncomment them.

    In JavaScript, it looks like this:

    comments:
    +  start: '// '
    +

    The end field should be used for languages that only support block comments, not line comments. If present, it will be appended to or removed from the end of the last selected line in order to comment or un-comment the selection.

    In CSS, it would look like this:

    comments:
    +  start: '/* '
    +  end: ' */'
    +

    Example Packages

    More examples of all of these features can be found in the Tree-sitter grammars bundled with Pulsar:

    `,23),ae={href:"https://github.com/pulsar-edit/pulsar/tree/master/packages/language-shellscript",target:"_blank",rel:"noopener noreferrer"},te={href:"https://github.com/pulsar-edit/pulsar/tree/master/packages/language-c",target:"_blank",rel:"noopener noreferrer"},oe={href:"https://github.com/pulsar-edit/pulsar/tree/master/packages/language-go",target:"_blank",rel:"noopener noreferrer"},ie={href:"https://github.com/pulsar-edit/pulsar/tree/master/packages/language-html",target:"_blank",rel:"noopener noreferrer"},re={href:"https://github.com/pulsar-edit/pulsar/tree/master/packages/language-javascript",target:"_blank",rel:"noopener noreferrer"},le={href:"https://github.com/pulsar-edit/pulsar/tree/master/packages/language-python",target:"_blank",rel:"noopener noreferrer"},ce={href:"https://github.com/pulsar-edit/pulsar/tree/master/packages/language-ruby",target:"_blank",rel:"noopener noreferrer"},pe={href:"https://github.com/pulsar-edit/pulsar/tree/master/packages/language-typescript",target:"_blank",rel:"noopener noreferrer"};function de(ue,he){const s=c("ExternalLinkIcon");return i(),r("div",null,[d,l("This may need to be thoroughly reworked if we change the tree-sitter implementation"),e("p",null,[n("Pulsar's syntax highlighting and code folding system is powered by "),e("a",u,[n("Tree-sitter"),a(s)]),n(". Tree-sitter parsers create and maintain full "),e("a",h,[m,a(s)]),n(" representing your code.")]),g,e("p",null,[n("Tree-sitter generates parsers based on "),e("a",v,[n("context-free grammars"),a(s)]),n(" that are typically written in JavaScript. The generated parsers are C libraries that can be used in other applications as well as Pulsar.")]),e("p",null,[n("They can also be developed and tested at the command line, separately from Pulsar. Tree-sitter has "),e("a",k,[n("its own documentation page"),a(s)]),n(" on how to create these parsers. The "),e("a",f,[n("Tree-sitter GitHub organization"),a(s)]),n(" also contains a lot of example parsers that you can learn from, each in its own repository.")]),e("p",null,[n("Once you have created a parser, you need to publish it to "),e("a",b,[n("the NPM registry"),a(s)]),n(" to use it in Pulsar. To do this, make sure you have a "),y,n(" and "),_,n(" in your parser's "),x,n(":")]),w,e("ul",null,[T,q,e("li",null,[S,n(" - The name of the parser node module that will be used for parsing. This string will be passed directly to "),e("a",j,[C,a(s)]),n(" in order to load the parser.")]),I]),A,e("p",null,[n("Note that in this selector, we're using the "),e("a",P,[n("immediate child combinator"),a(s)]),n(" ("),N,n("). Arbitrary descendant selectors without this combinator (for example "),E,n(", which would match any "),H,n(" occurring anywhere within a "),L,n(") are currently not supported.")]),M,e("p",null,[n("You can use the "),e("a",R,[F,n(" pseudo-class"),a(s)]),n(" to select nodes based on their order within their parent. For example, this example selects "),z,n(" nodes which are the fourth (zero-indexed) child of a "),B,n(" node.")]),G,e("p",null,[n("Finally, you can use double-quoted strings in the selectors to select "),J,n(" tokens in the syntax tree, like "),O,n(" and "),V,n(". See "),e("a",U,[n("the Tree-sitter documentation"),a(s)]),n(" for more information about named vs anonymous tokens.")]),W,e("p",null,[n("If multiple selectors in the "),Y,n(" object match a node, the node's classes will be decided based on the "),e("a",$,[n("most specific"),a(s)]),n(" selector. Note that the "),D,n(" and "),Z,n(" rules do "),K,n(" affect specificity, so you may need to supply the same "),Q,n(" or "),X,n(" rules for multiple selectors to ensure that they take precedence over other selectors. You can use the same selector multiple times in a scope mapping, within different comma-separated keys:")]),ee,e("p",null,[n("For example, in JavaScript, "),e("a",ne,[n("tagged template literals"),a(s)]),n(" sometimes contain code written in a different language, and the name of the language is often used in the 'tag' function, as shown in this example:")]),se,e("ul",null,[e("li",null,[e("a",ae,[n("Bash"),a(s)])]),e("li",null,[e("a",te,[n("C"),a(s)])]),e("li",null,[e("a",oe,[n("Go"),a(s)])]),e("li",null,[e("a",ie,[n("HTML"),a(s)])]),e("li",null,[e("a",re,[n("JavaScript"),a(s)])]),e("li",null,[e("a",le,[n("Python"),a(s)])]),e("li",null,[e("a",ce,[n("Ruby"),a(s)])]),e("li",null,[e("a",pe,[n("TypeScript"),a(s)])])])])}const ge=o(p,[["render",de],["__file","creating-a-grammar.html.vue"]]);export{ge as default}; diff --git a/assets/creating-a-grammar.html.d2553095.js b/assets/creating-a-grammar.html.d2553095.js new file mode 100644 index 0000000000..aa28158eda --- /dev/null +++ b/assets/creating-a-grammar.html.d2553095.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-43bb93f6","path":"/docs/launch-manual/sections/core-hacking/sections/creating-a-grammar.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Creating a Grammar","slug":"creating-a-grammar","link":"#creating-a-grammar","children":[{"level":3,"title":"Getting Started","slug":"getting-started","link":"#getting-started","children":[]},{"level":3,"title":"The Parser","slug":"the-parser","link":"#the-parser","children":[]},{"level":3,"title":"The Package","slug":"the-package","link":"#the-package","children":[]},{"level":3,"title":"The Grammar File","slug":"the-grammar-file","link":"#the-grammar-file","children":[]},{"level":3,"title":"Basic Fields","slug":"basic-fields","link":"#basic-fields","children":[]},{"level":3,"title":"Language Recognition","slug":"language-recognition","link":"#language-recognition","children":[]},{"level":3,"title":"Syntax Highlighting","slug":"syntax-highlighting","link":"#syntax-highlighting","children":[]},{"level":3,"title":"Language Injection","slug":"language-injection","link":"#language-injection","children":[]},{"level":3,"title":"Code Folding","slug":"code-folding","link":"#code-folding","children":[]},{"level":3,"title":"Comments","slug":"comments","link":"#comments","children":[]},{"level":3,"title":"Example Packages","slug":"example-packages","link":"#example-packages","children":[]}]}],"git":{"updatedTime":1670466847000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":7.46,"words":2237},"filePathRelative":"docs/launch-manual/sections/core-hacking/sections/creating-a-grammar.md"}');export{e as data}; diff --git a/assets/creating-a-legacy-textmate-grammar.html.87a1089f.js b/assets/creating-a-legacy-textmate-grammar.html.87a1089f.js new file mode 100644 index 0000000000..8f7bc0c43a --- /dev/null +++ b/assets/creating-a-legacy-textmate-grammar.html.87a1089f.js @@ -0,0 +1,94 @@ +import{_ as o,o as r,c as p,a as n,b as e,d as s,e as i,f as t,r as l}from"./app.0e1565ce.js";const c={},d=t('

    Creating a Legacy TextMate Grammar

    Atom's syntax highlighting can be powered by two types of grammars. If you're adding support for a new language, the preferred way is to create a Tree-sitter grammar. Tree-sitter grammars have better performance and provide support for more editor features, such as the Select Larger Syntax Node command.

    This section describes the Atom's legacy support for TextMate grammars.

    TextMate grammars are supported by several popular text editors. They provide a set of regex (regular expression) patterns which are assigned scopes. These scopes are then turned into the CSS classes that you can target in syntax themes.

    Note

    Note: This tutorial is a work in progress.

    Getting Started

    TextMate Grammars depend heavily on regexes, and you should be comfortable with interpreting and writing regexes before continuing. Note that Atom uses the Oniguruma engine, which is very similar to the PCRE or Perl regex engines. Here are some resources to help you out:

    • https://www.regular-expressions.info/tutorial.html provides a comprehensive regex tutorial
    • https://www.rexegg.com/regex-quickstart.html contains a cheat sheet for various regex expressions
    • https://regex101.com/ or https://regexr.com/ allows live prototyping
    • https://github.com/kkos/oniguruma/blob/master/doc/RE the docs for the Oniguruma regex engine
    ',8),u={href:"https://github.com/bevry/cson#what-is-cson",target:"_blank",rel:"noopener noreferrer"},h={href:"https://www.json.org/",target:"_blank",rel:"noopener noreferrer"},g=t('

    Create the Package

    To get started, press Cmd+Shift+PCtrl+Shift+P and start typing "Generate Package" to generate a new grammar package. Select "Package Generator: Generate Package," and you'll be asked for the path where your package will be created. Let's call ours language-flight-manual.

    Tip

    Tip: Grammar packages should start with language-.

    ',3),m=n("code",null,"keymaps",-1),k=n("code",null,"lib",-1),v=n("code",null,"menus",-1),b=n("code",null,"styles",-1),y=n("code",null,"package.json",-1),f=n("code",null,"activationCommands",-1),w=n("code",null,"grammars",-1),_=n("code",null,"flight-manual.cson",-1),x={href:"https://gist.github.com/DamnedScholar/622926bcd222eb1ddc483d12103fd315",target:"_blank",rel:"noopener noreferrer"},T=t(`

    scopeName is the root scope of your package. This should generally describe what language your grammar package is highlighting; for example, language-javascript's scopeName is source.js and language-html's is text.html.basic. Name it source.flight-manual for now.

    name is the user-friendly name that is displayed in places like the status bar or the grammar selector. Again, this name should describe what the grammar package is highlighting. Rename it to Flight Manual.

    fileTypes is an array of filetypes that language-flight-manual should highlight. We're interested in highlighting the Flight Manual's Markdown files, so add the md extension to the list and remove the others.

    patterns contains the array of regex patterns that will determine how the file is tokenized.

    Adding Patterns

    To start, let's add a basic pattern to tokenize the words Flight Manual whenever they show up. Your regex should look like \\bFlight Manual\\b. Here's what your patterns block should look like:

    'patterns': [
    +  {
    +    'match': '\\\\bFlight Manual\\\\b'
    +    'name': 'entity.other.flight-manual'
    +  }
    +]
    +
    `,7),M=n("code",null,"match",-1),N=n("code",null,"name",-1),S={href:"https://manual.macromates.com/en/language_grammars",target:"_blank",rel:"noopener noreferrer"},C=t(`

    Tip

    Tip: All scopes should end with the portion of the root scopeName after the leading source or text. In our case, all scopes should end with flight-manual.

    Note

    Note: Astute readers may have noticed that the \\b was changed to \\\\b with two backslashes and not one. This is because CSON processes the regex string before handing it to Oniguruma, so all backslashes need to be escaped twice.

    But what if we wanted to apply different scopes to Flight and Manual? This is possible by adding capture groups to the regex and then referencing those capture groups in a new capture property. For example:

    'match': '\\\\b(Flight) (Manual)\\\\b'
    +'name': 'entity.other.flight-manual'
    +'captures':
    +  '1':
    +    'name': 'keyword.other.flight.flight-manual'
    +  '2':
    +    'name': 'keyword.other.manual.flight-manual'
    +

    This will assign the scope keyword.other.flight.flight-manual to Flight, keyword.other.manual.flight-manual to Manual, and entity.other.flight-manual to the overarching Flight Manual.

    Begin/End Patterns

    Now let's say we want to tokenize the ::: note Note blocks that occur in Flight Manual files. Our previous two examples used match, but one limit of match is that it can only match single lines. ::: note Note blocks, on the other hand, can span multiple lines. For these cases, you can use the begin/end keys. Once the regex in the begin key is matched, tokenization will continue until the end pattern is reached.

    'begin': '({{)(#note)(}})'
    +'beginCaptures':
    +  '0': # The 0 capture contains the entire match
    +    'name': 'meta.block.start.flight-manual'
    +  '1':
    +    'name': 'punctuation.definition.block.flight-manual'
    +  '2':
    +    'name': 'keyword.note.flight-manual'
    +  '3':
    +    'name': 'punctuation.definition.block.flight-manual'
    +'end': '({{)(/note)(}})'
    +'endCaptures':
    +  '0':
    +    'name': 'meta.block.end.flight-manual'
    +  '1':
    +    'name': 'punctuation.definition.block.flight-manual'
    +  '2':
    +    'name': 'keyword.note.flight-manual'
    +  '3':
    +    'name': 'punctuation.definition.block.flight-manual'
    +'name': 'meta.block.note.flight-manual'
    +

    Tip

    Tip: Get into the habit of providing punctuation scopes early on. It's much less effort than having to go back and rewriting all your patterns to support punctuation scopes when your grammar starts to get a bit longer!

    Awesome, we have our first multiline pattern! However, if you've been following along and playing around in your own .md file, you may have noticed that Flight Manual doesn't receive any scopes inside a note block. A begin/end block is essentially a subgrammar of its own: once it starts matching, it will only match its own subpatterns until the end pattern is reached. Since we haven't defined any subpatterns, then clearly nothing will be matched inside of a note block. Let's fix that!

    'begin': '({{)(#note)(}})'
    +'beginCaptures':
    +  '0': # The 0 capture contains the entire match
    +    'name': 'meta.block.start.flight-manual'
    +  '1':
    +    'name': 'punctuation.definition.block.flight-manual'
    +  '2':
    +    'name': 'keyword.note.flight-manual'
    +  '3':
    +    'name': 'punctuation.definition.block.flight-manual'
    +'end': '({{)(/note)(}})'
    +'endCaptures':
    +  '0':
    +    'name': 'meta.block.end.flight-manual'
    +  '1':
    +    'name': 'punctuation.definition.block.flight-manual'
    +  '2':
    +    'name': 'keyword.note.flight-manual'
    +  '3':
    +    'name': 'punctuation.definition.block.flight-manual'
    +'name': 'meta.block.note.flight-manual'
    +'patterns': [
    +  {
    +    'match': '\\\\b(Flight) (Manual)\\\\b'
    +    'name': 'entity.other.flight-manual'
    +    'captures':
    +      '1':
    +        'name': 'keyword.other.flight.flight-manual'
    +      '2':
    +        'name': 'keyword.other.manual.flight-manual'
    +  }
    +]
    +

    There. With the patterns block, Flight Manual should now receive the proper scopes.

    Repositories and the Include keyword, or how to avoid duplication

    At this point, note blocks are looking pretty nice, as is the Flight Manual keyword, but the rest of the file is noticeably lacking any form of Markdown syntax highlighting. Is there a way to include the GitHub-Flavored Markdown grammar without copying and pasting everything over? This is where the include keyword comes in. include allows you to include other patterns, even from other grammars! language-gfm's scopeName is source.gfm, so let's include that. Our patterns block should now look like the following:

    'patterns': [
    +  {
    +    'include': 'source.gfm'
    +  }
    +  {
    +    # Flight Manual pattern
    +  }
    +  {
    +    # Note begin/end pattern
    +  }
    +]
    +

    However, including source.gfm has led to another problem: note blocks still don't have any Markdown highlighting! The quick fix would be to add the include pattern to the note's pattern block as well, but now we're duplicating two patterns. You can imagine that as this grammar grows it'll quickly become inefficient to keep copying each new global pattern over to the note pattern as well. Therefore, include helpfully recognizes the special $self scope. $self automatically includes all the top-level patterns of the current grammar. The note block can then be simplified to the following:

    'begin': '({{)(#note)(}})'
    +# beginCaptures
    +'end': '({{)(/note)(}})'
    +# endCaptures
    +'name': 'meta.block.note.flight-manual'
    +'patterns': [
    +  {
    +    'include': '$self'
    +  }
    +]
    +

    Where to Go from Here

    There are several good resources out there that help when writing a grammar. The following is a list of some particularly useful ones (some have been linked to in the sections above as well).

    `,19),F={href:"https://gist.github.com/DamnedScholar/622926bcd222eb1ddc483d12103fd315",target:"_blank",rel:"noopener noreferrer"},A={href:"https://gist.github.com/Aerijo/b8c82d647db783187804e86fa0a604a1",target:"_blank",rel:"noopener noreferrer"},G=n("li",null,"http://www.apeth.com/nonblog/stories/textmatebundle.html. A blog of a programmer's experience writing a grammar package for TextMate.",-1),P={href:"https://github.com/kkos/oniguruma/blob/master/doc/RE",target:"_blank",rel:"noopener noreferrer"},O={href:"http://manual.macromates.com/en/language_grammars.html",target:"_blank",rel:"noopener noreferrer"},I={href:"https://github.com/atom/first-mate",target:"_blank",rel:"noopener noreferrer"},L=n("code",null,"first-mate",-1),q={href:"https://github.com/atom/language-python",target:"_blank",rel:"noopener noreferrer"},E={href:"https://github.com/atom/language-javascript",target:"_blank",rel:"noopener noreferrer"},j={href:"https://github.com/atom/language-html",target:"_blank",rel:"noopener noreferrer"},H={href:"https://github.com/atom?utf8=%E2%9C%93&q=language&type=source&language=",target:"_blank",rel:"noopener noreferrer"};function W(z,B){const a=l("ExternalLinkIcon");return r(),p("div",null,[d,n("p",null,[e("Grammar files are written in the "),n("a",u,[e("CSON"),s(a)]),e(" or "),n("a",h,[e("JSON"),s(a)]),e(" format. Whichever one you decide to use is up to you, but this tutorial will be written in CSON.")]),g,n("p",null,[e("The default package template creates a lot of folders that aren't needed for grammar packages. Go ahead and delete the "),m,e(", "),k,e(", "),v,e(", and "),b,e(" folders. Furthermore, in "),y,e(", remove the "),f,e(" section. Now create a new folder called "),w,e(", and inside that a file called "),_,e(". This is the main file that we will be working with - start by populating it with a "),n("a",x,[e("boilerplate template"),s(a)]),e(". Now let's go over what each key means.")]),T,n("p",null,[M,e(" is where your regex is contained, and "),N,e(" is the scope name that is to be applied to the entirety of the match. More information about scope names can be found in "),n("a",S,[e("Section 12.4 of the TextMate Manual"),s(a)]),e(".")]),C,n("ul",null,[n("li",null,[n("a",F,[e("DamnedScholar's Gist"),s(a)]),e(". Provides a template of most keys, each with a short comment explaining their function.")]),n("li",null,[n("a",A,[e("Aerijo's Gist"),s(a)]),e(". [Work in Progress] Another guide that attempts to fully explain making a grammar package for users of all levels.")]),G,n("li",null,[n("a",P,[e("Oniguruma docs"),s(a)]),e(". The documentation for the regex engine Atom uses.")]),n("li",null,[n("a",O,[e("TextMate Section 12"),s(a)]),e(". Atom uses the same principles as laid out here, including the list of acceptable scopes.")]),n("li",null,[n("a",I,[L,s(a)]),e(". Not necessary to write a grammar, but a good technical reference for what Atom is doing behind the scenes.")]),n("li",null,[e("Look at any existing packages, such as the ones for "),n("a",q,[e("Python"),s(a)]),e(", "),n("a",E,[e("JavaScript"),s(a)]),e(", "),n("a",j,[e("HTML"),s(a)]),e(", "),n("a",H,[e("and more"),s(a)]),e(".")])]),i(` +TODO: +* \`repository\` and including from repository patterns +* Wrap-up +* Implement Package Generator functionality to generate a grammar +* Intermediate + advanced grammar tutorials +`)])}const V=o(c,[["render",W],["__file","creating-a-legacy-textmate-grammar.html.vue"]]);export{V as default}; diff --git a/assets/creating-a-legacy-textmate-grammar.html.895bfcba.js b/assets/creating-a-legacy-textmate-grammar.html.895bfcba.js new file mode 100644 index 0000000000..9198386422 --- /dev/null +++ b/assets/creating-a-legacy-textmate-grammar.html.895bfcba.js @@ -0,0 +1,94 @@ +import{_ as o,o as r,c as p,a as n,b as e,d as s,e as i,f as t,r as l}from"./app.0e1565ce.js";const c={},d=t('

    Creating a Legacy TextMate Grammar

    Pulsar's syntax highlighting can be powered by two types of grammars. If you're adding support for a new language, the preferred way is to create a Tree-sitter grammar. Tree-sitter grammars have better performance and provide support for more editor features, such as the Select Larger Syntax Node command.

    This section describes the Pulsar's legacy support for TextMate grammars.

    TextMate grammars are supported by several popular text editors. They provide a set of regex (regular expression) patterns which are assigned scopes. These scopes are then turned into the CSS classes that you can target in syntax themes.

    Getting Started

    TextMate Grammars depend heavily on regexes, and you should be comfortable with interpreting and writing regexes before continuing. Note that Pulsar uses the Oniguruma engine, which is very similar to the PCRE or Perl regex engines. Here are some resources to help you out:

    • https://www.regular-expressions.info/tutorial.html provides a comprehensive regex tutorial
    • https://www.rexegg.com/regex-quickstart.html contains a cheat sheet for various regex expressions
    • https://regex101.com/ or https://regexr.com/ allows live prototyping
    • https://github.com/kkos/oniguruma/blob/master/doc/RE the docs for the Oniguruma regex engine
    ',7),u={href:"https://github.com/bevry/cson#what-is-cson",target:"_blank",rel:"noopener noreferrer"},h={href:"https://www.json.org/",target:"_blank",rel:"noopener noreferrer"},g=t('

    Create the Package

    To get started, press LNX/WIN: Ctrl+Shift+P - MAC: Cmd+Shift+P and start typing "Generate Package" to generate a new grammar package. Select "Package Generator: Generate Package," and you'll be asked for the path where your package will be created. Let's call ours language-flight-manual.

    Tip

    Grammar packages should start with language-.

    ',3),m=n("code",null,"keymaps",-1),k=n("code",null,"lib",-1),v=n("code",null,"menus",-1),b=n("code",null,"styles",-1),y=n("code",null,"package.json",-1),f=n("code",null,"activationCommands",-1),w=n("code",null,"grammars",-1),_=n("code",null,"flight-manual.cson",-1),x={href:"https://gist.github.com/DamnedScholar/622926bcd222eb1ddc483d12103fd315",target:"_blank",rel:"noopener noreferrer"},T=t(`

    scopeName is the root scope of your package. This should generally describe what language your grammar package is highlighting; for example, language-javascript's scopeName is source.js and language-html's is text.html.basic. Name it source.flight-manual for now.

    name is the user-friendly name that is displayed in places like the status bar or the grammar selector. Again, this name should describe what the grammar package is highlighting. Rename it to Flight Manual.

    fileTypes is an array of filetypes that language-flight-manual should highlight. We're interested in highlighting the Flight Manual's Markdown files, so add the md extension to the list and remove the others.

    patterns contains the array of regex patterns that will determine how the file is tokenized.

    Adding Patterns

    To start, let's add a basic pattern to tokenize the words Flight Manual whenever they show up. Your regex should look like \\bFlight Manual\\b. Here's what your patterns block should look like:

    'patterns': [
    +  {
    +    'match': '\\\\bFlight Manual\\\\b'
    +    'name': 'entity.other.flight-manual'
    +  }
    +]
    +
    `,7),M=n("code",null,"match",-1),N=n("code",null,"name",-1),S={href:"https://manual.macromates.com/en/language_grammars",target:"_blank",rel:"noopener noreferrer"},C=t(`

    Tip

    All scopes should end with the portion of the root scopeName after the leading source or text. In our case, all scopes should end with flight-manual.

    Note

    Astute readers may have noticed that the \\b was changed to \\\\b with two backslashes and not one. This is because CSON processes the regex string before handing it to Oniguruma, so all backslashes need to be escaped twice.

    But what if we wanted to apply different scopes to Flight and Manual? This is possible by adding capture groups to the regex and then referencing those capture groups in a new capture property. For example:

    'match': '\\\\b(Flight) (Manual)\\\\b'
    +'name': 'entity.other.flight-manual'
    +'captures':
    +  '1':
    +    'name': 'keyword.other.flight.flight-manual'
    +  '2':
    +    'name': 'keyword.other.manual.flight-manual'
    +

    This will assign the scope keyword.other.flight.flight-manual to Flight, keyword.other.manual.flight-manual to Manual, and entity.other.flight-manual to the overarching Flight Manual.

    Begin/End Patterns

    Now let's say we want to tokenize the {{#note}} blocks that occur in Flight Manual files. Our previous two examples used match, but one limit of match is that it can only match single lines. {{#note}} blocks, on the other hand, can span multiple lines. For these cases, you can use the begin/end keys. Once the regex in the begin key is matched, tokenization will continue until the end pattern is reached.

    'begin': '({{)(#note)(}})'
    +'beginCaptures':
    +  '0': # The 0 capture contains the entire match
    +    'name': 'meta.block.start.flight-manual'
    +  '1':
    +    'name': 'punctuation.definition.block.flight-manual'
    +  '2':
    +    'name': 'keyword.note.flight-manual'
    +  '3':
    +    'name': 'punctuation.definition.block.flight-manual'
    +'end': '({{)(/note)(}})'
    +'endCaptures':
    +  '0':
    +    'name': 'meta.block.end.flight-manual'
    +  '1':
    +    'name': 'punctuation.definition.block.flight-manual'
    +  '2':
    +    'name': 'keyword.note.flight-manual'
    +  '3':
    +    'name': 'punctuation.definition.block.flight-manual'
    +'name': 'meta.block.note.flight-manual'
    +

    Tip

    Get into the habit of providing punctuation scopes early on. It's much less effort than having to go back and rewriting all your patterns to support punctuation scopes when your grammar starts to get a bit longer!

    Awesome, we have our first multiline pattern! However, if you've been following along and playing around in your own .md file, you may have noticed that Flight Manual doesn't receive any scopes inside a note block. A begin/end block is essentially a subgrammar of its own: once it starts matching, it will only match its own subpatterns until the end pattern is reached. Since we haven't defined any subpatterns, then clearly nothing will be matched inside of a note block. Let's fix that!

    'begin': '({{)(#note)(}})'
    +'beginCaptures':
    +  '0': # The 0 capture contains the entire match
    +    'name': 'meta.block.start.flight-manual'
    +  '1':
    +    'name': 'punctuation.definition.block.flight-manual'
    +  '2':
    +    'name': 'keyword.note.flight-manual'
    +  '3':
    +    'name': 'punctuation.definition.block.flight-manual'
    +'end': '({{)(/note)(}})'
    +'endCaptures':
    +  '0':
    +    'name': 'meta.block.end.flight-manual'
    +  '1':
    +    'name': 'punctuation.definition.block.flight-manual'
    +  '2':
    +    'name': 'keyword.note.flight-manual'
    +  '3':
    +    'name': 'punctuation.definition.block.flight-manual'
    +'name': 'meta.block.note.flight-manual'
    +'patterns': [
    +  {
    +    'match': '\\\\b(Flight) (Manual)\\\\b'
    +    'name': 'entity.other.flight-manual'
    +    'captures':
    +      '1':
    +        'name': 'keyword.other.flight.flight-manual'
    +      '2':
    +        'name': 'keyword.other.manual.flight-manual'
    +  }
    +]
    +

    There. With the patterns block, Flight Manual should now receive the proper scopes.

    Repositories and the Include keyword, or how to avoid duplication

    At this point, note blocks are looking pretty nice, as is the Flight Manual keyword, but the rest of the file is noticeably lacking any form of Markdown syntax highlighting. Is there a way to include the GitHub-Flavored Markdown grammar without copying and pasting everything over? This is where the include keyword comes in. include allows you to include other patterns, even from other grammars! language-gfm's scopeName is source.gfm, so let's include that. Our patterns block should now look like the following:

    'patterns': [
    +  {
    +    'include': 'source.gfm'
    +  }
    +  {
    +    # Flight Manual pattern
    +  }
    +  {
    +    # Note begin/end pattern
    +  }
    +]
    +

    However, including source.gfm has led to another problem: note blocks still don't have any Markdown highlighting! The quick fix would be to add the include pattern to the note's pattern block as well, but now we're duplicating two patterns. You can imagine that as this grammar grows it'll quickly become inefficient to keep copying each new global pattern over to the note pattern as well. Therefore, include helpfully recognizes the special $self scope. $self automatically includes all the top-level patterns of the current grammar. The note block can then be simplified to the following:

    'begin': '({{)(#note)(}})'
    +# beginCaptures
    +'end': '({{)(/note)(}})'
    +# endCaptures
    +'name': 'meta.block.note.flight-manual'
    +'patterns': [
    +  {
    +    'include': '$self'
    +  }
    +]
    +

    Where to Go from Here

    There are several good resources out there that help when writing a grammar. The following is a list of some particularly useful ones (some have been linked to in the sections above as well).

    `,19),F={href:"https://gist.github.com/DamnedScholar/622926bcd222eb1ddc483d12103fd315",target:"_blank",rel:"noopener noreferrer"},P={href:"https://gist.github.com/Aerijo/b8c82d647db783187804e86fa0a604a1",target:"_blank",rel:"noopener noreferrer"},G={href:"http://www.apeth.com/nonblog/stories/textmatebundle.html",target:"_blank",rel:"noopener noreferrer"},A={href:"https://github.com/kkos/oniguruma/blob/master/doc/RE",target:"_blank",rel:"noopener noreferrer"},O={href:"http://manual.macromates.com/en/language_grammars.html",target:"_blank",rel:"noopener noreferrer"},I={href:"https://github.com/pulsar-edit/first-mate",target:"_blank",rel:"noopener noreferrer"},L=n("code",null,"first-mate",-1),j={href:"https://github.com/pulsar-edit/pulsar/tree/master/packages/language-python",target:"_blank",rel:"noopener noreferrer"},q={href:"https://github.com/pulsar-edit/pulsar/tree/master/packages/language-javascript",target:"_blank",rel:"noopener noreferrer"},E={href:"https://github.com/pulsar-edit/pulsar/tree/master/packages/language-html",target:"_blank",rel:"noopener noreferrer"},H={href:"https://github.com/pulsar-edit/pulsar/tree/master/packages",target:"_blank",rel:"noopener noreferrer"};function W(z,B){const a=l("ExternalLinkIcon");return r(),p("div",null,[d,n("p",null,[e("Grammar files are written in the "),n("a",u,[e("CSON"),s(a)]),e(" or "),n("a",h,[e("JSON"),s(a)]),e(" format. Whichever one you decide to use is up to you, but this tutorial will be written in CSON.")]),g,n("p",null,[e("The default package template creates a lot of folders that aren't needed for grammar packages. Go ahead and delete the "),m,e(", "),k,e(", "),v,e(", and "),b,e(" folders. Furthermore, in "),y,e(", remove the "),f,e(" section. Now create a new folder called "),w,e(", and inside that a file called "),_,e(". This is the main file that we will be working with - start by populating it with a "),n("a",x,[e("boilerplate template"),s(a)]),e(". Now let's go over what each key means.")]),T,n("p",null,[M,e(" is where your regex is contained, and "),N,e(" is the scope name that is to be applied to the entirety of the match. More information about scope names can be found in "),n("a",S,[e("Section 12.4 of the TextMate Manual"),s(a)]),e(".")]),C,n("ul",null,[n("li",null,[n("a",F,[e("DamnedScholar's Gist"),s(a)]),e(". Provides a template of most keys, each with a short comment explaining their function.")]),n("li",null,[n("a",P,[e("Aerijo's Gist"),s(a)]),e(". Another guide that attempts to fully explain making a grammar package for users of all levels.")]),n("li",null,[n("a",G,[e("http://www.apeth.com/nonblog/stories/textmatebundle.html"),s(a)]),e(". A blog of a programmer's experience writing a grammar package for TextMate.")]),n("li",null,[n("a",A,[e("Oniguruma docs"),s(a)]),e(". The documentation for the regex engine Pulsar uses.")]),n("li",null,[n("a",O,[e("TextMate Section 12"),s(a)]),e(". Pulsar uses the same principles as laid out here, including the list of acceptable scopes.")]),n("li",null,[n("a",I,[L,s(a)]),e(". Not necessary to write a grammar, but a good technical reference for what Pulsar is doing behind the scenes.")]),n("li",null,[e("Look at any existing packages, such as the ones for "),n("a",j,[e("Python"),s(a)]),e(", "),n("a",q,[e("JavaScript"),s(a)]),e(", "),n("a",E,[e("HTML"),s(a)]),e(", "),n("a",H,[e("and more"),s(a)]),e(".")])]),i(` (This is left from the original Atom flight-manual) +TODO: +* \`repository\` and including from repository patterns +* Wrap-up +* Implement Package Generator functionality to generate a grammar +* Intermediate + advanced grammar tutorials +`)])}const V=o(c,[["render",W],["__file","creating-a-legacy-textmate-grammar.html.vue"]]);export{V as default}; diff --git a/assets/creating-a-legacy-textmate-grammar.html.b6783a7b.js b/assets/creating-a-legacy-textmate-grammar.html.b6783a7b.js new file mode 100644 index 0000000000..e30e857828 --- /dev/null +++ b/assets/creating-a-legacy-textmate-grammar.html.b6783a7b.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-1032ada6","path":"/docs/launch-manual/sections/core-hacking/sections/creating-a-legacy-textmate-grammar.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Creating a Legacy TextMate Grammar","slug":"creating-a-legacy-textmate-grammar","link":"#creating-a-legacy-textmate-grammar","children":[{"level":3,"title":"Getting Started","slug":"getting-started","link":"#getting-started","children":[]},{"level":3,"title":"Create the Package","slug":"create-the-package","link":"#create-the-package","children":[]},{"level":3,"title":"Adding Patterns","slug":"adding-patterns","link":"#adding-patterns","children":[]},{"level":3,"title":"Begin/End Patterns","slug":"begin-end-patterns","link":"#begin-end-patterns","children":[]},{"level":3,"title":"Repositories and the Include keyword, or how to avoid duplication","slug":"repositories-and-the-include-keyword-or-how-to-avoid-duplication","link":"#repositories-and-the-include-keyword-or-how-to-avoid-duplication","children":[]},{"level":3,"title":"Where to Go from Here","slug":"where-to-go-from-here","link":"#where-to-go-from-here","children":[]}]}],"git":{"updatedTime":1670466847000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":5.27,"words":1581},"filePathRelative":"docs/launch-manual/sections/core-hacking/sections/creating-a-legacy-textmate-grammar.md"}');export{e as data}; diff --git a/assets/creating-a-legacy-textmate-grammar.html.ed46d264.js b/assets/creating-a-legacy-textmate-grammar.html.ed46d264.js new file mode 100644 index 0000000000..edb5218c9c --- /dev/null +++ b/assets/creating-a-legacy-textmate-grammar.html.ed46d264.js @@ -0,0 +1 @@ +const a=JSON.parse('{"key":"v-41063e28","path":"/docs/atom-archive/hacking-atom/sections/creating-a-legacy-textmate-grammar.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"Creating a Legacy TextMate Grammar","slug":"creating-a-legacy-textmate-grammar","link":"#creating-a-legacy-textmate-grammar","children":[]}],"git":{"updatedTime":1664050274000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":3}]},"readingTime":{"minutes":5.26,"words":1579},"filePathRelative":"docs/atom-archive/hacking-atom/sections/creating-a-legacy-textmate-grammar.md"}');export{a as data}; diff --git a/assets/creating-a-theme.html.22be0ae0.js b/assets/creating-a-theme.html.22be0ae0.js new file mode 100644 index 0000000000..81cac5c8e3 --- /dev/null +++ b/assets/creating-a-theme.html.22be0ae0.js @@ -0,0 +1,12 @@ +import{_ as i,a as l,b as d}from"./theme-side-by-side.33cf8d5d.js";import{_ as c}from"./dev-tools.1b7b2813.js";import{_ as h,o as u,c as p,a as e,b as t,d as s,e as m,w as g,f as o,r as n}from"./app.0e1565ce.js";const b={},f=e("h2",{id:"creating-a-theme",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#creating-a-theme","aria-hidden":"true"},"#"),t(" Creating a Theme")],-1),y={href:"http://lesscss.org/",target:"_blank",rel:"noopener noreferrer"},k=o('

    Pulsar supports two types of themes: UI and Syntax. UI themes style elements such as the tree view, the tabs, drop-down lists, and the status bar. Syntax themes style the code, gutter and other elements inside the editor view.

    Theme boundary

    Themes can be installed and changed from the Settings View which you can open by selecting the LNX: Edit > Preferences - MAC: Pulsar > Preferences - WIN: File > Preferences menu, and clicking the "Install" or "Themes" tab on the left hand navigation.

    Getting Started

    Themes are pretty straightforward but it's still helpful to be familiar with a few things before starting:

    ',5),v={href:"https://speakerdeck.com/danmatthews/less-css",target:"_blank",rel:"noopener noreferrer"},w=o('
  • You may also want to review the concept of a package.json (as covered in Pulsar package.json). This file is used to help distribute your theme to Pulsar users.
  • Your theme's package.json must contain a theme key with a value of ui or syntax for Pulsar to recognize and load it as a theme.
  • ',2),_={href:"https://web.pulsar-edit.dev/packages",target:"_blank",rel:"noopener noreferrer"},x=o('

    Creating a Syntax Theme

    Let's create your first theme.

    To get started, press LNX/WIN: Ctrl+Shift+P - MAC: Cmd+Shift+P and start typing Generate Syntax Theme to generate a new theme package. Select Generate Syntax Theme, and you'll be asked for the path where your theme will be created. Let's call ours motif-syntax.

    Tip

    Syntax themes should end with -syntax and UI themes should end with -ui.

    Pulsar will display a new window, showing the motif-syntax theme, with a default set of folders and files created for us. If you open the Settings View with LNX/WIN: Ctrl+, - MAC: Cmd+, and click the "Themes" tab on the left, you'll see the "Motif" theme listed in the "Syntax Theme" drop-down. Select it from the menu to activate it, now when you open an editor you should see your new motif-syntax theme in action.

    Open up styles/colors.less to change the various color variables which have already been defined. For example, turn @red into #f4c2c1.

    Then open styles/base.less and modify the various selectors that have already been defined. These selectors style different parts of code in the editor such as comments, strings and the line numbers in the gutter.

    As an example, let's make the .gutter background-color into @red.

    Reload Pulsar by pressing LNX/WIN: Alt+Ctrl+R - MAC: Alt+Cmd+Ctrl+L to see the changes you made reflected in your Pulsar window. Pretty neat!

    Tip

    You can avoid reloading to see changes you make by opening an Pulsar window in Dev Mode. To open a Dev Mode Pulsar window run pulsar --dev . in the terminal, or use the View > Developer > Open in Dev Mode menu. When you edit your theme, changes will instantly be reflected!

    Note

    It's advised to not specify a font-family in your syntax theme because it will override the Font Family field in Pulsar's settings. If you still like to recommend a font that goes well with your theme, we suggest you do so in your README.

    Creating a UI Theme

    To create a UI theme, do the following:

    ',13),C={href:"https://github.com/pulsar-edit/ui-theme-template",target:"_blank",rel:"noopener noreferrer"},P=o("
  • Clone the forked repository to the local filesystem
  • Open a terminal in the forked theme's directory
  • Open your new theme in a Dev Mode Pulsar window run pulsar --dev . in the terminal or use the View > Developer > Open in Dev Mode menu
  • Change the name of the theme in the theme's package.json file
  • Name your theme end with a -ui, for example super-white-ui
  • Run pulsar -p link --dev to symlink your repository to LNX/MAC: ~/.pulsar/dev/packages - WIN: %USERPROFILE%\\.pulsar
  • Reload Pulsar using LNX/WIN: Alt+Ctrl+R - MAC: Alt+Cmd+Ctrl+L
  • Enable the theme via the "UI Theme" drop-down in the "Themes" tab of the Settings View
  • Make changes! Since you opened the theme in a Dev Mode window, changes will be instantly reflected in the editor without having to reload.
  • ",9),T=o('

    Tip

    Because we used pulsar -p link --dev in the above instructions, if you break anything you can always close Pulsar and launch Pulsar normally to force Pulsar to the default theme. This allows you to continue working on your theme even if something goes catastrophically wrong.

    Theme Variables

    UI themes must provide a ui-variables.less and Syntax themes a syntax-variables.less file. It contains predefined variables that packages use to make sure the look and feel matches.

    Here the variables with the default values:

    ',4),S={href:"https://github.com/pulsar-edit/pulsar/blob/master/static/variables/ui-variables.less",target:"_blank",rel:"noopener noreferrer"},I={href:"https://github.com/pulsar-edit/pulsar/blob/master/static/variables/syntax-variables.less",target:"_blank",rel:"noopener noreferrer"},N=e("p",null,"These default values will be used as a fallback in case a theme doesn't define its own variables.",-1),q=e("h4",{id:"use-in-packages",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#use-in-packages","aria-hidden":"true"},"#"),t(" Use in Packages")],-1),L=e("p",null,[t("In any of your package's "),e("code",null,".less"),t(" files, you can access the theme variables by importing the "),e("code",null,"ui-variables"),t(" or "),e("code",null,"syntax-variables"),t(" file from Pulsar.")],-1),M={href:"https://github.com/pulsar-edit/styleguide",target:"_blank",rel:"noopener noreferrer"},D=o(`

    Here's an example .less file that a package can define using theme variables:

    @import "ui-variables";
    +
    +.my-selector {
    +	background-color: @base-background-color;
    +	padding: @component-padding;
    +}
    +
    @import "syntax-variables";
    +
    +.my-selector {
    +	background-color: @syntax-background-color;
    +}
    +

    Development workflow

    There are a few tools to help make theme development faster and easier.

    Live Reload

    `,6),A=e("strong",null,[e("em",null,"LNX/WIN")],-1),R=e("kbd",null,"Alt+Ctrl+R",-1),O=e("strong",null,[e("em",null,"MAC")],-1),V=e("kbd",null,"Alt+Cmd+Ctrl+L",-1),W={href:"https://github.com/pulsar-edit/pulsar/tree/master/packages/dev-live-reload",target:"_blank",rel:"noopener noreferrer"},U=o('

    To launch a Dev Mode window:

    • Open your theme directory in a dev window by selecting the View > Developer > Open in Dev Mode menu item
    • Or launch Pulsar from the terminal with pulsar --dev

    If you'd like to reload all the styles at any time, you can use the shortcut LNX/WIN: Alt+Ctrl+R - MAC: Alt+Cmd+Ctrl+L,

    Developer Tools

    Pulsar is based on the Chromium browser and supports its Developer Tools. You can open them by selecting the View > Developer > Toggle Developer Tools menu, or by using the LNX/WIN: Ctrl+Shift+I - MAC: Alt+Cmd+I

    The dev tools allow you to inspect elements and take a look at their CSS properties.

    Developer Tools

    ',7),X={href:"https://developer.chrome.com/devtools/docs/dom-and-styles",target:"_blank",rel:"noopener noreferrer"},E=e("h4",{id:"pulsar-styleguide",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#pulsar-styleguide","aria-hidden":"true"},"#"),t(" Pulsar Styleguide")],-1),Y={href:"https://github.com/pulsar-edit/styleguide",target:"_blank",rel:"noopener noreferrer"},F=o('

    To open the Styleguide, open the command palette with LNX/WIN: Ctrl+Shift+P - MAC: Cmd+Shift+P and search for styleguide, or use the shortcut LNX/WIN: Ctrl+Shift+G - MAC: Cmd+Ctrl+Shift+G.

    Style Guide

    Side by side

    Sometimes when creating a theme (or package) things can go wrong and the editor becomes unusable. E.g. if the text and background have the same color or something gets pushed out of sight. To avoid having to open Pulsar in "normal" mode to fix the issue, it's advised to open two Pulsar windows. One for making changes and one in Dev Mode to see the changes getting applied.

    Side by side screenshot

    Make changes on the left, see the changes getting applied in "Dev Mode" on the right.

    Now if you mess up something, only the window in "Dev Mode" will be affected and you can easily correct the mistake in your "normal" window.

    Publish your theme

    Once you're happy with your theme and would like to share it with other Pulsar users, it's time to publish it. \u{1F389}

    ',9);function G(j,B){const a=n("ExternalLinkIcon"),r=n("RouterLink");return u(),p("div",null,[f,e("p",null,[t("Pulsar's interface is rendered using HTML, and it's styled via "),e("a",y,[t("Less"),s(a)]),t(" which is a superset of CSS. Don't worry if you haven't heard of Less before; it's just like CSS, but with a few handy extensions.")]),k,e("ul",null,[e("li",null,[t("Less is a superset of CSS, but it has some really handy features like variables. If you aren't familiar with its syntax, take a few minutes to "),e("a",v,[t("familiarize yourself"),s(a)]),t(".")]),w,e("li",null,[t("You can find existing themes to install or fork in "),e("a",_,[t("Pulsar Package Repository"),s(a)]),t(". "),m("TODO: Update to a themes URL if we get one on the front end site")])]),x,e("ol",null,[e("li",null,[t("Fork the "),e("a",C,[t("ui-theme-template"),s(a)])]),P]),T,e("ul",null,[e("li",null,[e("a",S,[t("ui-variables.less"),s(a)])]),e("li",null,[e("a",I,[t("syntax-variables.less"),s(a)])])]),N,q,L,e("p",null,[t("Your package should generally only specify structural styling, and these should come from "),e("a",M,[t("the style guide"),s(a)]),t(". Your package shouldn't specify colors, padding sizes, or anything in absolute pixels. You should instead use the theme variables. If you follow this guideline, your package will look good out of the box with any theme!")]),D,e("p",null,[t("Reloading by pressing "),A,t(": "),R,t(" - "),O,t(": "),V,t(" after you make changes to your theme is less than ideal. Pulsar supports "),e("a",W,[t("live updating"),s(a)]),t(" of styles on Pulsar windows in Dev Mode.")]),U,e("p",null,[t("Check out Google's "),e("a",X,[t("extensive tutorial"),s(a)]),t(" for a short introduction.")]),E,e("p",null,[t("If you are creating an UI theme, you'll want a way to see how your theme changes affect all the components in the system. The "),e("a",Y,[t("Styleguide"),s(a)]),t(" is a page that renders every component Pulsar supports.")]),F,e("p",null,[t("Follow the steps on the "),s(r,{to:"/docs/launch-manual/sections/core-hacking/sections/#publishing/"},{default:g(()=>[t("Publishing")]),_:1}),t(" page. The example used is for the Word Count package, but publishing a theme works exactly the same.")])])}const K=h(b,[["render",G],["__file","creating-a-theme.html.vue"]]);export{K as default}; diff --git a/assets/creating-a-theme.html.9a1c6a33.js b/assets/creating-a-theme.html.9a1c6a33.js new file mode 100644 index 0000000000..cc82378603 --- /dev/null +++ b/assets/creating-a-theme.html.9a1c6a33.js @@ -0,0 +1,12 @@ +import{_ as r,a as d,b as c}from"./theme-side-by-side.33cf8d5d.js";import{_ as h}from"./dev-tools.1b7b2813.js";import{_ as p,o as u,c as m,a as t,b as e,d as o,w as i,f as s,r as l}from"./app.0e1565ce.js";const g={},f=t("h3",{id:"creating-a-theme",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#creating-a-theme","aria-hidden":"true"},"#"),e(" Creating a Theme")],-1),b={href:"http://lesscss.org/",target:"_blank",rel:"noopener noreferrer"},y=s('

    Atom supports two types of themes: UI and Syntax. UI themes style elements such as the tree view, the tabs, drop-down lists, and the status bar. Syntax themes style the code, gutter and other elements inside the editor view.

    Theme boundary

    Themes can be installed and changed from the Settings View which you can open by selecting the Atom > PreferencesFile > PreferencesEdit > Preferences menu, and clicking the "Install" or "Themes" tab on the left hand navigation.

    Getting Started

    Themes are pretty straightforward but it's still helpful to be familiar with a few things before starting:

    ',5),k={href:"https://speakerdeck.com/danmatthews/less-css",target:"_blank",rel:"noopener noreferrer"},v=t("code",null,"package.json",-1),w=t("code",null,"package.json",-1),_=t("li",null,[e("Your theme's "),t("code",null,"package.json"),e(" must contain a "),t("code",null,"theme"),e(" key with a value of "),t("code",null,"ui"),e(" or "),t("code",null,"syntax"),e(" for Atom to recognize and load it as a theme.")],-1),x={href:"https://atom.io/themes",target:"_blank",rel:"noopener noreferrer"},T=s('

    Creating a Syntax Theme

    Let's create your first theme.

    To get started, press Cmd+Shift+PCtrl+Shift+P and start typing "Generate Syntax Theme" to generate a new theme package. Select "Generate Syntax Theme," and you'll be asked for the path where your theme will be created. Let's call ours motif-syntax.

    Tip

    Tip: Syntax themes should end with -syntax and UI themes should end with -ui.

    Atom will display a new window, showing the motif-syntax theme, with a default set of folders and files created for us. If you open the Settings View with Cmd+,Ctrl+, and click the "Themes" tab on the left, you'll see the "Motif" theme listed in the "Syntax Theme" drop-down. Select it from the menu to activate it, now when you open an editor you should see your new motif-syntax theme in action.

    Open up styles/colors.less to change the various color variables which have already been defined. For example, turn @red into #f4c2c1.

    Then open styles/base.less and modify the various selectors that have already been defined. These selectors style different parts of code in the editor such as comments, strings and the line numbers in the gutter.

    As an example, let's make the .gutter background-color into @red.

    Reload Atom by pressing Alt+Cmd+Ctrl+LAlt+Ctrl+R to see the changes you made reflected in your Atom window. Pretty neat!

    Tip

    Tip: You can avoid reloading to see changes you make by opening an Atom window in Dev Mode. To open a Dev Mode Atom window run atom --dev . in the terminal, or use the View > Developer > Open in Dev Mode menu. When you edit your theme, changes will instantly be reflected!

    Note

    Note: It's advised to not specify a font-family in your syntax theme because it will override the Font Family field in Atom's settings. If you still like to recommend a font that goes well with your theme, we suggest you do so in your README.

    Creating a UI Theme

    To create a UI theme, do the following:

    ',13),A={href:"https://github.com/atom-community/ui-theme-template",target:"_blank",rel:"noopener noreferrer"},C=s('
  • Clone the forked repository to the local filesystem
  • Open a terminal in the forked theme's directory
  • Open your new theme in a Dev Mode Atom window run atom --dev . in the terminal or use the View > Developer > Open in Dev Mode menu
  • Change the name of the theme in the theme's package.json file
  • Name your theme end with a -ui, for example super-white-ui
  • Run apm link --dev to symlink your repository to ~/.atom/dev/packages
  • Reload Atom using Alt+Cmd+Ctrl+LAlt+Ctrl+R
  • Enable the theme via the "UI Theme" drop-down in the "Themes" tab of the Settings View
  • Make changes! Since you opened the theme in a Dev Mode window, changes will be instantly reflected in the editor without having to reload.
  • ',9),S=s('

    Tip

    Tip: Because we used apm link --dev in the above instructions, if you break anything you can always close Atom and launch Atom normally to force Atom to the default theme. This allows you to continue working on your theme even if something goes catastrophically wrong.

    Theme Variables

    UI themes must provide a ui-variables.less and Syntax themes a syntax-variables.less file. It contains predefined variables that packages use to make sure the look and feel matches.

    Here the variables with the default values:

    ',4),q={href:"https://github.com/atom/atom/blob/master/static/variables/ui-variables.less",target:"_blank",rel:"noopener noreferrer"},D={href:"https://github.com/atom/atom/blob/master/static/variables/syntax-variables.less",target:"_blank",rel:"noopener noreferrer"},I=t("p",null,"These default values will be used as a fallback in case a theme doesn't define its own variables.",-1),M=t("h5",{id:"use-in-packages",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#use-in-packages","aria-hidden":"true"},"#"),e(" Use in Packages")],-1),L=t("p",null,[e("In any of your package's "),t("code",null,".less"),e(" files, you can access the theme variables by importing the "),t("code",null,"ui-variables"),e(" or "),t("code",null,"syntax-variables"),e(" file from Atom.")],-1),R={href:"https://github.com/atom/styleguide",target:"_blank",rel:"noopener noreferrer"},V=s(`

    Here's an example .less file that a package can define using theme variables:

    @import "ui-variables";
    +
    +.my-selector {
    +	background-color: @base-background-color;
    +	padding: @component-padding;
    +}
    +
    @import "syntax-variables";
    +
    +.my-selector {
    +	background-color: @syntax-background-color;
    +}
    +

    Development workflow

    There are a few tools to help make theme development faster and easier.

    Live Reload
    `,6),P=t("kbd",{class:"platform-mac"},"Alt+Cmd+Ctrl+L",-1),O=t("kbd",{class:"platform-windows platform-linux"},"Alt+Ctrl+R",-1),U={href:"https://github.com/atom/dev-live-reload",target:"_blank",rel:"noopener noreferrer"},E=s('

    To launch a Dev Mode window:

    • Open your theme directory in a dev window by selecting the View > Developer > Open in Dev Mode menu item
    • Or launch Atom from the terminal with atom --dev

    If you'd like to reload all the styles at any time, you can use the shortcut Alt+Cmd+Ctrl+LAlt+Ctrl+R.

    Developer Tools

    Atom is based on the Chrome browser, and supports Chrome's Developer Tools. You can open them by selecting the View > Developer > Toggle Developer Tools menu, or by using the Alt+Cmd+ICtrl+Shift+I shortcut.

    The dev tools allow you to inspect elements and take a look at their CSS properties.

    Developer Tools

    ',7),N={href:"https://developer.chrome.com/devtools/docs/dom-and-styles",target:"_blank",rel:"noopener noreferrer"},Y=t("h5",{id:"atom-styleguide",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#atom-styleguide","aria-hidden":"true"},"#"),e(" Atom Styleguide")],-1),G={href:"https://github.com/atom/styleguide",target:"_blank",rel:"noopener noreferrer"},j=s('

    To open the Styleguide, open the command palette with Cmd+Shift+PCtrl+Shift+P and search for "styleguide", or use the shortcut Cmd+Ctrl+Shift+GCtrl+Shift+G.

    Style Guide

    Side by side

    Sometimes when creating a theme (or package) things can go wrong and the editor becomes un-usable. E.g. if the text and background have the same color or something gets pushed out of sight. To avoid having to open Atom in "normal" mode to fix the issue, it's advised to open two Atom windows. One for making changes and one in Dev Mode to see the changes getting applied.

    Side by side screenshot

    Make changes on the left, see the changes getting applied in "Dev Mode" on the right.

    Now if you mess up something, only the window in "Dev Mode" will be affected and you can easily correct the mistake in your "normal" window.

    Publish your theme

    Once you're happy with your theme and would like to share it with other Atom users, it's time to publish it. \u{1F389}

    ',9);function F(B,z){const a=l("ExternalLinkIcon"),n=l("RouterLink");return u(),m("div",null,[f,t("p",null,[e("Atom's interface is rendered using HTML, and it's styled via "),t("a",b,[e("Less"),o(a)]),e(" which is a superset of CSS. Don't worry if you haven't heard of Less before; it's just like CSS, but with a few handy extensions.")]),y,t("ul",null,[t("li",null,[e("Less is a superset of CSS, but it has some really handy features like variables. If you aren't familiar with its syntax, take a few minutes to "),t("a",k,[e("familiarize yourself"),o(a)]),e(".")]),t("li",null,[e("You may also want to review the concept of a "),v,e(" (as covered in "),o(n,{to:"/hacking-atom/sections/package-word-count/#packagejson"},{default:i(()=>[e("Atom "),w]),_:1}),e("). This file is used to help distribute your theme to Atom users.")]),_,t("li",null,[e("You can find existing themes to install or fork in "),t("a",x,[e("the atom.io themes registry"),o(a)]),e(".")])]),T,t("ol",null,[t("li",null,[e("Fork the "),t("a",A,[e("ui-theme-template"),o(a)])]),C]),S,t("ul",null,[t("li",null,[t("a",q,[e("ui-variables.less"),o(a)])]),t("li",null,[t("a",D,[e("syntax-variables.less"),o(a)])])]),I,M,L,t("p",null,[e("Your package should generally only specify structural styling, and these should come from "),t("a",R,[e("the style guide"),o(a)]),e(". Your package shouldn't specify colors, padding sizes, or anything in absolute pixels. You should instead use the theme variables. If you follow this guideline, your package will look good out of the box with any theme!")]),V,t("p",null,[e("Reloading by pressing "),P,O,e(" after you make changes to your theme is less than ideal. Atom supports "),t("a",U,[e("live updating"),o(a)]),e(" of styles on Atom windows in Dev Mode.")]),E,t("p",null,[e("Check out Google's "),t("a",N,[e("extensive tutorial"),o(a)]),e(" for a short introduction.")]),Y,t("p",null,[e("If you are creating an UI theme, you'll want a way to see how your theme changes affect all the components in the system. The "),t("a",G,[e("Styleguide"),o(a)]),e(" is a page that renders every component Atom supports.")]),j,t("p",null,[e("Follow the steps on the "),o(n,{to:"/docs/atom-archive/hacking-atom/publishing/"},{default:i(()=>[e("Publishing")]),_:1}),e(" page. The example used is for the Word Count package, but publishing a theme works exactly the same.")])])}const K=p(g,[["render",F],["__file","creating-a-theme.html.vue"]]);export{K as default}; diff --git a/assets/creating-a-theme.html.a5fedbd5.js b/assets/creating-a-theme.html.a5fedbd5.js new file mode 100644 index 0000000000..1c8e988460 --- /dev/null +++ b/assets/creating-a-theme.html.a5fedbd5.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-049d4d93","path":"/docs/atom-archive/hacking-atom/sections/creating-a-theme.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"Creating a Theme","slug":"creating-a-theme","link":"#creating-a-theme","children":[]}],"git":{"updatedTime":1664050274000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":3}]},"readingTime":{"minutes":5.37,"words":1610},"filePathRelative":"docs/atom-archive/hacking-atom/sections/creating-a-theme.md"}');export{e as data}; diff --git a/assets/creating-a-theme.html.e0e52d2e.js b/assets/creating-a-theme.html.e0e52d2e.js new file mode 100644 index 0000000000..aad82d4b6b --- /dev/null +++ b/assets/creating-a-theme.html.e0e52d2e.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-f63d57d8","path":"/docs/launch-manual/sections/core-hacking/sections/creating-a-theme.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Creating a Theme","slug":"creating-a-theme","link":"#creating-a-theme","children":[{"level":3,"title":"Getting Started","slug":"getting-started","link":"#getting-started","children":[]},{"level":3,"title":"Creating a Syntax Theme","slug":"creating-a-syntax-theme","link":"#creating-a-syntax-theme","children":[]},{"level":3,"title":"Creating a UI Theme","slug":"creating-a-ui-theme","link":"#creating-a-ui-theme","children":[]},{"level":3,"title":"Theme Variables","slug":"theme-variables","link":"#theme-variables","children":[]},{"level":3,"title":"Development workflow","slug":"development-workflow","link":"#development-workflow","children":[]},{"level":3,"title":"Publish your theme","slug":"publish-your-theme","link":"#publish-your-theme","children":[]}]}],"git":{"updatedTime":1670811406000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":2}]},"readingTime":{"minutes":5.24,"words":1572},"filePathRelative":"docs/launch-manual/sections/core-hacking/sections/creating-a-theme.md"}');export{e as data}; diff --git a/assets/cross-platform-compatibility.html.12555539.js b/assets/cross-platform-compatibility.html.12555539.js new file mode 100644 index 0000000000..a44fd18e47 --- /dev/null +++ b/assets/cross-platform-compatibility.html.12555539.js @@ -0,0 +1 @@ +const t=JSON.parse('{"key":"v-0762ec4e","path":"/docs/atom-archive/hacking-atom/sections/cross-platform-compatibility.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"Cross-Platform Compatibility","slug":"cross-platform-compatibility","link":"#cross-platform-compatibility","children":[]}],"git":{"updatedTime":1664050274000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":2}]},"readingTime":{"minutes":1.81,"words":543},"filePathRelative":"docs/atom-archive/hacking-atom/sections/cross-platform-compatibility.md"}');export{t as data}; diff --git a/assets/cross-platform-compatibility.html.1dd2761e.js b/assets/cross-platform-compatibility.html.1dd2761e.js new file mode 100644 index 0000000000..e0356696be --- /dev/null +++ b/assets/cross-platform-compatibility.html.1dd2761e.js @@ -0,0 +1 @@ +const t=JSON.parse('{"key":"v-78bfd6da","path":"/docs/launch-manual/sections/core-hacking/sections/cross-platform-compatibility.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"Cross-Platform Compatibility","slug":"cross-platform-compatibility","link":"#cross-platform-compatibility","children":[]}],"git":{"updatedTime":1670466847000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":1.81,"words":543},"filePathRelative":"docs/launch-manual/sections/core-hacking/sections/cross-platform-compatibility.md"}');export{t as data}; diff --git a/assets/cross-platform-compatibility.html.ce60d4e3.js b/assets/cross-platform-compatibility.html.ce60d4e3.js new file mode 100644 index 0000000000..d30076d4d2 --- /dev/null +++ b/assets/cross-platform-compatibility.html.ce60d4e3.js @@ -0,0 +1,4 @@ +import{_ as i,o as s,c as r,a,b as e,d as n,f as t,r as d}from"./app.0e1565ce.js";const l={},c=t('

    Cross-Platform Compatibility

    Atom runs on a number of platforms and while Electron and Node take care of many of the details there are still some considerations to ensure your package works on other operating systems.

    File symlinks can be used on Windows by non-Administrators by specifying 'junction' as the type (this argument is ignored on macOS & Linux).

    Also consider:

    • Symlinks committed to Git will not checkout correctly on Windows - dynamically create what you need with fs.symlink instead
    • Symlinked directories are only available to Administrators on Windows - avoid a dependency on them

    Filenames

    • Reserved filenames on Windows are com1-com9, lpt1-lpt9, con, nul, aux and prn (regardless of extension, e.g. prn.txt is disallowed)
    • Reserved characters on Windows are ? \\ / < > ? % | : " so avoid where possible
    • Names with spaces when passed to the command line;
      • Windows requires you surround the path with double quotes e.g. "c:\\my test"
      • macOS and Linux require a backslash before each space e.g. /my\\ test

    File paths

    • Windows uses \\ although some tools and PowerShell allow / too
    • macOS and Linux use /

    You can dynamically find out what your platform uses with path.sep or better yet use the node path library functions such as join and normalize which automatically take care of this.

    Windows supports up to 250 characters for a path - avoid deeply nested directory structures

    Paths are not URLs

    URL parsing routines should not be used on file paths. While they initially look like a relative path it will fail in a number of scenarios on all platforms.

    • Various characters are misinterpreted, e.g. ? as query string, # as a fragment identifier
    • Windows drive specifiers are incorrectly parsed as a protocol

    If you need to use a path for a URL use the file: protocol with an absolute path instead to ensure drive letters and slashes are appropriately addressed, e.g. file:///c|/test/pic.png

    fs.stat on directories

    The fs.stat function does not return the size of the contents of a directory but rather the allocation size of the directory itself. This returns 0 on Windows and 1024 on macOS and so should not be relied upon.

    path.relative can't traverse drives

    • On a macOS or Linux system path.relative can be used to calculate a relative path to traverse between any two given paths.
    • On Windows this is not always possible as it can contain multiple absolute roots, e.g. c:\\ and d:\\

    Rapid file operations

    ',21),h={href:"https://www.npmjs.com/package/rimraf",target:"_blank",rel:"noopener noreferrer"},u=t(`

    Line endings

    • Windows uses CRLF
    • macOS and Linux use LF
    • Git on Windows often has autocrlf set which automatically converts between the two

    If you are writing specs that use text file fixtures consider that this will interfere with file lengths, hash codes and direct text comparisons. It will also change the Atom selection length by 1 character per line.

    If you have spec fixtures that are text files you may want to tell Git to force LF, CRLF or not convert them by specifying the paths in .gitattributes e.g.

    spec/fixtures/always-crlf.txt eol=crlf
    +spec/fixtures/always-lf.txt eol=lf
    +spec/fixtures/leave-as-is.txt -text
    +
    `,5);function m(p,f){const o=d("ExternalLinkIcon");return s(),r("div",null,[c,a("p",null,[e("Creation and deletion operations may take a few milliseconds to complete. If you need to remove many files and folders consider "),a("a",h,[e("RimRAF"),n(o)]),e(" which has built-in retry logic for this.")]),u])}const w=i(l,[["render",m],["__file","cross-platform-compatibility.html.vue"]]);export{w as default}; diff --git a/assets/cross-platform-compatibility.html.ffa3c2d2.js b/assets/cross-platform-compatibility.html.ffa3c2d2.js new file mode 100644 index 0000000000..3f269b0dbb --- /dev/null +++ b/assets/cross-platform-compatibility.html.ffa3c2d2.js @@ -0,0 +1,4 @@ +import{_ as i,o as s,c as r,a,b as e,d as n,f as t,r as d}from"./app.0e1565ce.js";const l={},c=t('

    Cross-Platform Compatibility

    Pulsar runs on a number of platforms and while Electron and Node take care of many of the details there are still some considerations to ensure your package works on other operating systems.

    File symlinks can be used on Windows by non-Administrators by specifying 'junction' as the type (this argument is ignored on macOS & Linux).

    Also consider:

    • Symlinks committed to Git will not checkout correctly on Windows - dynamically create what you need with fs.symlink instead
    • Symlinked directories are only available to Administrators on Windows - avoid a dependency on them

    Filenames

    • Reserved filenames on Windows are com1-com9, lpt1-lpt9, con, nul, aux and prn (regardless of extension, e.g. prn.txt is disallowed)
    • Reserved characters on Windows are ? \\ / < > ? % | : " so avoid where possible
    • Names with spaces when passed to the command line;
      • Linux and macOS require a backslash before each space e.g. /my\\ test
      • Windows requires you surround the path with double quotes e.g. "c:\\my test"

    File paths

    • Windows uses \\ although some tools and PowerShell allow / too
    • macOS and Linux use /

    You can dynamically find out what your platform uses with path.sep or better yet use the node path library functions such as join and normalize which automatically take care of this.

    Windows supports up to 250 characters for a path - avoid deeply nested directory structures

    Paths are not URLs

    URL parsing routines should not be used on file paths. While they initially look like a relative path it will fail in a number of scenarios on all platforms.

    • Various characters are misinterpreted, e.g. ? as query string, # as a fragment identifier
    • Windows drive specifiers are incorrectly parsed as a protocol

    If you need to use a path for a URL use the file: protocol with an absolute path instead to ensure drive letters and slashes are appropriately addressed, e.g. file:///c|/test/pic.png

    fs.stat on directories

    The fs.stat function does not return the size of the contents of a directory but rather the allocation size of the directory itself. This returns 0 on Windows and 1024 on macOS and so should not be relied upon.

    path.relative can't traverse drives

    • On a Linux or macOS system path.relative can be used to calculate a relative path to traverse between any two given paths.
    • On Windows this is not always possible as it can contain multiple absolute roots, e.g. c:\\ and d:\\

    Rapid file operations

    ',21),h={href:"https://www.npmjs.com/package/rimraf",target:"_blank",rel:"noopener noreferrer"},u=t(`

    Line endings

    • Linux and macOS use LF
    • Windows uses CRLF
    • Git on Windows often has autocrlf set which automatically converts between the two

    If you are writing specs that use text file fixtures consider that this will interfere with file lengths, hash codes and direct text comparisons. It will also change the Atom selection length by 1 character per line.

    If you have spec fixtures that are text files you may want to tell Git to force LF, CRLF or not convert them by specifying the paths in .gitattributes e.g.

    spec/fixtures/always-crlf.txt eol=crlf
    +spec/fixtures/always-lf.txt eol=lf
    +spec/fixtures/leave-as-is.txt -text
    +
    `,5);function p(m,f){const o=d("ExternalLinkIcon");return s(),r("div",null,[c,a("p",null,[e("Creation and deletion operations may take a few milliseconds to complete. If you need to remove many files and folders consider "),a("a",h,[e("RimRAF"),n(o)]),e(" which has built-in retry logic for this.")]),u])}const w=i(l,[["render",p],["__file","cross-platform-compatibility.html.vue"]]);export{w as default}; diff --git a/assets/cursor-scope.9586ad1c.png b/assets/cursor-scope.9586ad1c.png new file mode 100644 index 0000000000..2635db2475 Binary files /dev/null and b/assets/cursor-scope.9586ad1c.png differ diff --git a/assets/deb-get-logo.d7385828.png b/assets/deb-get-logo.d7385828.png new file mode 100644 index 0000000000..f731a51599 Binary files /dev/null and b/assets/deb-get-logo.d7385828.png differ diff --git a/assets/debugging.html.13d250a3.js b/assets/debugging.html.13d250a3.js new file mode 100644 index 0000000000..cfc72f5176 --- /dev/null +++ b/assets/debugging.html.13d250a3.js @@ -0,0 +1,51 @@ +import{_ as p,a as g,b as m,c as b,d as f,e as k,f as v,g as y,h as _,i as w}from"./cpu-profile-done.c24435bc.js";import{_ as P,o as C,c as x,a as e,b as t,d as a,e as d,w as s,f as o,r as u}from"./app.0e1565ce.js";const I={},T=e("h2",{id:"debugging",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#debugging","aria-hidden":"true"},"#"),t(" Debugging")],-1),S={href:"https://github.com/pulsar-edit/.github/blob/main/CONTRIBUTING.md#reporting-bugs",target:"_blank",rel:"noopener noreferrer"},U=o(`

    Update to the Latest Version

    You might be running into an issue which was already fixed in a more recent version of Pulsar than the one you're using.

    If you're using a released version, check which version of Pulsar you're using:

    $ pulsar --version
    +> Pulsar  : 1.63.0-dev
    +> Electron: 12.2.3
    +> Chrome  : 89.0.4389.128
    +> Node    : 14.16.0
    +
    `,4),N={href:"https://pulsar-edit.dev/download.html",target:"_blank",rel:"noopener noreferrer"},A=o(`

    If you're building Pulsar from source, pull down the latest version of master and re-build. Make sure that if logging an issue you include the latest commit hash you built from.

    Using Safe Mode

    A large part of Pulsar's functionality comes from packages you can install. Pulsar will also execute the code in your init script on startup. In some cases, these packages and the code in the init script might be causing unexpected behavior, problems, or performance issues.

    To determine if that is happening, start Pulsar from the terminal in safe mode:

    $ pulsar --safe
    +

    This starts Pulsar, but does not load packages from LNX/MAC: ~/.pulsar/packages or ~/.pulsar/dev/packages - WIN: %USERPROFILE%\\.pulsar\\packages or %USERPROFILE%\\.pulsar\\dev\\packages. and disables loading of your init script. If you can no longer reproduce the problem in safe mode, it's likely it was caused by one of the packages or the init script.

    If removing or commenting out all content from the init script and starting Pulsar normally still produces the error, then try figuring out which package is causing trouble. Start Pulsar normally again and open the Settings View with LNX/WIN: Ctrl+, - MAC: Cmd+,. Since the Settings View allows you to disable each installed package, you can disable packages one by one until you can no longer reproduce the issue. Restart Pulsar or reload Pulsar with LNX/WIN: Ctrl+Shift+F5 - MAC: Alt+Cmd+Ctrl+L. after you disable each package to make sure it's completely gone.

    When you find the problematic package, you can disable or uninstall the package. We strongly recommend creating an issue on the package's GitHub repository.

    Clearing Saved State

    Pulsar saves a number of things about your environment when you exit in order to restore Pulsar to the same configuration when you next launch the program. In some cases the state that gets saved can be something undesirable that prevents Pulsar from working properly. In these cases, you may want to clear the state that Pulsar has saved.

    DANGER

    Clearing the saved state permanently destroys any state that Pulsar has saved across all projects. This includes unsaved changes to files you may have been editing in all projects. This is a destructive action.

    Clearing the saved state can be done by opening a terminal and executing:

    $ pulsar --clear-window-state
    +

    Reset to Factory Defaults

    In some cases, you may want to reset Pulsar to "factory defaults", in other words clear all of your configuration and remove all packages. This can easily be done by opening a terminal and executing:

    `,15),L=e("div",{class:"language-bash ext-sh line-numbers-mode"},[e("pre",{class:"language-bash"},[e("code",null,[t("$ "),e("span",{class:"token function"},"mv"),t(` ~/.pulsar ~/.pulsar-backup +`)])]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"})])],-1),R=e("div",{class:"language-bash ext-sh line-numbers-mode"},[e("pre",{class:"language-bash"},[e("code",null,[t("$ "),e("span",{class:"token function"},"mv"),t(` ~/.pulsar ~/.pulsar-backup +`)])]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"})])],-1),W=e("div",{class:"language-bash ext-sh line-numbers-mode"},[e("pre",{class:"language-bash"},[e("code",null,[t("$ "),e("span",{class:"token function"},"rename"),t(" %USERPROFILE%"),e("span",{class:"token punctuation"},"\\"),t(`.pulsar .pulsar-backup +`)])]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"})])],-1),E=o(`

    Once that is complete, you can launch Pulsar as normal. Everything will be just as if you first installed Pulsar.

    Tip

    The command given above doesn't delete the old configuration, just puts it somewhere that Pulsar can't find it. If there are pieces of the old configuration you want to retrieve, you can find them in the LNX/MAC: ~/.pulsar-backup - WIN: %USERPROFILE%\\.pulsar-backup directory.

    Check for Linked Packages

    If you develop or contribute to Pulsar packages, there may be left-over packages linked to your LNX/MAC: ~/.pulsar/packages or ~/.pulsar/dev/packages - WIN: %USERPROFILE%\\.pulsar\\packages or %USERPROFILE%\\.pulsar\\dev\\packages. directories. You can use the pulsar -p links command to list all linked packages:

    $ pulsar -p links
    +> /Users/pulsy/.pulsar/dev/packages (0)
    +> \u2514\u2500\u2500 (no links)
    +> /Users/pulsy/.pulsar/packages (1)
    +> \u2514\u2500\u2500 color-picker -> /Users/pulsy/github/color-picker
    +

    You can remove links using the pulsar -p unlink command:

    $ pulsar -p unlink color-picker
    +> Unlinking /Users/pulsy/.pulsar/packages/color-picker \u2713
    +

    See pulsar -p links --help and pulsar -p unlink --help for more information on these commands.

    Tip

    You can also use pulsar -p unlink --all to easily unlink all packages and themes.

    Check for Incompatible Packages

    `,10),O={href:"https://github.com/pulsar-edit/pulsar/tree/master/packages/incompatible-packages",target:"_blank",rel:"noopener noreferrer"},F=e("p",null,[e("img",{src:p,alt:"Incompatible Packages Status Bar Indicator",title:"Incompatible Packages Status Bar Indicator"})],-1),q=e("p",null,"If you see this indicator, click it and follow the instructions.",-1),D=e("h3",{id:"check-pulsar-and-package-settings",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#check-pulsar-and-package-settings","aria-hidden":"true"},"#"),t(" Check Pulsar and Package Settings")],-1),Y=e("p",null,"In some cases, unexpected behavior might be caused by settings in Pulsar or in one of the packages.",-1),G={href:"https://github.com/pulsar-edit/settings-view",target:"_blank",rel:"noopener noreferrer"},M=e("strong",null,[e("em",null,"LNX/WIN")],-1),V=e("kbd",null,"Ctrl+,",-1),$=e("strong",null,[e("em",null,"MAC")],-1),z=e("kbd",null,"Cmd+,",-1),X=e("strong",null,[e("em",null,"LNX")],-1),B=e("em",null,"Edit > Preferences",-1),K=e("strong",null,[e("em",null,"MAC")],-1),j=e("em",null,"Pulsar > Preferences",-1),H=e("strong",null,[e("em",null,"WIN")],-1),J=e("em",null,"File > Preferences",-1),Q=e("code",null,"Settings View: Open",-1),Z={href:"https://github.com/pulsar-edit/command-palette",target:"_blank",rel:"noopener noreferrer"},ee=e("p",null,[e("img",{src:g,alt:"Settings View"})],-1),te=e("p",null,"Some of these options are also available on a per-language basis which means that they may be different for specific languages, for example JavaScript or Python. To check the per-language settings, open the settings for the language package under the Packages tab in the Settings View, for example the language-javascript or language-python package.",-1),ae={href:"https://github.com/pulsar-edit/wrap-guide",target:"_blank",rel:"noopener noreferrer"},ne={href:"https://github.com/pulsar-edit/whitespace",target:"_blank",rel:"noopener noreferrer"},se=e("p",null,[e("img",{src:m,alt:"Package Settings"})],-1),oe=e("h3",{id:"check-your-configuration",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#check-your-configuration","aria-hidden":"true"},"#"),t(" Check Your Configuration")],-1),ie=e("h3",{id:"check-your-keybindings",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#check-your-keybindings","aria-hidden":"true"},"#"),t(" Check Your Keybindings")],-1),re={href:"https://github.com/pulsar-edit/keybinding-resolver",target:"_blank",rel:"noopener noreferrer"},le=o('

    Show the keybinding resolver with LNX/WIN: Ctrl+. - MAC: Cmd+., or with Keybinding Resolver: Show from the Command palette. With the Keybinding Resolver shown, press a key combination:

    Keybinding Resolver

    The Keybinding Resolver shows you a list of keybindings that exist for the key combination, where each item in the list has the following:

    • the command for the keybinding
    • the CSS selector used to define the context in which the keybinding is valid
    • the file in which the keybinding is defined

    The keybindings are listed in two colors. All the keybindings that are matched but not executed are shown in gray. The one that is executed, if any, is shown in green. If the command you wanted to trigger isn't listed, then a keybinding for that command hasn't been loaded.

    ',5),ce=e("ul",null,[e("li",null,[e("p",null,"The key combination was not used in the context defined by the keybinding's selector"),e("p",null,[t("For example, you can't trigger the keybinding for the "),e("code",null,"tree-view:add-file"),t(" command if the Tree View is not focused.")])]),e("li",null,[e("p",null,"There is another keybinding that took precedence"),e("p",null,"This often happens when you install a package which defines keybindings that conflict with existing keybindings. If the package's keybindings have selectors with higher specificity or were loaded later, they'll have priority over existing ones.")])],-1),de=e("code",null,"keymap.cson",-1),ue={href:"https://github.com/orgs/pulsar-edit/discussions",target:"_blank",rel:"noopener noreferrer"},he=e("h3",{id:"check-font-rendering-issues",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#check-font-rendering-issues","aria-hidden":"true"},"#"),t(" Check Font Rendering Issues")],-1),pe=e("strong",null,[e("em",null,"LNX/WIN")],-1),ge=e("kbd",null,"Ctrl+Shift+I",-1),me=e("strong",null,[e("em",null,"MAC")],-1),be=e("kbd",null,"Alt+Cmd+I",-1),fe={href:"https://developers.google.com/web/tools/chrome-devtools/inspect-styles/",target:"_blank",rel:"noopener noreferrer"},ke=o('

    Fonts In Use

    Check for Errors in the Developer Tools

    When an unexpected error occurs in Pulsar, you will normally see a red notification which provides details about the error and allows you to create an issue on the right repository:

    Exception Notification

    Not all errors are logged with a notification so if you suspect you're experiencing an error but there's no notification, you can also look for errors in the developer tools Console tab. To access the Console tab, press LNX/WIN: Ctrl+Shift+I - MAC: Alt+Cmd+I to open developer tools and then click the Console tab:

    DevTools Error

    If there are multiple errors, you can scroll down to the bottom of the panel to see the most recent error. Or while reproducing an error, you can right click in the Console tab panel, select Clear console to remove all Console output, and then reproduce the error to see what errors are logged to the Console tab.

    Note

    When running in Dev Mode, the developer tools are automatically shown with the error logged in the Console tab.

    Find Crash Logs

    ',9),ve=e("p",null,"When Pulsar crashes, it should write a core dump if system settings permit. In order to find whether the core dump is written and to where, consult the documentation for your distribution of Linux. Once you have the core dump, you can save it to send in later if it is needed for debugging.",-1),ye=e("p",null,[t("When Pulsar crashes, you will find a crash dump in Console.app. You can launch Console.app using Spotlight or you can find it in "),e("code",null,"/Applications/Utilities/Console.app"),t(". Once you have launched the program, you can find the latest crash dump by following these instructions:")],-1),_e=e("ol",null,[e("li",null,'Click "User Reports" in the left-most column'),e("li",null,[t("Find the latest entry in the middle column that starts with "),e("code",null,"Pulsar"),t(" and ends with "),e("code",null,".crash")])],-1),we=e("p",null,"Once you have the crash dump, you can save it to send in later if it is needed for debugging.",-1),Pe=e("p",null,[t("When Pulsar crashes, you will find a crash dump inside your "),e("code",null,"%TEMP%\\Pulsar Crashes"),t(" directory. It will be the newest file with the "),e("code",null,".dmp"),t(" extension. Once you have the crash dump, you can save it to send in later if it is needed for debugging.")],-1),Ce=e("h3",{id:"diagnose-startup-performance",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#diagnose-startup-performance","aria-hidden":"true"},"#"),t(" Diagnose Startup Performance")],-1),xe={href:"https://github.com/pulsar-edit/timecop",target:"_blank",rel:"noopener noreferrer"},Ie=e("p",null,[e("img",{src:b,alt:"Timecop"})],-1),Te=e("p",null,"Timecop displays the following information:",-1),Se=e("ul",null,[e("li",null,"Pulsar startup times"),e("li",null,"File compilation times"),e("li",null,"Package loading and activation times"),e("li",null,"Theme loading and activation times")],-1),Ue=e("p",null,"If a specific package has high load or activation times, you might consider reporting an issue to the maintainers. You can also disable the package to potentially improve future startup times.",-1),Ne=e("h3",{id:"diagnose-runtime-performance",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#diagnose-runtime-performance","aria-hidden":"true"},"#"),t(" Diagnose Runtime Performance")],-1),Ae={href:"https://github.com/pulsar-edit/.github/blob/main/CONTRIBUTING.md#reporting-bugs",target:"_blank",rel:"noopener noreferrer"},Le=o('

    To run a profile, open the Developer Tools with LNX/WIN: Ctrl+Shift+I - MAC: Alt+Cmd+I. From there:

    1. Click the Profiles tab
    2. Select "Collect JavaScript CPU Profile"
    3. Click "Start"

    DevTools Profiler

    Once that is done, then perform the slow action to capture a recording. When finished, click "Stop". Switch to the "Chart" view, and a graph of the recorded actions will appear. You can save and post the profile data by clicking "Save" next to the profile's name in the left panel.

    DevTools Profiler

    ',5),Re={href:"https://developer.chrome.com/devtools/docs/cpu-profiling",target:"_blank",rel:"noopener noreferrer"},We=o(`

    Profiling Startup Performance

    If the time for loading the window looks high, you can create a CPU profile for that period using the --profile-startup command line flag when starting Pulsar:

    $ pulsar --profile-startup .
    +

    This will automatically capture a CPU profile as Pulsar is loading and open the Developer Tools once Pulsar loads. From there:

    1. Click the Profiles tab in the Developer Tools
    2. Select the "startup" profile
    3. Click the "Save" link for the startup profile

    You can then include the startup profile in any issue you report.

    Check Your Build Tools

    If you are having issues installing a package using pulsar -p install, this could be because the package has dependencies on libraries that contain native code. This means you will need to have a C++ compiler and Python installed to be able to install it. You can run pulsar -p install --check to see if the Pulsar package manager can build native code on your machine.

    Check out the pre-requisites in the build instructions for your platform for more details.

    Check if your GPU is causing the problem

    If you encounter flickering or other rendering issues, you can stop Pulsar from using your Graphics Processing Unit (GPU) with the --disable-gpu Chromium flag to see if the fault lies with your GPU:

    $ pulsar --disable-gpu
    +

    Chromium (and thus Pulsar) normally uses the GPU to accelerate drawing parts of the interface. --disable-gpu tells Pulsar to not even attempt to do this, and just use the CPU for rendering everything. This means that the parts of the interface that would normally be accelerated using the GPU will instead take slightly longer and render on the CPU. This likely won't make a noticeable difference, but does slightly increase the battery usage on portable devices as the CPU has to work harder to do the things the GPU is optimized for.

    Two other Chromium flags that are useful for debugging are --enable-gpu-rasterization and --force-gpu-rasterization:

    $ pulsar --enable-gpu-rasterization --force-gpu-rasterization
    +

    --enable-gpu-rasterization allows other commands to determine how a layer tile (graphics) should be drawn and --force-gpu-rasterization determines that the Skia GPU backend should be used for drawing layer tiles (only valid with GPU accelerated compositing).

    Be sure to use Chromium flags at the end of the terminal call if you want to use other Pulsar flags as they will not be executed after the Chromium flags e.g.:

    $ pulsar --safe --enable-gpu-rasterization --force-gpu-rasterization
    +
    `,18);function Ee(Oe,Fe){const n=u("ExternalLinkIcon"),h=u("Tabs"),c=u("RouterLink");return C(),x("div",null,[T,e("p",null,[t("Pulsar provides several tools to help you understand unexpected behavior and debug problems. This guide describes some of those tools and a few approaches to help you debug and provide more helpful information when "),e("a",S,[t("submitting issues"),a(n)]),t(":")]),U,d("The below does not exist yet, uncomment or update when it is"),d("Then check for the [latest released version](https://github.com/pulsar-edit/pulsar/releases/latest)."),e("p",null,[t("You can find the latest releases on the "),e("a",N,[t("Pulsar Website"),a(n)]),t(", follow the links for either the latest release or Cirrus CI version. Make sure to mention which version when logging an issue.")]),d(` TODO: This section may need re-instating once/if we have auto-updates working again +::: tabs#Updating + +@tab Linux + +To update to the latest version, you can download it from +[the Pulsar website](https://pulsar-edit.dev/) or [the latest release on GitHub](https://github.com/atom/atom/releases/latest) +and follow the [Installation instructions for Pulsar on Linux](../../getting-started/#installing-pulsar). + +@tab macOS + +If there is a more recent release available, you can update to the most recent +release with the auto-update functionality built in to Pulsar and the +[about package](https://github.com/pulsar-edit/pulsar/tree/master/packages/about). +You can open the About View by using the _Pulsar > About_ menu option to see +whether Pulsar is up-to-date, downloading a new update or click the button to +"Restart and Install Update". + +@tab Windows + +If there is a more recent release available, you can update to the most recent +release with the auto-update functionality built in to Pulsar and the +[about package](https://github.com/pulsar-edit/pulsar/tree/master/packages/about). +You can open the About View by using the _Help > About_ menu option to see +whether Pulsar is up-to-date, downloading a new update or click the button to +"Restart and Install Update". + +::: + +`),A,a(h,{id:"65",data:[{title:"Linux"},{title:"macOS"},{title:"Windows"}],"tab-id":"debugging"},{tab0:s(({title:i,value:r,isActive:l})=>[L]),tab1:s(({title:i,value:r,isActive:l})=>[R]),tab2:s(({title:i,value:r,isActive:l})=>[W]),_:1}),E,e("p",null,[t("If you have packages installed that use native Node modules, when you upgrade to a new version of Pulsar, they might need to be rebuilt. Pulsar detects this and through the "),e("a",O,[t("incompatible-packages package"),a(n)]),t(" displays an indicator in the status bar when this happens.")]),F,q,D,Y,e("p",null,[t("Open Pulsar's "),e("a",G,[t("Settings View"),a(n)]),t(" with "),M,t(": "),V,t(" - "),$,t(": "),z,t(", the "),X,t(": "),B,t(" - "),K,t(": "),j,t(" - "),H,t(": "),J,t(" menu option, or the "),Q,t(" command from the "),e("a",Z,[t("Command Palette"),a(n)]),t(".")]),ee,e("p",null,[t("Check Pulsar's settings in the Settings View, there's a description of most configuration options in the "),a(c,{to:"/docs/launch-manual/sections/using-pulsar/#configuration-key-reference"},{default:s(()=>[t("Basic Customization section")]),_:1}),t('. For example, if you want Pulsar to hide the invisible symbols representing whitespace characters, disable the "Show Invisibles" option.')]),te,e("p",null,[t("Since Pulsar ships with a set of packages and you can also install additional packages yourself, check the list of packages and their settings. For instance, if you'd like to get rid of the vertical line in the middle of the editor, disable the "),e("a",ae,[t("Wrap Guide package"),a(n)]),t(". And if you don't like it when Pulsar strips trailing whitespace or ensures that there's a single trailing newline in the file, you can configure that in the "),e("a",ne,[t("whitespace package's"),a(n)]),t(" settings.")]),se,oe,e("p",null,[t("You might have defined some custom styles, keymaps or snippets in "),a(c,{to:"/docs/launch-manual/sections/using-pulsar/#basic-customization/"},{default:s(()=>[t("one of your configuration files")]),_:1}),t(". In some situations, these personal hacks might be causing the unexpected behavior you're observing so try clearing those files and restarting Pulsar.")]),ie,e("p",null,[t("If a command is not executing when you press a key combination or the wrong command is executing, there might be an issue with the keybinding for that combination. Pulsar ships with the "),e("a",re,[t("Keybinding Resolver"),a(n)]),t(", a neat package which helps you understand what key Pulsar saw you press and the command that was triggered because of it.")]),le,e("p",null,[t("If multiple keybindings are matched, Pulsar determines which keybinding will be executed based on the "),a(c,{to:"/docs/launch-manual/sections/behind-pulsar/#specificity-and-cascade-order"},{default:s(()=>[t("specificity of the selectors and the order in which they were loaded")]),_:1}),t(". If the command you wanted to trigger is listed in the Keybinding Resolver, but wasn't the one that was executed, this is normally explained by one of two causes:")]),ce,e("p",null,[t("Pulsar loads core Pulsar keybindings and package keybindings first, and user-defined keybindings last. Since user-defined keybindings are loaded last, you can use your "),de,t(" file to tweak the keybindings and sort out problems like these. See the "),a(c,{to:"/docs/launch-manual/sections/behind-pulsar/#keymaps-in-depth"},{default:s(()=>[t("Keymaps in Depth section")]),_:1}),t(" for more information.")]),e("p",null,[t("If you notice that a package's keybindings are taking precedence over core Pulsar keybindings, it might be a good idea to report the issue on that package's GitHub repository. You can contact Pulsar maintainers on "),e("a",ue,[t("Pulsar's github discussions"),a(n)]),t(".")]),he,e("p",null,[t("You can determine which fonts are being used to render a specific piece of text by using the Developer Tools. To open the Developer Tools press "),pe,t(": "),ge,t(" - "),me,t(": "),be,t('. Once the Developer Tools are open, click the "Elements" tab. Use the '),e("a",fe,[t("standard tools for finding the element"),a(n)]),t(' containing the text you want to check. Once you have selected the element, you can click the "Computed" tab in the styles pane and scroll to the bottom. The list of fonts being used will be shown there:')]),ke,a(h,{id:"242",data:[{title:"Linux"},{title:"macOS"},{title:"Windows"}],"tab-id":"debugging"},{tab0:s(({title:i,value:r,isActive:l})=>[ve]),tab1:s(({title:i,value:r,isActive:l})=>[ye,_e,we]),tab2:s(({title:i,value:r,isActive:l})=>[Pe]),_:1}),Ce,e("p",null,[t("If Pulsar is taking a long time to start, you can use the "),e("a",xe,[t("Timecop package"),a(n)]),t(" to get insight into where Pulsar spends time while loading.")]),Ie,Te,Se,Ue,Ne,e("p",null,[t("If you're experiencing performance problems in a particular situation, your "),e("a",Ae,[t("Issue reports"),a(n)]),t(" will be more valuable if you include a saved profile from Chrome's CPU profiler that gives some insight into what is slow.")]),Le,e("p",null,[t("To learn more, check out the "),e("a",Re,[t("Chrome documentation on CPU profiling"),a(n)]),t(".")]),We])}const Ye=P(I,[["render",Ee],["__file","debugging.html.vue"]]);export{Ye as default}; diff --git a/assets/debugging.html.361d347b.js b/assets/debugging.html.361d347b.js new file mode 100644 index 0000000000..f8a84cb691 --- /dev/null +++ b/assets/debugging.html.361d347b.js @@ -0,0 +1,22 @@ +import{_ as u,a as m,b as p,c as g,d as f,e as b,f as k,g as v,h as y,i as _}from"./cpu-profile-done.c24435bc.js";import{_ as w,o as x,c as A,a as e,b as t,d as o,w as n,f as l,r as h}from"./app.0e1565ce.js";const C={},I=e("h3",{id:"debugging",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#debugging","aria-hidden":"true"},"#"),t(" Debugging")],-1),T={href:"https://github.com/atom/atom/blob/master/CONTRIBUTING.md#submitting-issues",target:"_blank",rel:"noopener noreferrer"},S=l(`

    Update to the Latest Version

    You might be running into an issue which was already fixed in a more recent version of Atom than the one you're using.

    If you're using a released version, check which version of Atom you're using:

    $ atom --version
    +> Atom    : 1.8.0
    +> Electron: 0.36.8
    +> Chrome  : 47.0.2526.110
    +> Node    : 5.1.1
    +
    `,4),P={href:"https://github.com/atom/atom/releases/latest",target:"_blank",rel:"noopener noreferrer"},U={href:"https://atom.io",target:"_blank",rel:"noopener noreferrer"},R={href:"https://github.com/atom/atom/releases/latest",target:"_blank",rel:"noopener noreferrer"},D={href:"https://github.com/atom/about",target:"_blank",rel:"noopener noreferrer"},q=e("em",null,"Atom > About",-1),Y={href:"https://github.com/atom/about",target:"_blank",rel:"noopener noreferrer"},E=e("em",null,"Help > About",-1),F={href:"https://github.com/atom/atom#building",target:"_blank",rel:"noopener noreferrer"},G=l(`

    Using Safe Mode

    A large part of Atom's functionality comes from packages you can install. Atom will also execute the code in your init script on startup. In some cases, these packages and the code in the init script might be causing unexpected behavior, problems, or performance issues.

    To determine if that is happening, start Atom from the terminal in safe mode:

    $ atom --safe
    +

    This starts Atom, but does not load packages from ~/.atom/packages or ~/.atom/dev/packages and disables loading of your init script. If you can no longer reproduce the problem in safe mode, it's likely it was caused by one of the packages or the init script.

    If removing or commenting out all content from the init script and starting Atom normally still produces the error, then try figuring out which package is causing trouble. Start Atom normally again and open the Settings View with Cmd+,Ctrl+,. Since the Settings View allows you to disable each installed package, you can disable packages one by one until you can no longer reproduce the issue. Restart Atom or reload Atom with Alt+Cmd+Ctrl+LCtrl+Shift+F5 after you disable each package to make sure it's completely gone.

    When you find the problematic package, you can disable or uninstall the package. We strongly recommend creating an issue on the package's GitHub repository.

    Clearing Saved State

    Atom saves a number of things about your environment when you exit in order to restore Atom to the same configuration when you next launch the program. In some cases the state that gets saved can be something undesirable that prevents Atom from working properly. In these cases, you may want to clear the state that Atom has saved.

    DANGER

    :rotatinglight: Danger: Clearing the saved state permanently destroys any state that Atom has saved _across all projects. This includes unsaved changes to files you may have been editing in all projects. This is a destructive action.

    Clearing the saved state can be done by opening a terminal and executing:

    $ atom --clear-window-state
    +

    Reset to Factory Defaults

    In some cases, you may want to reset Atom to "factory defaults", in other words clear all of your configuration and remove all packages. This can easily be done by opening a terminal and executing:

    `,14),L=e("div",{class:"language-command-line ext-command-line line-numbers-mode"},[e("pre",{class:"language-command-line"},[e("code",null,`$ mv ~/.atom ~/.atom-backup +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"})])],-1),N=e("div",{class:"language-command-line ext-command-line line-numbers-mode"},[e("pre",{class:"language-command-line"},[e("code",null,`$ mv ~/.atom ~/.atom-backup +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"})])],-1),O=e("div",{class:"language-command-line ext-command-line line-numbers-mode"},[e("pre",{class:"language-command-line"},[e("code",null,`$ rename %USERPROFILE%\\.atom .atom-backup +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"})])],-1),V=l(`

    Once that is complete, you can launch Atom as normal. Everything will be just as if you first installed Atom.

    Tip

    Tip: The command given above doesn't delete the old configuration, just puts it somewhere that Atom can't find it. If there are pieces of the old configuration you want to retrieve, you can find them in the ~/.atom-backup%USERPROFILE%\\.atom-backup directory.

    Check for Linked Packages

    If you develop or contribute to Atom packages, there may be left-over packages linked to your ~/.atom/packages or ~/.atom/dev/packages directories. You can use the apm links command to list all linked packages:

    $ apm links
    +> /Users/octocat/.atom/dev/packages (0)
    +> \u2514\u2500\u2500 (no links)
    +> /Users/octocat/.atom/packages (1)
    +> \u2514\u2500\u2500 color-picker -> /Users/octocat/github/color-picker
    +

    You can remove links using the apm unlink command:

    $ apm unlink color-picker
    +> Unlinking /Users/octocat/.atom/packages/color-picker \u2713
    +

    See apm links --help and apm unlink --help for more information on these commands.

    Tip

    Tip: You can also use apm unlink --all to easily unlink all packages and themes.

    Check for Incompatible Packages

    `,10),W={href:"https://github.com/atom/incompatible-packages",target:"_blank",rel:"noopener noreferrer"},z=e("p",null,[e("img",{src:u,alt:"Incompatible Packages Status Bar Indicator",title:"Incompatible Packages Status Bar Indicator"})],-1),$=e("p",null,"If you see this indicator, click it and follow the instructions.",-1),B=e("h4",{id:"check-atom-and-package-settings",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#check-atom-and-package-settings","aria-hidden":"true"},"#"),t(" Check Atom and Package Settings")],-1),K=e("p",null,"In some cases, unexpected behavior might be caused by settings in Atom or in one of the packages.",-1),j={href:"https://github.com/atom/settings-view",target:"_blank",rel:"noopener noreferrer"},M=e("kbd",{class:"platform-mac"},"Cmd+,",-1),H=e("kbd",{class:"platform-windows platform-linux"},"Ctrl+,",-1),J=e("span",{class:"platform-mac"},[e("em",null,"Atom > Preferences")],-1),Q=e("span",{class:"platform-windows"},[e("em",null,"File > Preferences")],-1),X=e("span",{class:"platform-linux"},[e("em",null,"Edit > Preferences")],-1),Z={href:"https://github.com/atom/command-palette",target:"_blank",rel:"noopener noreferrer"},ee=e("p",null,[e("img",{src:m,alt:"Settings View"})],-1),te=e("p",null,"Some of these options are also available on a per-language basis which means that they may be different for specific languages, for example JavaScript or Python. To check the per-language settings, open the settings for the language package under the Packages tab in the Settings View, for example the language-javascript or language-python package.",-1),oe={href:"https://atom.io/packages/wrap-guide",target:"_blank",rel:"noopener noreferrer"},ae={href:"https://atom.io/packages/whitespace",target:"_blank",rel:"noopener noreferrer"},ne=e("p",null,[e("img",{src:p,alt:"Package Settings"})],-1),ie=e("h4",{id:"check-your-configuration",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#check-your-configuration","aria-hidden":"true"},"#"),t(" Check Your Configuration")],-1),se=e("h4",{id:"check-your-keybindings",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#check-your-keybindings","aria-hidden":"true"},"#"),t(" Check Your Keybindings")],-1),re={href:"https://atom.io/packages/keybinding-resolver",target:"_blank",rel:"noopener noreferrer"},le=l('

    Show the keybinding resolver with Cmd+.Ctrl+. or with "Keybinding Resolver: Show" from the Command palette. With the Keybinding Resolver shown, press a key combination:

    Keybinding Resolver

    The Keybinding Resolver shows you a list of keybindings that exist for the key combination, where each item in the list has the following:

    • the command for the keybinding
    • the CSS selector used to define the context in which the keybinding is valid
    • the file in which the keybinding is defined

    The keybindings are listed in two colors. All the keybindings that are matched but not executed are shown in gray. The one that is executed, if any, is shown in green. If the command you wanted to trigger isn't listed, then a keybinding for that command hasn't been loaded.

    ',5),ce=e("ul",null,[e("li",null,[e("p",null,"The key combination was not used in the context defined by the keybinding's selector"),e("p",null,[t("For example, you can't trigger the keybinding for the "),e("code",null,"tree-view:add-file"),t(" command if the Tree View is not focused.")])]),e("li",null,[e("p",null,"There is another keybinding that took precedence"),e("p",null,"This often happens when you install a package which defines keybindings that conflict with existing keybindings. If the package's keybindings have selectors with higher specificity or were loaded later, they'll have priority over existing ones.")])],-1),de=e("code",null,"keymap.cson",-1),he={href:"https://github.com/atom/atom/discussions",target:"_blank",rel:"noopener noreferrer"},ue=e("h4",{id:"check-font-rendering-issues",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#check-font-rendering-issues","aria-hidden":"true"},"#"),t(" Check Font Rendering Issues")],-1),me=e("kbd",{class:"platform-mac"},"Alt+Cmd+I",-1),pe=e("kbd",{class:"platform-windows platform-linux"},"Ctrl+Shift+I",-1),ge={href:"https://developers.google.com/web/tools/chrome-devtools/inspect-styles/",target:"_blank",rel:"noopener noreferrer"},fe=l('

    Fonts In Use

    Check for Errors in the Developer Tools

    When an unexpected error occurs in Atom, you will normally see a red notification which provides details about the error and allows you to create an issue on the right repository:

    Exception Notification

    Not all errors are logged with a notification so if you suspect you're experiencing an error but there's no notification, you can also look for errors in the developer tools Console tab. To access the Console tab, press Alt-Cmd-ICtrl-Shift-I to open developer tools and then click the Console tab:

    DevTools Error

    If there are multiple errors, you can scroll down to the bottom of the panel to see the most recent error. Or while reproducing an error, you can right click in the Console tab panel, select Clear console to remove all Console output, and then reproduce the error to see what errors are logged to the Console tab.

    Note

    Note: When running in Dev Mode, the developer tools are automatically shown with the error logged in the Console tab.

    Find Crash Logs

    ',9),be=e("p",null,"When Atom crashes, it should write a core dump if system settings permit. In order to find whether the core dump is written and to where, consult the documentation for your distribution of Linux. Once you have the core dump, you can save it to send in later if it is needed for debugging.",-1),ke=e("p",null,[t("When Atom crashes, you will find a crash dump in Console.app. You can launch Console.app using Spotlight or you can find it in "),e("code",null,"/Applications/Utilities/Console.app"),t(". Once you have launched the program, you can find the latest crash dump by following these instructions:")],-1),ve=e("ol",null,[e("li",null,'Click "User Reports" in the left-most column'),e("li",null,[t("Find the latest entry in the middle column that starts with "),e("code",null,"Atom"),t(" and ends with "),e("code",null,".crash")])],-1),ye=e("p",null,"Once you have the crash dump, you can save it to send in later if it is needed for debugging.",-1),_e=e("p",null,[t("When Atom crashes, you will find a crash dump inside your "),e("code",null,"%TEMP%\\Atom Crashes"),t(" directory. It will be the newest file with the "),e("code",null,".dmp"),t(" extension. Once you have the crash dump, you can save it to send in later if it is needed for debugging.")],-1),we=e("h4",{id:"diagnose-startup-performance",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#diagnose-startup-performance","aria-hidden":"true"},"#"),t(" Diagnose Startup Performance")],-1),xe={href:"https://github.com/atom/timecop",target:"_blank",rel:"noopener noreferrer"},Ae=e("p",null,[e("img",{src:g,alt:"Timecop"})],-1),Ce=e("p",null,"Timecop displays the following information:",-1),Ie=e("ul",null,[e("li",null,"Atom startup times"),e("li",null,"File compilation times"),e("li",null,"Package loading and activation times"),e("li",null,"Theme loading and activation times")],-1),Te=e("p",null,"If a specific package has high load or activation times, you might consider reporting an Issue to the maintainers. You can also disable the package to potentially improve future startup times.",-1),Se=e("h4",{id:"diagnose-runtime-performance",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#diagnose-runtime-performance","aria-hidden":"true"},"#"),t(" Diagnose Runtime Performance")],-1),Pe={href:"https://github.com/atom/atom/blob/master/CONTRIBUTING.md#submitting-issues",target:"_blank",rel:"noopener noreferrer"},Ue=l('

    To run a profile, open the Developer Tools with Alt+Cmd+ICtrl+Shift+I. From there:

    1. Click the Profiles tab
    2. Select "Collect JavaScript CPU Profile"
    3. Click "Start"

    DevTools Profiler

    Once that is done, then perform the slow action to capture a recording. When finished, click "Stop". Switch to the "Chart" view, and a graph of the recorded actions will appear. You can save and post the profile data by clicking "Save" next to the profile's name in the left panel.

    DevTools Profiler

    ',5),Re={href:"https://developer.chrome.com/devtools/docs/cpu-profiling",target:"_blank",rel:"noopener noreferrer"},De=l(`

    Profiling Startup Performance

    If the time for loading the window looks high, you can create a CPU profile for that period using the --profile-startup command line flag when starting Atom:

    $ atom --profile-startup .
    +

    This will automatically capture a CPU profile as Atom is loading and open the Developer Tools once Atom loads. From there:

    1. Click the Profiles tab in the Developer Tools
    2. Select the "startup" profile
    3. Click the "Save" link for the startup profile

    You can then include the startup profile in any Issue you report.

    Check Your Build Tools

    If you are having issues installing a package using apm install, this could be because the package has dependencies on libraries that contain native code. This means you will need to have a C++ compiler and Python installed to be able to install it. You can run apm install --check to see if the Atom package manager can build native code on your machine.

    `,8),qe={href:"https://github.com/atom/atom/tree/master/docs/build-instructions",target:"_blank",rel:"noopener noreferrer"},Ye=l(`

    Check if your GPU is causing the problem

    If you encounter flickering or other rendering issues, you can stop Atom from using your Graphics Processing Unit (GPU) with the --disable-gpu Chromium flag to see if the fault lies with your GPU:

    $ atom --disable-gpu
    +

    Chromium (and thus Atom) normally uses the GPU to accelerate drawing parts of the interface. --disable-gpu tells Atom to not even attempt to do this, and just use the CPU for rendering everything. This means that the parts of the interface that would normally be accelerated using the GPU will instead take slightly longer and render on the CPU. This likely won't make a noticeable difference, but does slightly increase the battery usage as the CPU has to work harder to do the things the GPU is optimized for.

    Two other Chromium flags that are useful for debugging are --enable-gpu-rasterization and --force-gpu-rasterization:

    $ atom --enable-gpu-rasterization --force-gpu-rasterization
    +

    --enable-gpu-rasterization allows other commands to determine how a layer tile (graphics) should be drawn and --force-gpu-rasterization determines that the Skia GPU backend should be used for drawing layer tiles (only valid with GPU accelerated compositing).

    Be sure to use Chromium flags at the end of the terminal call if you want to use other Atom flags as they will not be executed after the Chromium flags e.g.:

    $ atom --safe --enable-gpu-rasterization --force-gpu-rasterization
    +
    `,9);function Ee(Fe,Ge){const a=h("ExternalLinkIcon"),c=h("RouterLink"),d=h("Tabs");return x(),A("div",null,[I,e("p",null,[t("Atom provides several tools to help you understand unexpected behavior and debug problems. This guide describes some of those tools and a few approaches to help you debug and provide more helpful information when "),e("a",T,[t("submitting issues"),o(a)]),t(":")]),S,e("p",null,[t("Then check for the "),e("a",P,[t("latest Stable version"),o(a)]),t(".")]),o(d,{id:"19",data:[{title:"Linux"},{title:"Mac"},{title:"Windows"}],"tab-id":"Updating"},{tab0:n(({title:i,value:s,isActive:r})=>[e("p",null,[t("To update to the latest version, you can download it from "),e("a",U,[t("the atom.io website"),o(a)]),t(" or "),e("a",R,[t("the latest release on GitHub"),o(a)]),t(" and follow the "),o(c,{to:"/getting-started/sections/installing-atom/#installing-atom-on-linux"},{default:n(()=>[t("Installation instructions for Atom on Linux")]),_:1}),t(".")])]),tab1:n(({title:i,value:s,isActive:r})=>[e("p",null,[t("If there is a more recent release available, you can update to the most recent release with the auto-update functionality built in to Atom and the "),e("a",D,[t("about package"),o(a)]),t(". You can open the About View by using the "),q,t(' menu option to see whether Atom is up-to-date, downloading a new update or click the button to "Restart and Install Update".')])]),tab2:n(({title:i,value:s,isActive:r})=>[e("p",null,[t("If there is a more recent release available, you can update to the most recent release with the auto-update functionality built in to Atom and the "),e("a",Y,[t("about package"),o(a)]),t(". You can open the About View by using the "),E,t(' menu option to see whether Atom is up-to-date, downloading a new update or click the button to "Restart and Install Update".')])]),_:1}),e("p",null,[t("If you're building Atom from source, pull down the latest version of master and "),e("a",F,[t("re-build"),o(a)]),t(".")]),G,o(d,{id:"79",data:[{title:"Linux"},{title:"Mac"},{title:"Windows"}],"tab-id":"Debugging"},{tab0:n(({title:i,value:s,isActive:r})=>[L]),tab1:n(({title:i,value:s,isActive:r})=>[N]),tab2:n(({title:i,value:s,isActive:r})=>[O]),_:1}),V,e("p",null,[t("If you have packages installed that use native Node modules, when you upgrade to a new version of Atom, they might need to be rebuilt. Atom detects this and through the "),e("a",W,[t("incompatible-packages package"),o(a)]),t(" displays an indicator in the status bar when this happens.")]),z,$,B,K,e("p",null,[t("Open Atom's "),e("a",j,[t("Settings View"),o(a)]),t(" with "),M,H,t(", the "),J,Q,X,t(' menu option, or the "Settings View: Open" command from the '),e("a",Z,[t("Command Palette"),o(a)]),t(".")]),ee,e("p",null,[t("Check Atom's settings in the Settings View, there's a description of most configuration options in the "),o(c,{to:"/using-atom/sections/basic-customization/#configuration-key-reference"},{default:n(()=>[t("Basic Customization section")]),_:1}),t('. For example, if you want Atom to hide the invisible symbols representing whitespace characters, disable the "Show Invisibles" option.')]),te,e("p",null,[t("Since Atom ships with a set of packages and you can also install additional packages yourself, check the list of packages and their settings. For instance, if you'd like to get rid of the vertical line in the middle of the editor, disable the "),e("a",oe,[t("Wrap Guide package"),o(a)]),t(". And if you don't like it when Atom strips trailing whitespace or ensures that there's a single trailing newline in the file, you can configure that in the "),e("a",ae,[t("whitespace package's"),o(a)]),t(" settings.")]),ne,ie,e("p",null,[t("You might have defined some custom styles, keymaps or snippets in "),o(c,{to:"/using-atom/sections/basic-customization/"},{default:n(()=>[t("one of your configuration files")]),_:1}),t(". In some situations, these personal hacks might be causing the unexpected behavior you're observing so try clearing those files and restarting Atom.")]),se,e("p",null,[t("If a command is not executing when you press a key combination or the wrong command is executing, there might be an issue with the keybinding for that combination. Atom ships with the "),e("a",re,[t("Keybinding Resolver"),o(a)]),t(", a neat package which helps you understand what key Atom saw you press and the command that was triggered because of it.")]),le,e("p",null,[t("If multiple keybindings are matched, Atom determines which keybinding will be executed based on the "),o(c,{to:"/behind-atom/sections/keymaps-in-depth/#specificity-and-cascade-order"},{default:n(()=>[t("specificity of the selectors and the order in which they were loaded")]),_:1}),t(". If the command you wanted to trigger is listed in the Keybinding Resolver, but wasn't the one that was executed, this is normally explained by one of two causes:")]),ce,e("p",null,[t("Atom loads core Atom keybindings and package keybindings first, and user-defined keybindings last. Since user-defined keybindings are loaded last, you can use your "),de,t(" file to tweak the keybindings and sort out problems like these. See the "),o(c,{to:"/behind-atom/sections/keymaps-in-depth/"},{default:n(()=>[t("Keymaps in Depth section")]),_:1}),t(" for more information.")]),e("p",null,[t("If you notice that a package's keybindings are taking precedence over core Atom keybindings, it might be a good idea to report the issue on that package's GitHub repository. You can contact atom maintainers on "),e("a",he,[t("Atom's github discussions"),o(a)])]),ue,e("p",null,[t("You can determine which fonts are being used to render a specific piece of text by using the Developer Tools. To open the Developer Tools press "),me,pe,t('. Once the Developer Tools are open, click the "Elements" tab. Use the '),e("a",ge,[t("standard tools for finding the element"),o(a)]),t(' containing the text you want to check. Once you have selected the element, you can click the "Computed" tab in the styles pane and scroll to the bottom. The list of fonts being used will be shown there:')]),fe,o(d,{id:"256",data:[{title:"Linux"},{title:"Mac"},{title:"Windows"}],"tab-id":"Debugging"},{tab0:n(({title:i,value:s,isActive:r})=>[be]),tab1:n(({title:i,value:s,isActive:r})=>[ke,ve,ye]),tab2:n(({title:i,value:s,isActive:r})=>[_e]),_:1}),we,e("p",null,[t("If Atom is taking a long time to start, you can use the "),e("a",xe,[t("Timecop package"),o(a)]),t(" to get insight into where Atom spends time while loading.")]),Ae,Ce,Ie,Te,Se,e("p",null,[t("If you're experiencing performance problems in a particular situation, your "),e("a",Pe,[t("Issue reports"),o(a)]),t(" will be more valuable if you include a saved profile from Chrome's CPU profiler that gives some insight into what is slow.")]),Ue,e("p",null,[t("To learn more, check out the "),e("a",Re,[t("Chrome documentation on CPU profiling"),o(a)]),t(".")]),De,e("p",null,[t("Check out the pre-requisites in the "),e("a",qe,[t("build instructions"),o(a)]),t(" for your platform for more details.")]),Ye])}const Oe=w(C,[["render",Ee],["__file","debugging.html.vue"]]);export{Oe as default}; diff --git a/assets/debugging.html.3bdb6a8e.js b/assets/debugging.html.3bdb6a8e.js new file mode 100644 index 0000000000..f650476e56 --- /dev/null +++ b/assets/debugging.html.3bdb6a8e.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-c040e972","path":"/docs/launch-manual/sections/core-hacking/sections/debugging.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Debugging","slug":"debugging","link":"#debugging","children":[{"level":3,"title":"Update to the Latest Version","slug":"update-to-the-latest-version","link":"#update-to-the-latest-version","children":[]},{"level":3,"title":"Using Safe Mode","slug":"using-safe-mode","link":"#using-safe-mode","children":[]},{"level":3,"title":"Clearing Saved State","slug":"clearing-saved-state","link":"#clearing-saved-state","children":[]},{"level":3,"title":"Reset to Factory Defaults","slug":"reset-to-factory-defaults","link":"#reset-to-factory-defaults","children":[]},{"level":3,"title":"Check for Linked Packages","slug":"check-for-linked-packages","link":"#check-for-linked-packages","children":[]},{"level":3,"title":"Check for Incompatible Packages","slug":"check-for-incompatible-packages","link":"#check-for-incompatible-packages","children":[]},{"level":3,"title":"Check Pulsar and Package Settings","slug":"check-pulsar-and-package-settings","link":"#check-pulsar-and-package-settings","children":[]},{"level":3,"title":"Check Your Configuration","slug":"check-your-configuration","link":"#check-your-configuration","children":[]},{"level":3,"title":"Check Your Keybindings","slug":"check-your-keybindings","link":"#check-your-keybindings","children":[]},{"level":3,"title":"Check Font Rendering Issues","slug":"check-font-rendering-issues","link":"#check-font-rendering-issues","children":[]},{"level":3,"title":"Check for Errors in the Developer Tools","slug":"check-for-errors-in-the-developer-tools","link":"#check-for-errors-in-the-developer-tools","children":[]},{"level":3,"title":"Find Crash Logs","slug":"find-crash-logs","link":"#find-crash-logs","children":[]},{"level":3,"title":"Diagnose Startup Performance","slug":"diagnose-startup-performance","link":"#diagnose-startup-performance","children":[]},{"level":3,"title":"Diagnose Runtime Performance","slug":"diagnose-runtime-performance","link":"#diagnose-runtime-performance","children":[]},{"level":3,"title":"Profiling Startup Performance","slug":"profiling-startup-performance","link":"#profiling-startup-performance","children":[]},{"level":3,"title":"Check Your Build Tools","slug":"check-your-build-tools","link":"#check-your-build-tools","children":[]},{"level":3,"title":"Check if your GPU is causing the problem","slug":"check-if-your-gpu-is-causing-the-problem","link":"#check-if-your-gpu-is-causing-the-problem","children":[]}]}],"git":{"updatedTime":1706504081000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":2}]},"readingTime":{"minutes":10.47,"words":3142},"filePathRelative":"docs/launch-manual/sections/core-hacking/sections/debugging.md"}');export{e as data}; diff --git a/assets/debugging.html.85a630ac.js b/assets/debugging.html.85a630ac.js new file mode 100644 index 0000000000..72e26f98e6 --- /dev/null +++ b/assets/debugging.html.85a630ac.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-28cbb2a8","path":"/docs/atom-archive/hacking-atom/sections/debugging.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"Debugging","slug":"debugging","link":"#debugging","children":[]}],"git":{"updatedTime":1664050274000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":3}]},"readingTime":{"minutes":10.29,"words":3087},"filePathRelative":"docs/atom-archive/hacking-atom/sections/debugging.md"}');export{e as data}; diff --git a/assets/dep-cop.6353fb49.js b/assets/dep-cop.6353fb49.js new file mode 100644 index 0000000000..9981287cb5 --- /dev/null +++ b/assets/dep-cop.6353fb49.js @@ -0,0 +1 @@ +const s="/assets/dep-cop.d194dc31.png";export{s as _}; diff --git a/assets/dep-cop.d194dc31.png b/assets/dep-cop.d194dc31.png new file mode 100644 index 0000000000..f322ec1fe5 Binary files /dev/null and b/assets/dep-cop.d194dc31.png differ diff --git a/assets/detective.00171520.png b/assets/detective.00171520.png new file mode 100644 index 0000000000..1d2350e172 Binary files /dev/null and b/assets/detective.00171520.png differ diff --git a/assets/detective.e7e89ed0.js b/assets/detective.e7e89ed0.js new file mode 100644 index 0000000000..9489a82fa5 --- /dev/null +++ b/assets/detective.e7e89ed0.js @@ -0,0 +1 @@ +const s="/assets/detective.00171520.png";export{s as _}; diff --git a/assets/dev-tools.1b7b2813.js b/assets/dev-tools.1b7b2813.js new file mode 100644 index 0000000000..f00fdee7e7 --- /dev/null +++ b/assets/dev-tools.1b7b2813.js @@ -0,0 +1 @@ +const s="/assets/dev-tools.c249e41a.png";export{s as _}; diff --git a/assets/dev-tools.c249e41a.png b/assets/dev-tools.c249e41a.png new file mode 100644 index 0000000000..ad4b19a03b Binary files /dev/null and b/assets/dev-tools.c249e41a.png differ diff --git a/assets/developing-node-modules.html.270ef95c.js b/assets/developing-node-modules.html.270ef95c.js new file mode 100644 index 0000000000..e4740fb620 --- /dev/null +++ b/assets/developing-node-modules.html.270ef95c.js @@ -0,0 +1,38 @@ +import{_ as e,o as n,c as i,f as d}from"./app.0e1565ce.js";const l={},a=d(`

    Developing Node Modules

    Atom contains a number of packages that are Node modules instead of Atom packages. If you want to make changes to the Node modules, for instance atom-keymap, you have to link them into the development environment differently than you would a normal Atom package.

    Linking a Node Module Into Your Atom Dev Environment

    Here are the steps to run a local version of a Node module within Atom. We're using atom-keymap as an example:

    ::: tab#developing-node-modules

    `,8),o=[a];function t(m,s){return n(),i("div",null,o)}const r=e(l,[["render",t],["__file","developing-node-modules.html.vue"]]);export{r as default}; diff --git a/assets/developing-node-modules.html.7558848f.js b/assets/developing-node-modules.html.7558848f.js new file mode 100644 index 0000000000..8c24b0472b --- /dev/null +++ b/assets/developing-node-modules.html.7558848f.js @@ -0,0 +1,54 @@ +import{_ as i,o as c,c as r,d as u,w as e,a as n,b as s,f as d,r as p}from"./app.0e1565ce.js";const m={},v=n("h2",{id:"developing-node-modules",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#developing-node-modules","aria-hidden":"true"},"#"),s(" Developing Node Modules")],-1),b=n("p",null,[s("Pulsar contains a number of packages that are Node modules instead of Pulsar packages. If you want to make changes to the Node modules, for instance "),n("code",null,"atom-keymap"),s(", you have to link them into the development environment differently than you would a normal Pulsar package.")],-1),k=n("h3",{id:"linking-a-node-module-into-your-pulsar-dev-environment",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#linking-a-node-module-into-your-pulsar-dev-environment","aria-hidden":"true"},"#"),s(" Linking a Node Module Into Your Pulsar Dev Environment")],-1),h=n("p",null,[s("Here are the steps to run a local version of a Node module within Pulsar. We're using "),n("code",null,"atom-keymap"),s(" as an example:")],-1),f=n("div",{class:"language-bash ext-sh line-numbers-mode"},[n("pre",{class:"language-bash"},[n("code",null,[s("$ "),n("span",{class:"token function"},"git"),s(` clone https://github.com/pulsar-edit/atom-keymap.git +$ `),n("span",{class:"token builtin class-name"},"cd"),s(` atom-keymap +$ `),n("span",{class:"token function"},"npm"),s(),n("span",{class:"token function"},"install"),s(` +$ `),n("span",{class:"token function"},"npm"),s(),n("span",{class:"token function"},"link"),s(` +$ `),n("span",{class:"token builtin class-name"},"cd"),s(),n("span",{class:"token operator"},"<"),s("WHERE YOU CLONED PULSAR"),n("span",{class:"token operator"},">"),s(` +$ `),n("span",{class:"token function"},"npm"),s(),n("span",{class:"token function"},"link"),s(` atom-keymap + +`),n("span",{class:"token comment"},"# This is the special step, it makes the Node module work with Pulsar's version of Node"),s(` +$ pulsar `),n("span",{class:"token parameter variable"},"-p"),s(` rebuild + +`),n("span",{class:"token comment"},"# If you have cloned Pulsar in a different location than ~/github/pulsar"),s(` +`),n("span",{class:"token comment"},"# you need to set the following environment variable"),s(` +$ `),n("span",{class:"token builtin class-name"},"export"),s(),n("span",{class:"token assign-left variable"},"ATOM_DEV_RESOURCE_PATH"),n("span",{class:"token operator"},"="),n("span",{class:"token operator"},"<"),s("WHERE YOU CLONED PULSAR"),n("span",{class:"token operator"},">"),s(` + +`),n("span",{class:"token comment"},"# Should work!"),s(` +$ pulsar `),n("span",{class:"token parameter variable"},"--dev"),s(),n("span",{class:"token builtin class-name"},"."),s(` +`)])]),n("div",{class:"line-numbers","aria-hidden":"true"},[n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"})])],-1),g=n("div",{class:"language-bash ext-sh line-numbers-mode"},[n("pre",{class:"language-bash"},[n("code",null,[s("$ "),n("span",{class:"token function"},"git"),s(` clone https://github.com/pulsar-edit/atom-keymap.git +$ `),n("span",{class:"token builtin class-name"},"cd"),s(` atom-keymap +$ `),n("span",{class:"token function"},"npm"),s(),n("span",{class:"token function"},"install"),s(` +$ `),n("span",{class:"token function"},"npm"),s(),n("span",{class:"token function"},"link"),s(` +$ `),n("span",{class:"token builtin class-name"},"cd"),s(),n("span",{class:"token operator"},"<"),s("WHERE YOU CLONED PULSAR"),n("span",{class:"token operator"},">"),s(` +$ `),n("span",{class:"token function"},"npm"),s(),n("span",{class:"token function"},"link"),s(` atom-keymap + +`),n("span",{class:"token comment"},"# This is the special step, it makes the Node module work with Pulsar's version of Node"),s(` +$ pulsar `),n("span",{class:"token parameter variable"},"-p"),s(` rebuild + +`),n("span",{class:"token comment"},"# If you have cloned Pulsar in a different location than ~/github/pulsar"),s(` +`),n("span",{class:"token comment"},"# you need to set the following environment variable"),s(` +$ `),n("span",{class:"token builtin class-name"},"export"),s(),n("span",{class:"token assign-left variable"},"ATOM_DEV_RESOURCE_PATH"),n("span",{class:"token operator"},"="),n("span",{class:"token operator"},"<"),s("WHERE YOU CLONED PULSAR"),n("span",{class:"token operator"},">"),s(` + +`),n("span",{class:"token comment"},"# Should work!"),s(` +$ pulsar `),n("span",{class:"token parameter variable"},"--dev"),s(),n("span",{class:"token builtin class-name"},"."),s(` +`)])]),n("div",{class:"line-numbers","aria-hidden":"true"},[n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"})])],-1),E=n("div",{class:"language-bash ext-sh line-numbers-mode"},[n("pre",{class:"language-bash"},[n("code",null,[s("$ "),n("span",{class:"token function"},"git"),s(` clone https://github.com/pulsar-edit/atom-keymap.git +$ `),n("span",{class:"token builtin class-name"},"cd"),s(` atom-keymap +$ `),n("span",{class:"token function"},"npm"),s(),n("span",{class:"token function"},"install"),s(` +$ `),n("span",{class:"token function"},"npm"),s(),n("span",{class:"token function"},"link"),s(` +$ `),n("span",{class:"token builtin class-name"},"cd"),s(),n("span",{class:"token operator"},"<"),s("WHERE YOU CLONED PULSAR"),n("span",{class:"token operator"},">"),s(` +$ `),n("span",{class:"token function"},"npm"),s(),n("span",{class:"token function"},"link"),s(` atom-keymap + +`),n("span",{class:"token comment"},"# This is the special step, it makes the Node module work with Pulsar's version of Node"),s(` +$ pulsar `),n("span",{class:"token parameter variable"},"-p"),s(` rebuild + +`),n("span",{class:"token comment"},"# If you have cloned Pulsar in a different location than %USERPROFILE%\\github\\pulsar"),s(` +`),n("span",{class:"token comment"},"# you need to set the following environment variable"),s(` +$ setx `),n("span",{class:"token assign-left variable"},"ATOM_DEV_RESOURCE_PATH"),n("span",{class:"token operator"},"="),n("span",{class:"token operator"},"<"),s("WHERE YOU CLONED PULSAR"),n("span",{class:"token operator"},">"),s(` + +`),n("span",{class:"token comment"},"# Should work!"),s(` +$ pulsar `),n("span",{class:"token parameter variable"},"--dev"),s(),n("span",{class:"token builtin class-name"},"."),s(` +`)])]),n("div",{class:"line-numbers","aria-hidden":"true"},[n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"})])],-1),_=d(`

    After you get the Node module linked and working, every time you make a change to the Node module's code, you will have to exit Pulsar and do the following:

    $ cd <WHERE YOU CLONED THE NODE MODULE>
    +$ npm install
    +$ cd <WHERE YOU CLONED PULSAR>
    +$ pulsar -p rebuild
    +$ pulsar --dev .
    +
    `,2);function $(y,N){const o=p("Tabs");return c(),r("div",null,[v,b,k,h,u(o,{id:"12",data:[{title:"Linux"},{title:"Mac"},{title:"Windows"}],"tab-id":"behind-pulsar"},{tab0:e(({title:a,value:l,isActive:t})=>[f]),tab1:e(({title:a,value:l,isActive:t})=>[g]),tab2:e(({title:a,value:l,isActive:t})=>[E]),_:1}),_])}const P=i(m,[["render",$],["__file","developing-node-modules.html.vue"]]);export{P as default}; diff --git a/assets/developing-node-modules.html.e77f2326.js b/assets/developing-node-modules.html.e77f2326.js new file mode 100644 index 0000000000..3c63c6956f --- /dev/null +++ b/assets/developing-node-modules.html.e77f2326.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-15f047dd","path":"/docs/atom-archive/behind-atom/sections/developing-node-modules.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"Developing Node Modules","slug":"developing-node-modules","link":"#developing-node-modules","children":[]}],"git":{"updatedTime":1668309800000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":4},{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":1.02,"words":306},"filePathRelative":"docs/atom-archive/behind-atom/sections/developing-node-modules.md"}');export{e as data}; diff --git a/assets/developing-node-modules.html.f010be5c.js b/assets/developing-node-modules.html.f010be5c.js new file mode 100644 index 0000000000..aaa0e900d1 --- /dev/null +++ b/assets/developing-node-modules.html.f010be5c.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-1a40e700","path":"/docs/launch-manual/sections/behind-pulsar/sections/developing-node-modules.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Developing Node Modules","slug":"developing-node-modules","link":"#developing-node-modules","children":[{"level":3,"title":"Linking a Node Module Into Your Pulsar Dev Environment","slug":"linking-a-node-module-into-your-pulsar-dev-environment","link":"#linking-a-node-module-into-your-pulsar-dev-environment","children":[]}]}],"git":{"updatedTime":1668992376000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":2}]},"readingTime":{"minutes":1.24,"words":371},"filePathRelative":"docs/launch-manual/sections/behind-pulsar/sections/developing-node-modules.md"}');export{e as data}; diff --git a/assets/development.9a152ab5.png b/assets/development.9a152ab5.png new file mode 100644 index 0000000000..3db8723d20 Binary files /dev/null and b/assets/development.9a152ab5.png differ diff --git a/assets/devtools-error.d36c506a.png b/assets/devtools-error.d36c506a.png new file mode 100644 index 0000000000..c05d776207 Binary files /dev/null and b/assets/devtools-error.d36c506a.png differ diff --git a/assets/devtools.5a855fb4.png b/assets/devtools.5a855fb4.png new file mode 100644 index 0000000000..8d8a75371b Binary files /dev/null and b/assets/devtools.5a855fb4.png differ diff --git a/assets/document-style.html.83e9f03f.js b/assets/document-style.html.83e9f03f.js new file mode 100644 index 0000000000..cb553de20d --- /dev/null +++ b/assets/document-style.html.83e9f03f.js @@ -0,0 +1,21 @@ +import{_ as o,o as s,c as a,a as e,b as t,d as n,f as d,r}from"./app.0e1565ce.js";const l={},c=d(`

    Documentation style & reusable components

    The documentation for the project should maintain a consistent style where possible. This covers a number of aspects such as writing style, naming conventions, folder structure, links etc.

    All docs are currently in American English (en-US) but localization is planned.

    Structure

    The main structure for documentation can be seen in docs/docs. There are a number of "root" sections:

    • launch-manual - For the current main Pulsar documentation
    • packages - Currently holds wiki info from the atom package repos
    • resources - For other referenced docs
    • blog - For the website blog
    • atom-archive - For "as is" archived Atom documentation

    Within each section is an index.md which will usually contain info about each sub-section as well as links to them. These correspond to the second level items on the sidebar.

    Inside sections are the sub-sections which group more specific topics. These also have an index.md which corresponds to the third level item on the sidebar. This file is displayed on the website as a single long documenent but is actually created from a number of @include() lines which reference individual sections within the next sections directory. These should be relative to the location of index.md e.g. @include(sections/pulsar-packages.md). This file also contains the frontmatter for defining the title, language and description of the file and should also be the first level heading for the page. Here is also where you can place a container such as Under Construction to apply to the entire page.

    Inside the next sections directory should be the actual content of the document. Each section should start with a second level header and should not contain any frontmatter.

    Internal links can just be to the header (e.g.[Structure](#structure)), this to all sections included on the parent index.md so care should be made to not create any duplicate headers.

    All other links should be relative but do not need to reference the index file itself (e.g.[Installing](../getting-started#installing)) will find the heading #installing within the index file in the getting-started directory above.

    Images should be added to [pulsar-assets](https://github.com/pulsar-edit/pulsar-assets) and referenced from the package imported from it. This is done via an alias on the .vuepress/config.js file which adds most of the path for you: '@images': path.resolve(__dirname, '../../node_modules/pulsar-assets/images')
    so the link to your image would just be ![myImage](@images/pulsar/myImage.png "My Image").

    Naming

    The name of the application is Pulsar and should be capitalized as such. Whilst the website and GitHub org name is Pulsar-Edit, this should not be used within documentation outside of links to the GitHub org or website.

    Operating systems should be named as such:

    • Linux - All GNU/Linux distributions
    • macOS - Apple's current operating system family
    • Windows - Microsoft Windows family of operating systems

    This is also the order they should appear in within the tab switcher.

    When using the #tabs switcher they should be in this order.

    When referencing them inline then they should be abbreviated to the following, strongly emphasized and separated by a -:

    • Linux = LNX
    • macOS = MAC
    • Windows = WIN

    To keep order consistent it should be LNX -> MAC -> WIN. If instructions: common to two then it should either be LNX/MAC, LNX/WIN -> MAC/WIN

    For Linux we may sometimes need to reference particular distros or families of distributions. We currently use:

    • Ubuntu/Debian for all distributions based on Debian or Ubuntu
    • Fedora/RHEL for all distrububtions based on Fedora Linux & Red Hat Red Hat Enterprise Linux. This includes AlmaLinux, CentOS, Rocky Linux etc.
    • Arch - for all Arch based distributions such as Manjaro, Garuda, ArcoLinux etc.
    • OpenSUSE - for all OpenSUSE based distributions such as GeckoLinux

    We may need to add more in the future but generally users of less popular or more technical distributions such as Gentoo or NixOS understand how to adapt to their OS from the instructions above.

    Containers

    Where you want to display an info, warning or tab/code switcher in the document you should use a container with the ::: syntax.

    e.g.

    ::: tabs#filename
    +
    +@tab Linux
    +
    +Lorem ipsum dolor sit amet...
    +
    +@tab macOS
    +
    +Lorem ipsum dolor sit amet...
    +
    +@tab Windows
    +
    +Lorem ipsum dolor sit amet...
    +
    +:::
    +

    or

    ::: tip My Helpful Tip
    +
    +You might want to do X to get Y
    +
    +:::
    +
    `,31),u={href:"https://github.com/pulsar-edit/pulsar-edit.github.io/blob/main/common-text-blocks.md",target:"_blank",rel:"noopener noreferrer"},h={href:"https://vuepress-theme-hope.github.io/v2/guide/get-started/markdown.html#theme-enhancement",target:"_blank",rel:"noopener noreferrer"},m=e("h3",{id:"writing-style",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#writing-style","aria-hidden":"true"},"#"),t(" Writing style")],-1),p=e("p",null,"TODO: Needs consensus",-1);function b(g,f){const i=r("ExternalLinkIcon");return s(),a("div",null,[c,e("p",null,[t("You can also find a list of currently maintained preformatted containers for various purposes at "),e("a",u,[t("pulsar-edit.github.io/common-text-blocks.md"),n(i)]),t(".")]),e("p",null,[t("See "),e("a",h,[t("VuePress Hope documentation"),n(i)])]),m,p])}const y=o(l,[["render",b],["__file","document-style.html.vue"]]);export{y as default}; diff --git a/assets/document-style.html.c693adb9.js b/assets/document-style.html.c693adb9.js new file mode 100644 index 0000000000..839f6cb49a --- /dev/null +++ b/assets/document-style.html.c693adb9.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-71e2ac9c","path":"/docs/resources/website/sections/document-style.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Documentation style & reusable components","slug":"documentation-style-reusable-components","link":"#documentation-style-reusable-components","children":[{"level":3,"title":"Structure","slug":"structure","link":"#structure","children":[]},{"level":3,"title":"Links","slug":"links","link":"#links","children":[]},{"level":3,"title":"Naming","slug":"naming","link":"#naming","children":[]},{"level":3,"title":"Containers","slug":"containers","link":"#containers","children":[]},{"level":3,"title":"Writing style","slug":"writing-style","link":"#writing-style","children":[]}]}],"git":{"updatedTime":1668356740000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":2.58,"words":774},"filePathRelative":"docs/resources/website/sections/document-style.md"}');export{e as data}; diff --git a/assets/donate.html.21bc69cb.js b/assets/donate.html.21bc69cb.js new file mode 100644 index 0000000000..7c27516289 --- /dev/null +++ b/assets/donate.html.21bc69cb.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-0100cb31","path":"/donate.html","title":"Donate to Pulsar","lang":"en-US","frontmatter":{"title":"Donate to Pulsar","path":"/donate/","sitemap":{"priority":0.8,"changefreq":"weekly"}},"excerpt":"","headers":[],"git":{"updatedTime":1673576796000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1},{"name":"confused-Techie","email":"dev@lhbasics.com","commits":1},{"name":"confused_techie","email":"dev@lhbasics.com","commits":1}]},"readingTime":{"minutes":0.5,"words":151},"filePathRelative":"donate.md"}');export{e as data}; diff --git a/assets/donate.html.b9be285d.js b/assets/donate.html.b9be285d.js new file mode 100644 index 0000000000..3a03919aa3 --- /dev/null +++ b/assets/donate.html.b9be285d.js @@ -0,0 +1 @@ +import{_ as r,o as s,c as a,a as e,b as t,d as n,r as c}from"./app.0e1565ce.js";const l={},i=e("p",null,"Pulsar will always be free and fully open to the community but we do have some running costs associated with hosting the package repository along with other expenses.",-1),h={href:"https://opencollective.com/pulsar-edit",target:"_blank",rel:"noopener noreferrer"},p=e("a",{href:"https://opencollective.com/pulsar-edit/donate",target:"_blank"},[e("img",{src:"https://opencollective.com/webpack/donate/button@2x.png?color=blue",width:"300"})],-1),u={href:"https://github.com/sponsors/pulsar-edit",target:"_blank",rel:"noopener noreferrer"},d=e("iframe",{src:"https://github.com/sponsors/pulsar-edit/card",title:"Sponsor pulsar-edit",height:"225",width:"600",style:{border:"0"}},null,-1),_=e("p",null,"We are currently talking about other donation platforms that we might support so watch this space!",-1);function f(m,b){const o=c("ExternalLinkIcon");return s(),a("div",null,[i,e("p",null,[t("If you wish to contribute to the running costs of the project we have an "),e("a",h,[t("OpenCollective"),n(o)]),t(" where you can safely donate and view how the money is being used.")]),p,e("p",null,[t("If you'd rather contribute via "),e("a",u,[t("GitHub Sponsors"),n(o)]),t(" you can do this on our GitHub profile.")]),d,_])}const w=r(l,[["render",f],["__file","donate.html.vue"]]);export{w as default}; diff --git a/assets/download.html.45b3ffc4.js b/assets/download.html.45b3ffc4.js new file mode 100644 index 0000000000..5c5dd945fb --- /dev/null +++ b/assets/download.html.45b3ffc4.js @@ -0,0 +1 @@ +import{_ as h,o as _,c as p,a as e,b as t,d as l,e as i,w as s,f as c,r as d}from"./app.0e1565ce.js";const g={},b=c('

    "Rolling" and "Regular" Releases

    We have "Rolling" and "Regular" releases to choose from. These differ only in the frequency of updates they receive.

    Note: There are no automatic updates at this time, regardless of which type of download you choose.

    Rolling Release

    ',2),y=e("code",null,"master",-1),m={href:"https://github.com/pulsar-edit/pulsar/actions/workflows/build.yml",target:"_blank",rel:"noopener noreferrer"},f={href:"https://cirrus-ci.com/github/pulsar-edit/pulsar",target:"_blank",rel:"noopener noreferrer"},x=e("p",null,[t("While we do our best to review and test each PR thoroughly before merging there is a chance that something falls through the cracks in which case be prepared to upgrade or switch to our "),e("a",{href:"#regular-releases"},"Regular Releases"),t(" until it is fixed.")],-1),w=e("p",null,[t("See below for links to the latest binaries for each OS. If you want to download manually or pick a binary from another branch or PR, then follow the "),e("a",{href:"#manual-download"},"manual instructions"),t(".")],-1),k={class:"custom-container details"},v=e("summary",null,"Linux",-1),R=e("div",{class:"custom-container info"},[e("p",{class:"custom-container-title"},"Info"),e("p",null,[t("We currently have no Rolling Releases for Linux package managers - see "),e("a",{href:"#regular-releases"},"Regular Releases"),t(" for these instead.")])],-1),A=e("p",null,[e("strong",null,"x86_64"),t(" - For most desktops and laptops with Intel or AMD processors")],-1),M=e("thead",null,[e("tr",null,[e("th",{style:{"text-align":"center"}},"Package"),e("th",{style:{"text-align":"center"}},"Distribution")])],-1),P={style:{"text-align":"center"}},S={href:"https://download.pulsar-edit.dev/?os=linux&type=linux_deb",target:"_blank",rel:"noopener noreferrer"},I=e("td",{style:{"text-align":"center"}},"Debian/Ubuntu etc.",-1),L={style:{"text-align":"center"}},C={href:"https://download.pulsar-edit.dev/?os=linux&type=linux_rpm",target:"_blank",rel:"noopener noreferrer"},D=e("td",{style:{"text-align":"center"}},"Fedora/RHEL etc.",-1),T={style:{"text-align":"center"}},z={href:"https://download.pulsar-edit.dev/?os=linux&type=linux_appimage",target:"_blank",rel:"noopener noreferrer"},W=e("sup",null,"[1][2]",-1),q=e("td",{style:{"text-align":"center"}},"All distributions",-1),F={style:{"text-align":"center"}},H={href:"https://download.pulsar-edit.dev/?os=linux&type=linux_tar",target:"_blank",rel:"noopener noreferrer"},O=e("td",{style:{"text-align":"center"}},"All distributions",-1),G=e("p",null,[e("strong",null,"ARM_64"),t(" - For ARM based devices - Raspberry Pi, Pinebook etc.")],-1),E=e("thead",null,[e("tr",null,[e("th",{style:{"text-align":"center"}},"Package"),e("th",{style:{"text-align":"center"}},"Distribution")])],-1),U={style:{"text-align":"center"}},N={href:"https://download.pulsar-edit.dev/?os=arm_linux&type=linux_deb",target:"_blank",rel:"noopener noreferrer"},V=e("td",{style:{"text-align":"center"}},"Debian/Ubuntu etc.",-1),Y={style:{"text-align":"center"}},B={href:"https://download.pulsar-edit.dev/?os=arm_linux&type=linux_rpm",target:"_blank",rel:"noopener noreferrer"},Z=e("td",{style:{"text-align":"center"}},"Fedora/RHEL etc.",-1),j={style:{"text-align":"center"}},J={href:"https://download.pulsar-edit.dev/?os=arm_linux&type=linux_appimage",target:"_blank",rel:"noopener noreferrer"},K=e("sup",null,"[1][2]",-1),Q=e("td",{style:{"text-align":"center"}},"All distributions",-1),X={style:{"text-align":"center"}},$={href:"https://download.pulsar-edit.dev/?os=arm_linux&type=linux_tar",target:"_blank",rel:"noopener noreferrer"},ee=e("td",{style:{"text-align":"center"}},"All distributions",-1),te=e("p",null,[t("[1] Appimage may require "),e("code",null,"--no-sandbox"),t(" as an argument to run correctly on some systems."),e("br"),t(" [2] Some distributions no longer ship with "),e("code",null,"libfuse2"),t(" which Appimage requires to run. You may need to install this manually, e.g on Ubuntu >=22.04 "),e("code",null,"apt install libfuse2"),t(".")],-1),le={class:"custom-container details"},ne=e("summary",null,"macOS",-1),se=e("p",null,[e("strong",null,"Silicon"),t(" - For Apple Silicon (M1/M2) macs")],-1),oe=e("thead",null,[e("tr",null,[e("th",{style:{"text-align":"center"}},"Package"),e("th",{style:{"text-align":"center"}},"Type")])],-1),re={style:{"text-align":"center"}},ae={href:"https://download.pulsar-edit.dev/?os=silicon_mac&type=mac_dmg",target:"_blank",rel:"noopener noreferrer"},ie=e("td",{style:{"text-align":"center"}},"DMG installer",-1),de={style:{"text-align":"center"}},ce={href:"https://download.pulsar-edit.dev/?os=silicon_mac&type=mac_zip",target:"_blank",rel:"noopener noreferrer"},ue=e("td",{style:{"text-align":"center"}},"Zip archive",-1),he=e("p",null,[e("strong",null,"Intel"),t(" - For Intel macs")],-1),_e=e("thead",null,[e("tr",null,[e("th",{style:{"text-align":"center"}},"Package"),e("th",{style:{"text-align":"center"}},"Type")])],-1),pe={style:{"text-align":"center"}},ge={href:"https://download.pulsar-edit.dev/?os=intel_mac&type=mac_dmg",target:"_blank",rel:"noopener noreferrer"},be=e("td",{style:{"text-align":"center"}},"DMG installer",-1),ye={style:{"text-align":"center"}},me={href:"https://download.pulsar-edit.dev/?os=intel_mac&type=mac_zip",target:"_blank",rel:"noopener noreferrer"},fe=e("td",{style:{"text-align":"center"}},"Zip archive",-1),xe={class:"custom-container details"},we=e("summary",null,"Windows",-1),ke=e("div",{class:"custom-container info"},[e("p",{class:"custom-container-title"},"Info"),e("p",null,[t("We currently have no Rolling Releases for Windows package managers - see "),e("a",{href:"#regular-releases"},"Regular Releases"),t(" for these instead.")]),e("p",null,'Current binaries are not signed so will produce an error with Windows Smartscreen "Windows protected your PC"... You can bypass this by clicking "More info" then "Run anyway".')],-1),ve=e("thead",null,[e("tr",null,[e("th",{style:{"text-align":"center"}},"Package"),e("th",{style:{"text-align":"center"}},"Type")])],-1),Re={style:{"text-align":"center"}},Ae={href:"https://download.pulsar-edit.dev/?os=windows&type=windows_setup",target:"_blank",rel:"noopener noreferrer"},Me=e("td",{style:{"text-align":"center"}},"Installer",-1),Pe={style:{"text-align":"center"}},Se={href:"https://download.pulsar-edit.dev/?os=windows&type=windows_portable",target:"_blank",rel:"noopener noreferrer"},Ie=e("td",{style:{"text-align":"center"}},"Portable (no install)",-1),Le=e("h2",{id:"regular-releases",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#regular-releases","aria-hidden":"true"},"#"),t(" Regular Releases")],-1),Ce=e("p",null,"These releases are put out regularly according to milestones defined by the Pulsar team (currently roughly monthly).",-1),De=e("p",null,[t("Compared to our "),e("a",{href:"#rolling-release"},"Rolling Release"),t(" these offer a more understood state by the team in terms of support. However these will sometimes feature issues that have already been resolved in our Rolling Release so if a particular fix or feature is important to you it may be worth swapping to one of those instead.")],-1),Te={href:"https://github.com/pulsar-edit/pulsar/releases/tag/v1.119.0",target:"_blank",rel:"noopener noreferrer"},ze={class:"custom-container details"},We=e("summary",null,"Linux",-1),qe=e("p",null,[e("strong",null,"x86_64"),t(" - For most desktops and laptops with Intel or AMD processors")],-1),Fe=e("thead",null,[e("tr",null,[e("th",{style:{"text-align":"center"}},"Package"),e("th",{style:{"text-align":"center"}},"Distribution")])],-1),He={style:{"text-align":"center"}},Oe={href:"https://github.com/pulsar-edit/pulsar/releases/download/v1.119.0/Linux.pulsar_1.119.0_amd64.deb",target:"_blank",rel:"noopener noreferrer"},Ge=e("td",{style:{"text-align":"center"}},"Debian/Ubuntu etc.",-1),Ee={style:{"text-align":"center"}},Ue={href:"https://github.com/pulsar-edit/pulsar/releases/download/v1.119.0/Linux.pulsar-1.119.0.x86_64.rpm",target:"_blank",rel:"noopener noreferrer"},Ne=e("td",{style:{"text-align":"center"}},"Fedora/RHEL etc.",-1),Ve={style:{"text-align":"center"}},Ye={href:"https://github.com/pulsar-edit/pulsar/releases/download/v1.119.0/Linux.Pulsar-1.119.0.AppImage",target:"_blank",rel:"noopener noreferrer"},Be=e("sup",null,"[1][2]",-1),Ze=e("td",{style:{"text-align":"center"}},"All distributions",-1),je={style:{"text-align":"center"}},Je={href:"https://github.com/pulsar-edit/pulsar/releases/download/v1.119.0/Linux.pulsar-1.119.0.tar.gz",target:"_blank",rel:"noopener noreferrer"},Ke=e("td",{style:{"text-align":"center"}},"All distributions",-1),Qe=e("p",null,[e("strong",null,"ARM_64"),t(" - For ARM based devices - Raspberry Pi, Pinebook etc.")],-1),Xe=e("thead",null,[e("tr",null,[e("th",{style:{"text-align":"center"}},"Package"),e("th",{style:{"text-align":"center"}},"Distribution")])],-1),$e={style:{"text-align":"center"}},et={href:"https://github.com/pulsar-edit/pulsar/releases/download/v1.119.0/ARM.Linux.pulsar_1.119.0_arm64.deb",target:"_blank",rel:"noopener noreferrer"},tt=e("td",{style:{"text-align":"center"}},"Debian/Ubuntu etc.",-1),lt={style:{"text-align":"center"}},nt={href:"https://github.com/pulsar-edit/pulsar/releases/download/v1.119.0/ARM.Linux.pulsar-1.119.0.aarch64.rpm",target:"_blank",rel:"noopener noreferrer"},st=e("td",{style:{"text-align":"center"}},"Fedora/RHEL etc.",-1),ot={style:{"text-align":"center"}},rt={href:"https://github.com/pulsar-edit/pulsar/releases/download/v1.119.0/ARM.Linux.Pulsar-1.119.0-arm64.AppImage",target:"_blank",rel:"noopener noreferrer"},at=e("sup",null,"[1][2]",-1),it=e("td",{style:{"text-align":"center"}},"All distributions",-1),dt={style:{"text-align":"center"}},ct={href:"https://github.com/pulsar-edit/pulsar/releases/download/v1.119.0/ARM.Linux.pulsar-1.119.0-arm64.tar.gz",target:"_blank",rel:"noopener noreferrer"},ut=e("td",{style:{"text-align":"center"}},"All distributions",-1),ht=e("p",null,[t("[1] Appimage may require "),e("code",null,"--no-sandbox"),t(" as an argument to run correctly on some systems."),e("br"),t(" [2] Some distributions no longer ship with "),e("code",null,"libfuse2"),t(" which Appimage requires to run. You may need to install this manually, e.g on Ubuntu >=22.04 "),e("code",null,"apt install libfuse2"),t(".")],-1),_t=e("p",null,[e("strong",null,"Package Managers")],-1),pt=e("thead",null,[e("tr",null,[e("th",{style:{"text-align":"center"}},"Package Manager"),e("th",{style:{"text-align":"center"}},"Distribution"),e("th",{style:{"text-align":"center"}},"Command")])],-1),gt={style:{"text-align":"center"}},bt={href:"https://github.com/wimpysworld/deb-get/",target:"_blank",rel:"noopener noreferrer"},yt=e("td",{style:{"text-align":"center"}},"Debian/Ubuntu etc.",-1),mt=e("td",{style:{"text-align":"center"}},[e("code",null,"deb-get install pulsar")],-1),ft={class:"custom-container details"},xt=e("summary",null,"macOS",-1),wt=e("p",null,[e("strong",null,"Silicon"),t(" - For Apple Silicon (M1/M2) macs")],-1),kt=e("thead",null,[e("tr",null,[e("th",{style:{"text-align":"center"}},"Package"),e("th",{style:{"text-align":"center"}},"Type")])],-1),vt={style:{"text-align":"center"}},Rt={href:"https://github.com/pulsar-edit/pulsar/releases/download/v1.119.0/Silicon.Mac.Pulsar-1.119.0-arm64.dmg",target:"_blank",rel:"noopener noreferrer"},At=e("td",{style:{"text-align":"center"}},"DMG installer",-1),Mt={style:{"text-align":"center"}},Pt={href:"https://github.com/pulsar-edit/pulsar/releases/download/v1.119.0/Intel.Mac.Pulsar-1.119.0-mac.zip",target:"_blank",rel:"noopener noreferrer"},St=e("td",{style:{"text-align":"center"}},"Zip archive",-1),It=e("p",null,[e("strong",null,"Intel"),t(" - For Intel macs")],-1),Lt=e("thead",null,[e("tr",null,[e("th",{style:{"text-align":"center"}},"Package"),e("th",{style:{"text-align":"center"}},"Type")])],-1),Ct={style:{"text-align":"center"}},Dt={href:"https://github.com/pulsar-edit/pulsar/releases/download/v1.119.0/Intel.Mac.Pulsar-1.119.0.dmg",target:"_blank",rel:"noopener noreferrer"},Tt=e("td",{style:{"text-align":"center"}},"DMG installer",-1),zt={style:{"text-align":"center"}},Wt={href:"https://github.com/pulsar-edit/pulsar/releases/download/v1.119.0/Intel.Mac.Pulsar-1.119.0-mac.zip",target:"_blank",rel:"noopener noreferrer"},qt=e("td",{style:{"text-align":"center"}},"Zip archive",-1),Ft={class:"custom-container details"},Ht=e("summary",null,"Windows",-1),Ot=e("div",{class:"custom-container info"},[e("p",{class:"custom-container-title"},"Info"),e("p",null,'Current binaries are not signed so will produce an error with Windows Smartscreen "Windows protected your PC"... You can bypass this by clicking "More info" then "Run anyway".')],-1),Gt=e("thead",null,[e("tr",null,[e("th",{style:{"text-align":"center"}},"Package"),e("th",{style:{"text-align":"center"}},"Type")])],-1),Et={style:{"text-align":"center"}},Ut={href:"https://github.com/pulsar-edit/pulsar/releases/download/v1.119.0/Windows.Pulsar.Setup.1.119.0.exe",target:"_blank",rel:"noopener noreferrer"},Nt=e("td",{style:{"text-align":"center"}},"Installer",-1),Vt={style:{"text-align":"center"}},Yt={href:"https://github.com/pulsar-edit/pulsar/releases/download/v1.119.0/Windows.Pulsar-1.119.0-win.zip",target:"_blank",rel:"noopener noreferrer"},Bt=e("td",{style:{"text-align":"center"}},"Portable (no install)",-1),Zt=e("thead",null,[e("tr",null,[e("th",{style:{"text-align":"center"}},"Package Manager"),e("th",{style:{"text-align":"center"}},"Command")])],-1),jt={style:{"text-align":"center"}},Jt={href:"https://community.chocolatey.org/packages/pulsar",target:"_blank",rel:"noopener noreferrer"},Kt=e("td",{style:{"text-align":"center"}},[e("code",null,"choco install pulsar")],-1),Qt=e("h2",{id:"manual-download",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#manual-download","aria-hidden":"true"},"#"),t(" Manual download")],-1),Xt=e("p",null,[t("Binaries are built from a number of different branches and PRs but you should stick to the "),e("strong",null,"master"),t(" branch releases for the most stable ones unless you know exactly what you are looking for.")],-1),$t=e("p",null,"We currently build on both GitHub Actions and Cirrus CI:",-1),el={href:"https://github.com/pulsar-edit/pulsar-rolling-releases/releases",target:"_blank",rel:"noopener noreferrer"},tl=e("li",null,[t("Cirrus CI is "),e("em",null,"only"),t(" used for building Apple silicon (macOS M1/M2) and ARM Linux binaries. These are run less frequently (every Monday, Wednesday, and Friday).")],-1),ll=e("p",null,"To download a binary produced by GitHub Actions then follow the below steps:",-1),nl={href:"https://github.com/pulsar-edit/pulsar-rolling-releases/releases",target:"_blank",rel:"noopener noreferrer"},sl=e("li",null,[t("Select the most recent release (or for a specific release reference the timestamp part of the version e.g. 1.109."),e("strong",null,"2023091606"),t("). "),e("ul",null,[e("li",null,"Most binaries will be available with every release but Apple silicon and ARM Linux binaries will not. You may need to look for earlier releases.")])],-1),ol=e("li",null,[t("Choose the binary you wish to download from the "),e("code",null,"Assets"),t(" section (you may need click the "),e("code",null,"Show all x assets"),t(" link to display them all). See below for descriptions of all available binaries.")],-1),rl=e("p",null,"To download a binary from Cirrus CI (Apple silicon and ARM Linux only) please follow the below steps:",-1),al={href:"https://cirrus-ci.com/github/pulsar-edit/pulsar/master",target:"_blank",rel:"noopener noreferrer"},il=c("
  • Select the latest successful build (check for a finished green check mark \u2705 on the right side of the page - make sure you do not select a failed one).
  • Select your system from the list of options:
    • arm-linux - ARM based Linux systems (Raspberry Pi etc.)
    • silicon_mac - M1 and M2 chip Apple Macs
  • Select binary > binaries from the Artifacts pane
  • Download the binary you require for your system:
  • ",4),dl=e("p",null,"Four binaries are currently available for each architecture (amd64 & arm64):",-1),cl=e("ul",null,[e("li",null,[e("code",null,"Appimage"),t(" for a universal Linux binary (should work on most systems)")]),e("li",null,[e("code",null,"rpm"),t(" for Red Hat based OSs (Fedora, RHEL etc.)")]),e("li",null,[e("code",null,"deb"),t(" for Debian or Ubuntu based OSs")]),e("li",null,[e("code",null,"tar.gz"),t(" generic binary for most Linux systems")])],-1),ul=e("p",null,"Two binaries are provided for each architecture (silicon_mac & intel_mac):",-1),hl=e("ul",null,[e("li",null,[e("code",null,"dmg"),t(" standard installer image")]),e("li",null,[e("code",null,"zip"),t(" containing the application binary for manual installation.")])],-1),_l=e("p",null,"Two binaries are currently available:",-1),pl=e("ul",null,[e("li",null,[e("code",null,"exe"),t(" is the installer based executable that will install Pulsar on your system")]),e("li",null,[e("code",null,"zip"),t(' is the "portable" version which can run without needing to be installed on the system (for example from a flash drive).')])],-1);function gl(bl,yl){const n=d("ExternalLinkIcon"),u=d("Tabs");return _(),p("div",null,[b,e("p",null,[t("These releases are built on each push to our "),y,t(" branch and automatically built by "),e("a",m,[t("GitHub Actions"),l(n)]),t(" and "),e("a",f,[t("Cirrus CI"),l(n)]),t(". These releases come with the latest fixes, updates and improvements so this is a great choice if you want to get the latest features as soon as possible.")]),x,w,e("details",k,[v,R,A,e("table",null,[M,e("tbody",null,[e("tr",null,[e("td",P,[e("a",S,[t("deb"),l(n)])]),I]),e("tr",null,[e("td",L,[e("a",C,[t("rpm"),l(n)])]),D]),e("tr",null,[e("td",T,[e("a",z,[t("Appimage"),l(n)]),W]),q]),e("tr",null,[e("td",F,[e("a",H,[t("tar.gz"),l(n)])]),O])])]),G,e("table",null,[E,e("tbody",null,[e("tr",null,[e("td",U,[e("a",N,[t("deb"),l(n)])]),V]),e("tr",null,[e("td",Y,[e("a",B,[t("rpm"),l(n)])]),Z]),e("tr",null,[e("td",j,[e("a",J,[t("Appimage"),l(n)]),K]),Q]),e("tr",null,[e("td",X,[e("a",$,[t("tar.gz"),l(n)])]),ee])])]),te]),e("details",le,[ne,se,e("table",null,[oe,e("tbody",null,[e("tr",null,[e("td",re,[e("a",ae,[t("dmg"),l(n)])]),ie]),e("tr",null,[e("td",de,[e("a",ce,[t("zip"),l(n)])]),ue])])]),he,e("table",null,[_e,e("tbody",null,[e("tr",null,[e("td",pe,[e("a",ge,[t("dmg"),l(n)])]),be]),e("tr",null,[e("td",ye,[e("a",me,[t("zip"),l(n)])]),fe])])])]),e("details",xe,[we,i("TODO: Remove once app is signed and error no longer shows"),ke,e("table",null,[ve,e("tbody",null,[e("tr",null,[e("td",Re,[e("a",Ae,[t("Setup"),l(n)])]),Me]),e("tr",null,[e("td",Pe,[e("a",Se,[t("Portable"),l(n)])]),Ie])])])]),Le,Ce,De,e("p",null,[t("Current version is "),e("a",Te,[t("v1.119.0"),l(n)]),t(".")]),e("details",ze,[We,qe,e("table",null,[Fe,e("tbody",null,[e("tr",null,[e("td",He,[e("a",Oe,[t("deb"),l(n)])]),Ge]),e("tr",null,[e("td",Ee,[e("a",Ue,[t("rpm"),l(n)])]),Ne]),e("tr",null,[e("td",Ve,[e("a",Ye,[t("AppImage"),l(n)]),Be]),Ze]),e("tr",null,[e("td",je,[e("a",Je,[t("tar.gz"),l(n)])]),Ke])])]),Qe,e("table",null,[Xe,e("tbody",null,[e("tr",null,[e("td",$e,[e("a",et,[t("deb"),l(n)])]),tt]),e("tr",null,[e("td",lt,[e("a",nt,[t("rpm"),l(n)])]),st]),e("tr",null,[e("td",ot,[e("a",rt,[t("AppImage"),l(n)]),at]),it]),e("tr",null,[e("td",dt,[e("a",ct,[t("tar.gz"),l(n)])]),ut])])]),ht,_t,e("table",null,[pt,e("tbody",null,[e("tr",null,[e("td",gt,[e("a",bt,[t("deb-get"),l(n)])]),yt,mt])])])]),e("details",ft,[xt,wt,e("table",null,[kt,e("tbody",null,[e("tr",null,[e("td",vt,[e("a",Rt,[t("dmg"),l(n)])]),At]),e("tr",null,[e("td",Mt,[e("a",Pt,[t("zip"),l(n)])]),St])])]),It,e("table",null,[Lt,e("tbody",null,[e("tr",null,[e("td",Ct,[e("a",Dt,[t("dmg"),l(n)])]),Tt]),e("tr",null,[e("td",zt,[e("a",Wt,[t("zip"),l(n)])]),qt])])])]),e("details",Ft,[Ht,i("TODO: Remove once app is signed and error no longer shows"),Ot,e("table",null,[Gt,e("tbody",null,[e("tr",null,[e("td",Et,[e("a",Ut,[t("Setup"),l(n)])]),Nt]),e("tr",null,[e("td",Vt,[e("a",Yt,[t("Portable"),l(n)])]),Bt])])]),e("table",null,[Zt,e("tbody",null,[e("tr",null,[e("td",jt,[e("a",Jt,[t("Chocolatey"),l(n)])]),Kt])])])]),Qt,Xt,$t,e("ul",null,[e("li",null,[t("GitHub Actions is used for building the majority of our binaries and builds are run on every commit or PR. "),e("ul",null,[e("li",null,[t("Direct artifact download is available only for those with at least read permission on the repository. All artifacts are therefore automatically uploaded to the "),e("a",el,[t("pulsar-edit/pulsar-rolling-releases"),l(n)]),t(" repository for access by anyone.")])])]),tl]),ll,e("ul",null,[e("li",null,[t("Navigate to the "),e("a",nl,[t("pulsar-edit/pulsar-rolling-releases"),l(n)]),t(" releases.")]),sl,ol]),rl,e("ul",null,[e("li",null,[t("Go to the master branch "),e("a",al,[t("Pulsar Cirrus CI page"),l(n)]),t(".")]),il]),l(u,{id:"608",data:[{title:"Linux"},{title:"macOS"},{title:"Windows"}],"tab-id":"downloads"},{tab0:s(({title:o,value:r,isActive:a})=>[dl,cl]),tab1:s(({title:o,value:r,isActive:a})=>[ul,hl]),tab2:s(({title:o,value:r,isActive:a})=>[_l,pl]),_:1})])}const fl=h(g,[["render",gl],["__file","download.html.vue"]]);export{fl as default}; diff --git a/assets/download.html.7c98c4b3.js b/assets/download.html.7c98c4b3.js new file mode 100644 index 0000000000..3f456c100e --- /dev/null +++ b/assets/download.html.7c98c4b3.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-5395f6f8","path":"/download.html","title":"Pulsar Downloads","lang":"en-US","frontmatter":{"title":"Pulsar Downloads","path":"/download/","sitemap":{"priority":0.9,"changefreq":"daily"}},"excerpt":"","headers":[{"level":2,"title":"Rolling Release","slug":"rolling-release","link":"#rolling-release","children":[]},{"level":2,"title":"Regular Releases","slug":"regular-releases","link":"#regular-releases","children":[]},{"level":2,"title":"Manual download","slug":"manual-download","link":"#manual-download","children":[]}],"git":{"updatedTime":1721246422000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":32},{"name":"DeeDeeG","email":"DeeDeeG@users.noreply.github.com","commits":13},{"name":"Autumn Meadow","email":"blazeykirin@gmail.com","commits":3},{"name":"confused-Techie","email":"dev@lhbasics.com","commits":3},{"name":"confused_techie","email":"dev@lhbasics.com","commits":2},{"name":"Kat","email":"61893437+TransKat@users.noreply.github.com","commits":1},{"name":"Meadowsys","email":"blazeykirin@gmail.com","commits":1}]},"readingTime":{"minutes":4.78,"words":1434},"filePathRelative":"download.md"}');export{e as data}; diff --git a/assets/editing-and-deleting-text.html.1d230db3.js b/assets/editing-and-deleting-text.html.1d230db3.js new file mode 100644 index 0000000000..4abf029415 --- /dev/null +++ b/assets/editing-and-deleting-text.html.1d230db3.js @@ -0,0 +1 @@ +import{_ as h,a as m,b as p}from"./encodings.f93acf64.js";import{_ as u,o as f,c as b,d as a,w as i,a as e,b as t,f as o,r as d}from"./app.0e1565ce.js";const g={},k=o('

    Editing and Deleting Text

    So far we've looked at a number of ways to move around and select regions of a file, so now let's actually change some of that text. Obviously you can type in order to insert characters, but there are also a number of ways to delete and manipulate text that could come in handy.

    Basic Manipulation

    There are a handful of cool keybindings for basic text manipulation that might come in handy. These range from moving around lines of text and duplicating lines to changing the case.

    • Cmd+JCtrl+J - Join the next line to the end of the current line
    • Cmd+Ctrl+Up/DownCtrl+Up/Down - Move the current line up or down
    • Cmd+Shift+DCtrl+Shift+D - Duplicate the current line
    • Cmd+K Cmd+UCtrl+K Ctrl+U - Upper case the current word
    • Cmd+K Cmd+LCtrl+K Ctrl+L - Lower case the current word
    ',5),w=e("ul",null,[e("li",null,[e("kbd",{class:"platform-mac"},"Ctrl+T"),t(" - Transpose characters. This swaps the two characters on either side of the cursor.")])],-1),y=o('

    Atom also has built in functionality to re-flow a paragraph to hard-wrap at a given maximum line length. You can format the current selection to have lines no longer than 80 (or whatever number editor.preferredLineLength is set to) characters using Alt+Cmd+QAlt+Ctrl+Q. If nothing is selected, the current paragraph will be reflowed.

    Deleting and Cutting

    You can also delete or cut text out of your buffer with some shortcuts. Be ruthless.

    • Ctrl+Shift+K - Delete current line
    • Alt+Backspace or Alt+HCtrl+Backspace - Delete to beginning of word
    • Alt+Delete or Alt+DCtrl+Delete - Delete to end of word
    ',4),x=e("ul",null,[e("li",null,[e("kbd",{class:"platform-mac"},"Cmd+Delete"),t(" - Delete to end of line")]),e("li",null,[e("kbd",{class:"platform-mac"},"Ctrl+K"),t(" - Cut to end of line")]),e("li",null,[e("kbd",{class:"platform-mac"},"Cmd+Backspace"),t(" - Delete to beginning of line")])],-1),_=o('

    Multiple Cursors and Selections

    One of the cool things that Atom can do out of the box is support multiple cursors. This can be incredibly helpful in manipulating long lists of text.

    • Cmd+ClickCtrl+Click - Add a new cursor at the clicked location
    • Ctrl+Shift+Up/DownAlt+Ctrl+Up/DownAlt+Shift+Up/Down - Add another cursor above/below the current cursor
    • Cmd+DCtrl+D - Select the next word in the document that is the same as the currently selected word
    • Cmd+Ctrl+GAlt+F3 - Select all words in the document that are the same as the currently selected word
    ',3),C=e("ul",null,[e("li",null,[e("kbd",{class:"platform-mac"},"Cmd+Shift+L"),t(" - Convert a multi-line selection into multiple cursors")])],-1),v=o('

    Using these commands you can place cursors in multiple places in your document and effectively execute the same commands in multiple places at once.

    Using multiple cursors

    This can be incredibly helpful in doing many type of repetitive tasks such as renaming variables or changing the format of some text. You can use this with almost any plugin or command - for example, changing case and moving or duplicating lines.

    You can also use the mouse to select text with the CmdCtrl key pressed down to select multiple regions of your text simultaneously.

    Whitespace

    Atom comes with several commands to help you manage the whitespace in your document. One very useful pair of commands converts leading spaces into tabs and converts leading tabs into spaces. If you're working with a document that has mixed whitespace, these commands are great for helping to normalize the file. There are no keybindings for the whitespace commands, so you will have to search your command palette for "Convert Spaces to Tabs" (or vice versa) to run one of these commands.

    ',6),A={href:"https://github.com/atom/whitespace",target:"_blank",rel:"noopener noreferrer"},T=e("code",null,"whitespace",-1),D=o('

    Managing your whitespace settings

    Note

    The "Remove Trailing Whitespace" option is on by default. This means that every time you save any file opened in Atom, it will strip all trailing whitespace from the file. If you want to disable this, go to the whitespace package in your settings panel and uncheck that option.

    Atom will also by default ensure that your file has a trailing newline. You can also disable this option on that screen.

    Brackets

    Atom ships with intelligent and easy to use bracket handling.

    It will by default highlight [], (), and {} style brackets when your cursor is over them. It will also highlight matching XML and HTML tags.

    Atom will also automatically autocomplete [], (), and {}, "", '', \u201C\u201D, \u2018\u2019, \xAB\xBB, \u2039\u203A, and backticks when you type the leading one. If you have a selection and you type any of these opening brackets or quotes, Atom will enclose the selection with the opening and closing brackets or quotes.

    There are a few other interesting bracket related commands that you can use.

    • Ctrl+M - Jump to the bracket matching the one adjacent to the cursor. It jumps to the nearest enclosing bracket when there's no adjacent bracket.
    • Cmd+Ctrl+MAlt+Ctrl+, - Select all the text inside the current brackets
    • Alt+Cmd+.Alt+Ctrl+. - Close the current XML/HTML tag
    ',9),S={href:"https://github.com/atom/bracket-matcher",target:"_blank",rel:"noopener noreferrer"},U=o('

    Encoding

    Atom also ships with some basic file encoding support should you find yourself working with non-UTF-8 encoded files, or should you wish to create one.

    • Ctrl+Shift+UAlt+U - Toggle menu to change file encoding

    If you pull up the file encoding dialog, you can choose an alternate file encoding to save your file in.

    When you open a file, Atom will try to auto-detect the encoding. If Atom can't identify the encoding, the encoding will default to UTF-8, which is also the default encoding for new files.

    Changing your file encoding

    If you pull up the encoding menu and change the active encoding to something else, the file will be written out in that encoding the next time you save the file.

    ',7),L={href:"https://github.com/atom/encoding-selector",target:"_blank",rel:"noopener noreferrer"};function M(I,B){const l=d("Tabs"),n=d("ExternalLinkIcon");return f(),b("div",null,[k,a(l,{id:"39",data:[{title:"Mac"}],"tab-id":"editing-and-deleting"},{tab0:i(({title:s,value:r,isActive:c})=>[w]),_:1}),y,a(l,{id:"76",data:[{title:"Mac"}],"tab-id":"editing-and-deleting"},{tab0:i(({title:s,value:r,isActive:c})=>[x]),_:1}),_,a(l,{id:"125",data:[{title:"Mac"}],"tab-id":"editing-and-deleting"},{tab0:i(({title:s,value:r,isActive:c})=>[C]),_:1}),v,e("p",null,[t("The whitespace commands are implemented in the "),e("a",A,[t("atom/whitespace"),a(n)]),t(" package. The settings for the whitespace commands are managed on the page for the "),T,t(" package.")]),D,e("p",null,[t("The brackets functionality is implemented in the "),e("a",S,[t("bracket-matcher"),a(n)]),t(" package. Like all of these packages, to change defaults related to bracket handling, or to disable it entirely, you can navigate to this package in the Settings view.")]),U,e("p",null,[t("The encoding selector is implemented in the "),e("a",L,[t("encoding-selector"),a(n)]),t(" package.")])])}const E=u(g,[["render",M],["__file","editing-and-deleting-text.html.vue"]]);export{E as default}; diff --git a/assets/editing-and-deleting-text.html.31c0ca86.js b/assets/editing-and-deleting-text.html.31c0ca86.js new file mode 100644 index 0000000000..faa7f5da29 --- /dev/null +++ b/assets/editing-and-deleting-text.html.31c0ca86.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-73d97d01","path":"/docs/launch-manual/sections/using-pulsar/sections/editing-and-deleting-text.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Editing and Deleting Text","slug":"editing-and-deleting-text","link":"#editing-and-deleting-text","children":[{"level":3,"title":"Basic Manipulation","slug":"basic-manipulation","link":"#basic-manipulation","children":[]},{"level":3,"title":"Deleting and Cutting","slug":"deleting-and-cutting","link":"#deleting-and-cutting","children":[]},{"level":3,"title":"Multiple Cursors and Selections","slug":"multiple-cursors-and-selections","link":"#multiple-cursors-and-selections","children":[]},{"level":3,"title":"Whitespace","slug":"whitespace","link":"#whitespace","children":[]},{"level":3,"title":"Brackets","slug":"brackets","link":"#brackets","children":[]},{"level":3,"title":"Encoding","slug":"encoding","link":"#encoding","children":[]}]}],"git":{"updatedTime":1669229414000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":5}]},"readingTime":{"minutes":5.24,"words":1571},"filePathRelative":"docs/launch-manual/sections/using-pulsar/sections/editing-and-deleting-text.md"}');export{e as data}; diff --git a/assets/editing-and-deleting-text.html.77f55668.js b/assets/editing-and-deleting-text.html.77f55668.js new file mode 100644 index 0000000000..e7f4c01b00 --- /dev/null +++ b/assets/editing-and-deleting-text.html.77f55668.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-34299b28","path":"/docs/atom-archive/using-atom/sections/editing-and-deleting-text.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"Editing and Deleting Text","slug":"editing-and-deleting-text","link":"#editing-and-deleting-text","children":[]}],"git":{"updatedTime":1664634443000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1},{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":4.5,"words":1349},"filePathRelative":"docs/atom-archive/using-atom/sections/editing-and-deleting-text.md"}');export{e as data}; diff --git a/assets/editing-and-deleting-text.html.cf992d5a.js b/assets/editing-and-deleting-text.html.cf992d5a.js new file mode 100644 index 0000000000..f7d6520938 --- /dev/null +++ b/assets/editing-and-deleting-text.html.cf992d5a.js @@ -0,0 +1 @@ +import{b as u,_ as h,a as p}from"./encodings.f93acf64.js";import{_ as b,o as m,c as g,d as a,w as l,a as e,b as t,f as c,r as d}from"./app.0e1565ce.js";const k={},f=e("h2",{id:"editing-and-deleting-text",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#editing-and-deleting-text","aria-hidden":"true"},"#"),t(" Editing and Deleting Text")],-1),w=e("p",null,"So far we've looked at a number of ways to move around and select regions of a file, so now let's actually change some of that text. Obviously you can type in order to insert characters, but there are also a number of ways to delete and manipulate text that could come in handy.",-1),_=e("h3",{id:"basic-manipulation",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#basic-manipulation","aria-hidden":"true"},"#"),t(" Basic Manipulation")],-1),C=e("p",null,"There are a handful of cool keybindings for basic text manipulation that might come in handy. These range from moving around lines of text and duplicating lines to changing the case.",-1),y=e("ul",null,[e("li",null,[e("kbd",null,"Ctrl+J"),t(" - Join the next line to the end of the current line")]),e("li",null,[e("kbd",null,"Ctrl+Up/Down"),t(" - Move the current line up or down")]),e("li",null,[e("kbd",null,"Ctrl+Shift+D"),t(" - Duplicate the current line")]),e("li",null,[e("kbd",null,"Ctrl+K"),t(),e("kbd",null,"Ctrl+U"),t(" - Upper case the current word")]),e("li",null,[e("kbd",null,"Ctrl+K"),t(),e("kbd",null,"Ctrl+L"),t(" - Lower case the current word")])],-1),v=e("ul",null,[e("li",null,[e("kbd",null,"Cmd+J"),t(" - Join the next line to the end of the current line")]),e("li",null,[e("kbd",null,"Cmd+Ctrl+Up/Down"),t(" - Move the current line up or down")]),e("li",null,[e("kbd",null,"Cmd+Shift+D"),t(" - Duplicate the current line")]),e("li",null,[e("kbd",null,"Cmd+K"),t(),e("kbd",null,"Cmd+U"),t(" - Upper case the current word")]),e("li",null,[e("kbd",null,"Cmd+K"),t(),e("kbd",null,"Cmd+L"),t(" - Lower case the current word")]),e("li",null,[e("kbd",null,"Ctrl+T"),t(" - Transpose characters. This swaps the two characters on either side of the cursor.")])],-1),x=e("ul",null,[e("li",null,[e("kbd",null,"Ctrl+J"),t(" - Join the next line to the end of the current line")]),e("li",null,[e("kbd",null,"Ctrl+Up/Down"),t(" - Move the current line up or down")]),e("li",null,[e("kbd",null,"Ctrl+Shift+D"),t(" - Duplicate the current line")]),e("li",null,[e("kbd",null,"Ctrl+K"),t(),e("kbd",null,"Ctrl+U"),t(" - Upper case the current word")]),e("li",null,[e("kbd",null,"Ctrl+K"),t(),e("kbd",null,"Ctrl+L"),t(" - Lower case the current word")])],-1),A=c('

    Pulsar also has built in functionality to re-flow a paragraph to hard-wrap at a given maximum line length. You can format the current selection to have lines no longer than 80 (or whatever number editor.preferredLineLength is set to) characters using LNX/WIN: Ctrl+Shift+Q - MAC: Alt+Cmd+Q. If nothing is selected, the current paragraph will be reflowed.

    Deleting and Cutting

    You can also delete or cut text out of your buffer with some shortcuts. Be ruthless.

    ',3),D=e("ul",null,[e("li",null,[e("kbd",null,"Ctrl+Shift+K"),t(" - Delete current line")]),e("li",null,[e("kbd",null,"Ctrl+Backspace"),t(" - Delete to beginning of word")]),e("li",null,[e("kbd",null,"Ctrl+Delete"),t(" - Delete to end of word")])],-1),S=e("ul",null,[e("li",null,[e("kbd",null,"Ctrl+Shift+K"),t(" - Delete current line")]),e("li",null,[e("kbd",null,"Alt+Backspace"),t(" or "),e("kbd",null,"Alt+H"),t(" - Delete to beginning of word")]),e("li",null,[e("kbd",null,"Alt+Delete"),t(" or "),e("kbd",null,"Alt+D"),t(" - Delete to end of word")]),e("li",null,[e("kbd",null,"Cmd+Delete"),t(" - Delete to end of line")]),e("li",null,[e("kbd",null,"Ctrl+K"),t(" - Cut to end of line")]),e("li",null,[e("kbd",null,"Cmd+Backspace"),t(" - Delete to beginning of line")])],-1),T=e("ul",null,[e("li",null,[e("kbd",null,"Ctrl+Shift+K"),t(" - Delete current line")]),e("li",null,[e("kbd",null,"Ctrl+Backspace"),t(" - Delete to beginning of word")]),e("li",null,[e("kbd",null,"Ctrl+Delete"),t(" - Delete to end of word")])],-1),L=e("h3",{id:"multiple-cursors-and-selections",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#multiple-cursors-and-selections","aria-hidden":"true"},"#"),t(" Multiple Cursors and Selections")],-1),M=e("p",null,"One of the cool things that Pulsar can do out of the box is support multiple cursors. This can be incredibly helpful in manipulating long lists of text.",-1),U=e("ul",null,[e("li",null,[e("kbd",null,"Ctrl+Click"),t(" - Add a new cursor at the clicked location")]),e("li",null,[e("kbd",null,"Alt+Shift+Up/Down"),t(" - Add another cursor above/below the current cursor")]),e("li",null,[e("kbd",null,"Ctrl+D"),t(" - Select the next word in the document that is the same as the currently selected word")]),e("li",null,[e("kbd",null,"Alt+F3"),t(" - Select all words in the document that are the same as the currently selected word")])],-1),I=e("ul",null,[e("li",null,[e("kbd",null,"Cmd+Click"),t(" - Add a new cursor at the clicked location")]),e("li",null,[e("kbd",null,"Ctrl+Shift+Up/Down"),t(" - Add another cursor above/below the current cursor")]),e("li",null,[e("kbd",null,"Cmd+D"),t(" - Select the next word in the document that is the same as the currently selected word")]),e("li",null,[e("kbd",null,"Cmd+Ctrl+G"),t(" - Select all words in the document that are the same as the currently selected word")]),e("li",null,[e("kbd",null,"Cmd+Shift+L"),t(" - Convert a multi-line selection into multiple cursors")])],-1),P=e("ul",null,[e("li",null,[e("kbd",null,"Ctrl+Click"),t(" - Add a new cursor at the clicked location")]),e("li",null,[e("kbd",null,"Alt+Ctrl+Up/Down"),t(" - Add another cursor above/below the current cursor")]),e("li",null,[e("kbd",null,"Ctrl+D"),t(" - Select the next word in the document that is the same as the currently selected word")]),e("li",null,[e("kbd",null,"Alt+F3"),t(" - Select all words in the document that are the same as the currently selected word")])],-1),B=c('

    Using these commands you can place cursors in multiple places in your document and effectively execute the same commands in multiple places at once.

    Using multiple cursors

    This can be incredibly helpful in doing many type of repetitive tasks such as renaming variables or changing the format of some text. You can use this with almost any package or command - for example, changing case and moving or duplicating lines.

    You can also use the mouse to select text with the LNX/WIN: Ctrl - MAC: Cmd key pressed down to select multiple regions of your text simultaneously.

    Return to a single cursor with Esc or by clicking anywhere in the file to position a cursor there as normal.

    Whitespace

    Pulsar comes with several commands to help you manage the whitespace in your document. One very useful pair of commands converts leading spaces into tabs and converts leading tabs into spaces. If you're working with a document that has mixed whitespace, these commands are great for helping to normalize the file. There are no keybindings for the whitespace commands, so you will have to search your command palette for "Convert Spaces to Tabs" (or vice versa) to run one of these commands.

    ',7),K={href:"https://github.com/pulsar-edit/whitespace",target:"_blank",rel:"noopener noreferrer"},W=e("code",null,"whitespace",-1),j=c('

    Managing your whitespace settings

    Note

    The "Remove Trailing Whitespace" option is on by default. This means that every time you save any file opened in Pulsar, it will strip all trailing whitespace from the file. If you want to disable this, go to the whitespace package in your settings panel and uncheck that option.

    Pulsar will also by default ensure that your file has a trailing newline. You can also disable this option on that screen.

    Brackets

    Pulsar ships with intelligent and easy to use bracket handling.

    It will by default highlight [], (), and {} style brackets when your cursor is over them. It will also highlight matching XML and HTML tags.

    Pulsar will also automatically autocomplete [], (), and {}, "", '', \u201C\u201D, \u2018\u2019, \xAB\xBB, \u2039\u203A, and `` when you type the leading one. If you have a selection and you type any of these opening brackets or quotes, Pulsar will enclose the selection with the opening and closing brackets or quotes.

    There are a few other interesting bracket related commands that you can use.

    ',8),J=e("ul",null,[e("li",null,[e("kbd",null,"Ctrl+M"),t(" - Jump to the bracket matching the one adjacent to the cursor. It jumps to the nearest enclosing bracket when there's no adjacent bracket.")]),e("li",null,[e("kbd",null,"Alt+Ctrl+,"),t(" - Select all the text inside the current brackets")]),e("li",null,[e("kbd",null,"Alt+Ctrl+."),t(" - Close the current XML/HTML tag")])],-1),N=e("ul",null,[e("li",null,[e("kbd",null,"Ctrl+M"),t(" - Jump to the bracket matching the one adjacent to the cursor. It jumps to the nearest enclosing bracket when there's no adjacent bracket.")]),e("li",null,[e("kbd",null,"Cmd+Ctrl+M"),t(" - Select all the text inside the current brackets")]),e("li",null,[e("kbd",null,"Alt+Cmd+."),t(" - Close the current XML/HTML tag")])],-1),q=e("ul",null,[e("li",null,[e("kbd",null,"Ctrl+M"),t(" - Jump to the bracket matching the one adjacent to the cursor. It jumps to the nearest enclosing bracket when there's no adjacent bracket.")]),e("li",null,[e("kbd",null,"Alt+Ctrl+,"),t(" - Select all the text inside the current brackets")]),e("li",null,[e("kbd",null,"Alt+Ctrl+."),t(" - Close the current XML/HTML tag")])],-1),O={href:"https://github.com/pulsar-edit/bracket-matcher",target:"_blank",rel:"noopener noreferrer"},E=e("h3",{id:"encoding",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#encoding","aria-hidden":"true"},"#"),t(" Encoding")],-1),X=e("p",null,"Pulsar also ships with some basic file encoding support should you find yourself working with non-UTF-8 encoded files, or should you wish to create one.",-1),H=e("p",null,[e("kbd",null,"Alt+U"),t(" - Toggle menu to change file encoding")],-1),Y=e("p",null,[e("kbd",null,"Ctrl+Shift+U"),t(" - Toggle menu to change file encoding")],-1),F=e("p",null,[e("kbd",null,"Ctrl+Shift+U"),t(" - Toggle menu to change file encoding")],-1),V=e("p",null,"If you pull up the file encoding dialog, you can choose an alternate file encoding to save your file in.",-1),Q=e("p",null,"When you open a file, Pulsar will try to auto-detect the encoding. If Pulsar can't identify the encoding, the encoding will default to UTF-8, which is also the default encoding for new files.",-1),R=e("p",null,[e("img",{src:u,alt:"Changing your file encoding"})],-1),z=e("p",null,"If you pull up the encoding menu and change the active encoding to something else, the file will be written out in that encoding the next time you save the file.",-1),G={href:"https://github.com/pulsar-edit/encoding-selector",target:"_blank",rel:"noopener noreferrer"};function Z($,ee){const r=d("Tabs"),s=d("ExternalLinkIcon");return m(),g("div",null,[f,w,_,C,a(r,{id:"12",data:[{title:"Linux"},{title:"macOS"},{title:"Windows"}],"tab-id":"using-pulsar"},{tab0:l(({title:n,value:i,isActive:o})=>[y]),tab1:l(({title:n,value:i,isActive:o})=>[v]),tab2:l(({title:n,value:i,isActive:o})=>[x]),_:1}),A,a(r,{id:"115",data:[{title:"Linux"},{title:"macOS"},{title:"Windows"}],"tab-id":"using-pulsar"},{tab0:l(({title:n,value:i,isActive:o})=>[D]),tab1:l(({title:n,value:i,isActive:o})=>[S]),tab2:l(({title:n,value:i,isActive:o})=>[T]),_:1}),L,M,a(r,{id:"195",data:[{title:"Linux"},{title:"macOS"},{title:"Windows"}],"tab-id":"using-pulsar"},{tab0:l(({title:n,value:i,isActive:o})=>[U]),tab1:l(({title:n,value:i,isActive:o})=>[I]),tab2:l(({title:n,value:i,isActive:o})=>[P]),_:1}),B,e("p",null,[t("The whitespace commands are implemented in the "),e("a",K,[t("whitespace"),a(s)]),t(" package. The settings for the whitespace commands are managed on the page for the "),W,t(" package.")]),j,a(r,{id:"324",data:[{title:"Linux"},{title:"macOS"},{title:"Windows"}],"tab-id":"using-pulsar"},{tab0:l(({title:n,value:i,isActive:o})=>[J]),tab1:l(({title:n,value:i,isActive:o})=>[N]),tab2:l(({title:n,value:i,isActive:o})=>[q]),_:1}),e("p",null,[t("The brackets functionality is implemented in the "),e("a",O,[t("bracket-matcher"),a(s)]),t(" package. Like all of these packages, to change defaults related to bracket handling, or to disable it entirely, you can navigate to this package in the Settings view.")]),E,X,a(r,{id:"392",data:[{title:"Linux"},{title:"macOS"},{title:"Windows"}],"tab-id":"using-pulsar"},{tab0:l(({title:n,value:i,isActive:o})=>[H]),tab1:l(({title:n,value:i,isActive:o})=>[Y]),tab2:l(({title:n,value:i,isActive:o})=>[F]),_:1}),V,Q,R,z,e("p",null,[t("The encoding selector is implemented in the "),e("a",G,[t("encoding-selector"),a(s)]),t(" package.")])])}const ne=b(k,[["render",Z],["__file","editing-and-deleting-text.html.vue"]]);export{ne as default}; diff --git a/assets/electron.0f83d84c.png b/assets/electron.0f83d84c.png new file mode 100644 index 0000000000..7c4e2574b1 Binary files /dev/null and b/assets/electron.0f83d84c.png differ diff --git a/assets/electron.b5867f08.js b/assets/electron.b5867f08.js new file mode 100644 index 0000000000..46698a1fab --- /dev/null +++ b/assets/electron.b5867f08.js @@ -0,0 +1 @@ +const s="/assets/electron.0f83d84c.png";export{s as _}; diff --git a/assets/encodings.782197c2.png b/assets/encodings.782197c2.png new file mode 100644 index 0000000000..40e5bb510a Binary files /dev/null and b/assets/encodings.782197c2.png differ diff --git a/assets/encodings.f93acf64.js b/assets/encodings.f93acf64.js new file mode 100644 index 0000000000..97e74a7af6 --- /dev/null +++ b/assets/encodings.f93acf64.js @@ -0,0 +1 @@ +const s="/assets/multiple-cursors.4466b041.gif",t="/assets/whitespace.59413c09.png",o="/assets/encodings.782197c2.png";export{s as _,t as a,o as b}; diff --git a/assets/exception-notification.f8f467e7.png b/assets/exception-notification.f8f467e7.png new file mode 100644 index 0000000000..5d3b45b847 Binary files /dev/null and b/assets/exception-notification.f8f467e7.png differ diff --git a/assets/file-icons.45aa1c32.png b/assets/file-icons.45aa1c32.png new file mode 100644 index 0000000000..2acf4947c8 Binary files /dev/null and b/assets/file-icons.45aa1c32.png differ diff --git a/assets/file-organization.html.750afb1d.js b/assets/file-organization.html.750afb1d.js new file mode 100644 index 0000000000..80cc72243d --- /dev/null +++ b/assets/file-organization.html.750afb1d.js @@ -0,0 +1,72 @@ +import{_ as t,o,c as d,a as n,b as e,d as a,f as s,r as l}from"./app.0e1565ce.js";const c={},r=s(`

    File Organization

    One of the most important things to take note of when adding new documentation is where it should go within the website layout.

    The generalized overall layout of the website looks like this:

    docs
    +\u251C\u2500\u2500 root level .md files
    +\u2514\u2500\u2500 section folder
    +    \u251C\u2500\u2500 sections
    +    \u2514\u2500\u2500 index.md
    +

    The general idea is that for files that can stand by themselves (for example the About Us page, Repositories etc.) they exist at the docs/ "root" level.

    For anything that is more complex it needs to have a section directory named appropriately, an index.md file within it and a sections directory.

    index.md

    This index file needs to have a YAML frontmatter to define, at a minimum, the title of the document. This is displayed as an H1 header for the page (note: subsequent H1 headers will be ignored so always start at H2).

    The rest of this index file will be used to display the actual content you want to show. This is done in a number of ways.

    First of all you can just include standard markdown. This is often used for introducing the section or adding one of our reusable components (e.g. a danger container).

    The rest of the file should consist of @includes which take data from other folders on the website and integrates it automatically. Usually this will be the sections files which will be covered next.

    e.g.

    
    +File not found
    +
    +
    `,13),u=n("code",null,"@include",-1),p={href:"https://github.com/pulsar-edit/.github",target:"_blank",rel:"noopener noreferrer"},h=s(`

    This is done by having a value defined on the config.js file which will provide an alias for us to use:

    if (file.startsWith("@orgdocs")) {
    +	return file.replace(
    +		"@orgdocs",
    +		path.resolve(__dirname, "../../node_modules/.github/")
    +	);
    +}
    +

    This allows us to include org-level docs by using this special alias.

    e.g.

    @include-push(/home/runner/work/pulsar-edit.github.io/pulsar-edit.github.io/node_modules/.github)
    +# Pulsar tooling
    +
    +Here you will find a list of tools used by the Pulsar team and information about them.
    +
    +## Continuous Integration
    +
    +### [Cirrus CI](https://cirrus-ci.com/github/pulsar-edit/pulsar)
    +
    +Cirrus CI is used for Pulsar's continuous integration as well as for building application binaries.
    +
    +### [Codacy](https://app.codacy.com/gh/pulsar-edit/repositories)
    +
    +Codacy is used to scan committed code for any issues that may have been missed.
    +
    +Currently though Codacy is only used on the following repositories:
    +
    +* [ppm](https://app.codacy.com/gh/pulsar-edit/ppm/dashboard)
    +* [pulsar](https://app.codacy.com/gh/pulsar-edit/pulsar/dashboard)
    +* [background-tips](https://app.codacy.com/gh/pulsar-edit/background-tips/dashboard)
    +* [autocomplete-plus](https://app.codacy.com/gh/pulsar-edit/autocomplete-plus/dashboard)
    +
    +## i18n (Internationalization)
    +
    +### [Crowdin](https://crowdin.pulsar-edit.dev/)
    +
    +Crowdin will be used for Pulsar's internationalization efforts but exact details on this are still pending.
    +
    +## Package Managers
    +
    +While most repositories you can easily tell what Package Manager is being used by checking for a specific lock file, there are some execptions to this that should be noted.
    +
    +* [pulsar](https://github.com/pulsar-edit/pulsar) uses \`yarn\` as its Package Manager.
    +* [pulsar-edit.github.io](https://github.com/pulsar-edit/pulsar-edit.github.io) uses \`pnpm\` as its Package Manager.
    +
    +## Cloud Database
    +
    +The [package-backend](https://github.com/pulsar-edit/package-backend) currently uses DigitalOcean to host the PostgreSQL Pulsar Package Repositories data in the cloud.
    +
    +## Cloud Compute
    +
    +Both the [package-backend](https://github.com/pulsar-edit/package-backend) and [package-frontend](https://github.com/pulsar-edit/package-frontend) use Google App Engine to host the compute instance of these websites in the cloud.
    +
    +## Additional Testing tools
    +
    +### [Action Pulsar Dependency Tester](https://github.com/marketplace/actions/action-pulsar-dependency-tester)
    +
    +Pulsar Dependency Tester is a GitHub action used to test changes of a core Pulsar Dependency if you need to determine how your core package dependency will run on the current version of the Pulsar Editor.
    +
    +This GitHub Action is a direct Pulsar replacement of the previous Action to test Atom Dependencies [Setup Atom](https://github.com/marketplace/actions/setup-atom).
    +
    +@include-pop()
    +
    +

    Sections

    The sections directory is where we include the rest of the documents broken down by section. These should be self contained files which can be used alone but are designed to be included on the section page. This approach allows us flexibility with ordering as well as including these files in other places without needing to duplicate the material.

    Files here can be navigated to directly on the website but should not be linked to directly.

    These files shoud not have any YAML frontmatter as they will be included and shown as text.

    Assets

    `,10),v={href:"https://github.com/pulsar-edit/.github/tree/main/images/",target:"_blank",rel:"noopener noreferrer"},m=s(`

    An alias for this exists in config.js to access files from this repository.

    alias: {
    +  '@images': path.resolve(__dirname, '../../node_modules/.github/images')
    +},
    +

    So to include an image you simply need to use the standard markdown image link along with the alias:

    e.g.

    ![MyImage](@images/path/to/image.png)
    +
    `,5);function b(g,f){const i=l("ExternalLinkIcon");return o(),d("div",null,[r,n("p",null,[e("However you can also use "),u,e(" to feature files from a different section of the website or even files from outside the main site. We use this to include files which are maintained on the organization "),n("a",p,[e(".github repo"),a(i)]),e(" for org-level documents.")]),h,n("p",null,[e("Assets should be uploaded to the "),n("a",v,[e(".github repo"),a(i)]),e(" repository so they can be used org-wide.")]),m])}const y=t(c,[["render",b],["__file","file-organization.html.vue"]]);export{y as default}; diff --git a/assets/file-organization.html.96259859.js b/assets/file-organization.html.96259859.js new file mode 100644 index 0000000000..901d0e2c74 --- /dev/null +++ b/assets/file-organization.html.96259859.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-f38bdb06","path":"/docs/resources/website/sections/file-organization.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"File Organization","slug":"file-organization","link":"#file-organization","children":[{"level":3,"title":"index.md","slug":"index-md","link":"#index-md","children":[]},{"level":3,"title":"Sections","slug":"sections","link":"#sections","children":[]},{"level":3,"title":"Assets","slug":"assets","link":"#assets","children":[]}]}],"git":{"updatedTime":1671044195000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":2}]},"readingTime":{"minutes":1.67,"words":500},"filePathRelative":"docs/resources/website/sections/file-organization.md"}');export{e as data}; diff --git a/assets/find-and-replace-newline.08e81a06.js b/assets/find-and-replace-newline.08e81a06.js new file mode 100644 index 0000000000..18e9465f76 --- /dev/null +++ b/assets/find-and-replace-newline.08e81a06.js @@ -0,0 +1 @@ +const e="/assets/find-and-replace-newline.91adc4f3.png";export{e as _}; diff --git a/assets/find-and-replace-newline.91adc4f3.png b/assets/find-and-replace-newline.91adc4f3.png new file mode 100644 index 0000000000..fa6f059719 Binary files /dev/null and b/assets/find-and-replace-newline.91adc4f3.png differ diff --git a/assets/find-and-replace.html.4a488eeb.js b/assets/find-and-replace.html.4a488eeb.js new file mode 100644 index 0000000000..3cf6e40a79 --- /dev/null +++ b/assets/find-and-replace.html.4a488eeb.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-0311d835","path":"/docs/atom-archive/using-atom/sections/find-and-replace.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"Find and Replace","slug":"find-and-replace","link":"#find-and-replace","children":[]}],"git":{"updatedTime":1664634443000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1},{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":2.09,"words":628},"filePathRelative":"docs/atom-archive/using-atom/sections/find-and-replace.md"}');export{e as data}; diff --git a/assets/find-and-replace.html.82039939.js b/assets/find-and-replace.html.82039939.js new file mode 100644 index 0000000000..aa2a7ecc61 --- /dev/null +++ b/assets/find-and-replace.html.82039939.js @@ -0,0 +1 @@ +import{a as h,_ as u}from"./find-replace-project.ada882a7.js";import{_ as p,o as f,c as m,d as o,w as r,a as e,b as t,f as c,r as s}from"./app.0e1565ce.js";const _={},g=e("h2",{id:"find-and-replace",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#find-and-replace","aria-hidden":"true"},"#"),t(" Find and Replace")],-1),b=e("p",null,"Finding and replacing text in your file or project is quick and easy in Pulsar.",-1),k=e("ul",null,[e("li",null,[e("kbd",null,"Ctrl+F"),t(" - Search within a buffer")]),e("li",null,[e("kbd",null,"Ctrl+Shift+F"),t(" - Search the entire project")])],-1),y=e("ul",null,[e("li",null,[e("kbd",null,"Cmd+F"),t(" - Search within a buffer")]),e("li",null,[e("kbd",null,"Cmd+Shift+F"),t(" - Search the entire project")])],-1),x=e("ul",null,[e("li",null,[e("kbd",null,"Ctrl+F"),t(" - Search within a buffer")]),e("li",null,[e("kbd",null,"Ctrl+Shift+F"),t(" - Search the entire project")])],-1),w=c('

    If you launch either of those commands, you'll be greeted with the Find and Replace panel at the bottom of your screen.

    Find and replace text in the current file

    To search within your current file you can press LNX/WIN: Cmd+F - MAC: Ctrl+F, type in a search string and press LNX/WIN/MAC: Enter or LNX/WINF3 - MAC: Cmd+G or the "Find Next" button) multiple times to cycle through all the matches in that file. Alt+Enter will find all occurrences of the search string. The Find and Replace panel also contains buttons for toggling case sensitivity, performing regular expression matching, scoping the search to selections, and performing whole word search.

    If you type a string in the replacement text box, you can replace matches with a different string. For example, if you wanted to replace every instance of the string "Atom" with the string "Pulsar", you would enter those values in the two text boxes and press the "Replace All" button to perform the replacements.

    ',4),F={class:"custom-container note"},v=e("p",{class:"custom-container-title"},"Note",-1),C=e("p",null,[e("strong",null,"Note:"),t(" Pulsar uses JavaScript regular expressions to perform regular expression searches.")],-1),S={href:"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions",target:"_blank",rel:"noopener noreferrer"},N=c('

    You can also find and replace throughout your entire project if you invoke the panel with LNX/WIN: Ctrl+Shift+F - MAC: Cmd+Shift+F.

    Find and replace text in your project

    This is a great way to find out where in your project a function is called, an anchor is linked to or a specific misspelling is located. Click on the matching line to jump to that location in that file.

    ',3),j={href:"https://en.wikipedia.org/wiki/Glob_%28programming%29",target:"_blank",rel:"noopener noreferrer"},A=e("code",null,"src/*.js",-1),q=e("code",null,"src",-1),R=e("code",null,"**",-1),I=e("code",null,"docs/**/*.md",-1),T=e("code",null,"docs/a/foo.md",-1),W=e("code",null,"docs/a/b/foo.md",-1),E=e("p",null,[t("When you have multiple project folders open, this feature can also be used to search in only one of those folders. For example, if you had the folders "),e("code",null,"/path1/folder1"),t(" and "),e("code",null,"/path2/folder2"),t(" open, you could enter a pattern starting with "),e("code",null,"folder1"),t(" to search only in the first folder.")],-1),L=e("p",null,[t("Press "),e("kbd",null,"Esc"),t(" while focused on the Find and Replace panel to clear the pane from your workspace.")],-1),P={href:"https://github.com/pulsar-edit/find-and-replace",target:"_blank",rel:"noopener noreferrer"},J={href:"https://github.com/pulsar-edit/scandal",target:"_blank",rel:"noopener noreferrer"};function M(V,X){const d=s("Tabs"),n=s("ExternalLinkIcon");return f(),m("div",null,[g,b,o(d,{id:"6",data:[{title:"Linux"},{title:"macOS"},{title:"Windows"}],"tab-id":"using-pulsar"},{tab0:r(({title:a,value:l,isActive:i})=>[k]),tab1:r(({title:a,value:l,isActive:i})=>[y]),tab2:r(({title:a,value:l,isActive:i})=>[x]),_:1}),w,e("div",F,[v,C,e("p",null,[t("When doing a regular expression search, the replacement syntax to refer back to search groups is $1, $2, \u2026 $&. Refer to JavaScript's "),e("a",S,[t("guide to regular expressions"),o(n)]),t(" to learn more about regular expression syntax you can use in Pulsar.")])]),N,e("p",null,[t("You can limit a search to a subset of the files in your project by entering a "),e("a",j,[t("glob pattern"),o(n)]),t(' into the "File/Directory pattern" text box. For example, the pattern '),A,t(" would restrict the search to JavaScript files in the "),q,t(' directory. The "globstar" pattern ('),R,t(") can be used to match arbitrarily many subdirectories. For example, "),I,t(" will match "),T,t(", "),W,t(", etc. You can enter multiple glob patterns separated by commas, which is useful for searching in multiple file types or subdirectories.")]),E,L,e("p",null,[t("The Find and Replace functionality is implemented in the "),e("a",P,[t("find-and-replace"),o(n)]),t(" package and uses the "),e("a",J,[t("scandal"),o(n)]),t(" Node module to do the actual searching.")])])}const Y=p(_,[["render",M],["__file","find-and-replace.html.vue"]]);export{Y as default}; diff --git a/assets/find-and-replace.html.a6a6d2f5.js b/assets/find-and-replace.html.a6a6d2f5.js new file mode 100644 index 0000000000..7391c9d112 --- /dev/null +++ b/assets/find-and-replace.html.a6a6d2f5.js @@ -0,0 +1 @@ +import{_ as n,a as r}from"./find-replace-project.ada882a7.js";import{_ as l,o as s,c as i,a as e,b as t,d as a,f as c,r as d}from"./app.0e1565ce.js";const p={},h=c('

    Find and Replace

    Finding and replacing text in your file or project is quick and easy in Atom.

    • Cmd+FCtrl+F - Search within a buffer
    • Cmd+Shift+FCtrl+Shift+F - Search the entire project

    If you launch either of those commands, you'll be greeted with the Find and Replace panel at the bottom of your screen.

    Find and replace text in the current file

    To search within your current file you can press Cmd+FCtrl+F, type in a search string and press Enter (or Cmd+GF3 or the "Find Next" button) multiple times to cycle through all the matches in that file. Alt+Enter will find all occurences of the search string. The Find and Replace panel also contains buttons for toggling case sensitivity, performing regular expression matching, scoping the search to selections, and performing whole word search.

    If you type a string in the replacement text box, you can replace matches with a different string. For example, if you wanted to replace every instance of the string "Scott" with the string "Dragon", you would enter those values in the two text boxes and press the "Replace All" button to perform the replacements.

    ',7),u={class:"custom-container note"},f=e("p",{class:"custom-container-title"},"Note",-1),m=e("p",null,[e("strong",null,"Note:"),t(" Atom uses JavaScript regular expressions to perform regular expression searches.")],-1),_={href:"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions",target:"_blank",rel:"noopener noreferrer"},b=e("p",null,[t("You can also find and replace throughout your entire project if you invoke the panel with "),e("kbd",{class:"platform-mac"},"Cmd+Shift+F"),e("kbd",{class:"platform-windows platform-linux"},"Ctrl+Shift+F"),t(".")],-1),g=e("p",null,[e("img",{src:n,alt:"Find and replace text in your project",title:"Find and replace text in your project"})],-1),k=e("p",null,"This is a great way to find out where in your project a function is called, an anchor is linked to or a specific misspelling is located. Click on the matching line to jump to that location in that file.",-1),y={href:"https://en.wikipedia.org/wiki/Glob_%28programming%29",target:"_blank",rel:"noopener noreferrer"},x=e("code",null,"src/*.js",-1),w=e("code",null,"src",-1),F=e("code",null,"**",-1),v=e("code",null,"docs/**/*.md",-1),S=e("code",null,"docs/a/foo.md",-1),C=e("code",null,"docs/a/b/foo.md",-1),j=e("p",null,[t("When you have multiple project folders open, this feature can also be used to search in only one of those folders. For example, if you had the folders "),e("code",null,"/path1/folder1"),t(" and "),e("code",null,"/path2/folder2"),t(" open, you could enter a pattern starting with "),e("code",null,"folder1"),t(" to search only in the first folder.")],-1),q=e("p",null,[t("Press "),e("kbd",{class:"platform-all"},"Esc"),t(" while focused on the Find and Replace panel to clear the pane from your workspace.")],-1),R={href:"https://github.com/atom/find-and-replace",target:"_blank",rel:"noopener noreferrer"},N={href:"https://github.com/atom/scandal",target:"_blank",rel:"noopener noreferrer"};function E(A,T){const o=d("ExternalLinkIcon");return s(),i("div",null,[h,e("div",u,[f,m,e("p",null,[t("When doing a regular expression search, the replacement syntax to refer back to search groups is $1, $2, \u2026 $&. Refer to JavaScript's "),e("a",_,[t("guide to regular expressions"),a(o)]),t(" to learn more about regular expression syntax you can use in Atom.")])]),b,g,k,e("p",null,[t("You can limit a search to a subset of the files in your project by entering a "),e("a",y,[t("glob pattern"),a(o)]),t(' into the "File/Directory pattern" text box. For example, the pattern '),x,t(" would restrict the search to JavaScript files in the "),w,t(' directory. The "globstar" pattern ('),F,t(") can be used to match arbitrarily many subdirectories. For example, "),v,t(" will match "),S,t(", "),C,t(", etc. You can enter multiple glob patterns separated by commas, which is useful for searching in multiple file types or subdirectories.")]),j,q,e("p",null,[t("The Find and Replace functionality is implemented in the "),e("a",R,[t("find-and-replace"),a(o)]),t(" package and uses the "),e("a",N,[t("scandal"),a(o)]),t(" Node module to do the actual searching.")])])}const V=l(p,[["render",E],["__file","find-and-replace.html.vue"]]);export{V as default}; diff --git a/assets/find-and-replace.html.f30e892a.js b/assets/find-and-replace.html.f30e892a.js new file mode 100644 index 0000000000..07c918b4db --- /dev/null +++ b/assets/find-and-replace.html.f30e892a.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-282b3c00","path":"/docs/launch-manual/sections/using-pulsar/sections/find-and-replace.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Find and Replace","slug":"find-and-replace","link":"#find-and-replace","children":[]}],"git":{"updatedTime":1678542051000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":4}]},"readingTime":{"minutes":2.09,"words":627},"filePathRelative":"docs/launch-manual/sections/using-pulsar/sections/find-and-replace.md"}');export{e as data}; diff --git a/assets/find-replace-file.824478bb.png b/assets/find-replace-file.824478bb.png new file mode 100644 index 0000000000..15db0ae171 Binary files /dev/null and b/assets/find-replace-file.824478bb.png differ diff --git a/assets/find-replace-project.5a3600b1.png b/assets/find-replace-project.5a3600b1.png new file mode 100644 index 0000000000..a8131f497d Binary files /dev/null and b/assets/find-replace-project.5a3600b1.png differ diff --git a/assets/find-replace-project.ada882a7.js b/assets/find-replace-project.ada882a7.js new file mode 100644 index 0000000000..94843e6f94 --- /dev/null +++ b/assets/find-replace-project.ada882a7.js @@ -0,0 +1 @@ +const s="/assets/find-replace-file.824478bb.png",e="/assets/find-replace-project.5a3600b1.png";export{e as _,s as a}; diff --git a/assets/finder.04f876a5.js b/assets/finder.04f876a5.js new file mode 100644 index 0000000000..ac4ab3ce84 --- /dev/null +++ b/assets/finder.04f876a5.js @@ -0,0 +1 @@ +const s="/assets/first-launch.ac3dbc06.png",t="/assets/command-palette.6ea4da90.png",e="/assets/settings.f822be64.png",a="/assets/theme.a183f0e9.png",n="/assets/settings-wrap.da47925c.png",p="/assets/open-file.c788eed5.png",o="/assets/project-view.e5e09e11.png",c="/assets/finder.c50f6161.png";export{s as _,t as a,e as b,a as c,n as d,p as e,o as f,c as g}; diff --git a/assets/finder.c50f6161.png b/assets/finder.c50f6161.png new file mode 100644 index 0000000000..32f61dd1e1 Binary files /dev/null and b/assets/finder.c50f6161.png differ diff --git a/assets/first-launch.ac3dbc06.png b/assets/first-launch.ac3dbc06.png new file mode 100644 index 0000000000..0d261f2fc1 Binary files /dev/null and b/assets/first-launch.ac3dbc06.png differ diff --git a/assets/flight-manual.36cb1750.png b/assets/flight-manual.36cb1750.png new file mode 100644 index 0000000000..5c309398ed Binary files /dev/null and b/assets/flight-manual.36cb1750.png differ diff --git a/assets/folding.6d09f8df.js b/assets/folding.6d09f8df.js new file mode 100644 index 0000000000..5e442b8a35 --- /dev/null +++ b/assets/folding.6d09f8df.js @@ -0,0 +1 @@ +const s="/assets/folding.76f8fd04.png";export{s as _}; diff --git a/assets/folding.76f8fd04.png b/assets/folding.76f8fd04.png new file mode 100644 index 0000000000..6281656b26 Binary files /dev/null and b/assets/folding.76f8fd04.png differ diff --git a/assets/folding.html.3ffe9e96.js b/assets/folding.html.3ffe9e96.js new file mode 100644 index 0000000000..6c249bf7a1 --- /dev/null +++ b/assets/folding.html.3ffe9e96.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-ea7dfef2","path":"/docs/launch-manual/sections/using-pulsar/sections/folding.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Folding","slug":"folding","link":"#folding","children":[]}],"git":{"updatedTime":1669229414000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":2}]},"readingTime":{"minutes":1.46,"words":439},"filePathRelative":"docs/launch-manual/sections/using-pulsar/sections/folding.md"}');export{e as data}; diff --git a/assets/folding.html.6d44f0d2.js b/assets/folding.html.6d44f0d2.js new file mode 100644 index 0000000000..1dba035d1f --- /dev/null +++ b/assets/folding.html.6d44f0d2.js @@ -0,0 +1 @@ +import{_ as o}from"./folding.6d09f8df.js";import{_ as t,o as l,c as a,f as e}from"./app.0e1565ce.js";const d={},n=e('

    Folding

    If you want to see an overview of the structure of the code file you're working on, folding can be a helpful tool. Folding hides blocks of code such as functions or looping blocks in order to simplify what is on your screen.

    You can fold blocks of code by clicking the arrows that appear when you hover your mouse cursor over the gutter. You can also fold and unfold from the keyboard with the Alt+Cmd+[Alt+Ctrl+[ and Alt+Cmd+]Alt+Ctrl+] keybindings.

    Code folding example

    To fold everything, use Alt+Cmd+Shift+[Alt+Ctrl+Shift+[ and to unfold everything use Alt+Cmd+Shift+]Alt+Ctrl+Shift+]. You can also fold at a specific indentation level with Cmd+KCtrl+K Cmd+0-9Ctrl+0-9 where the number is the indentation depth.

    Finally, you can fold arbitrary sections of your code or text by making a selection and then typing Alt+Cmd+Ctrl+FAlt+Ctrl+F or choosing "Fold Selection" in the Command Palette.

    ',6),r=[n];function s(i,c){return l(),a("div",null,r)}const p=t(d,[["render",s],["__file","folding.html.vue"]]);export{p as default}; diff --git a/assets/folding.html.aad93fbd.js b/assets/folding.html.aad93fbd.js new file mode 100644 index 0000000000..55d587c248 --- /dev/null +++ b/assets/folding.html.aad93fbd.js @@ -0,0 +1 @@ +import{_ as l}from"./folding.6d09f8df.js";import{_ as r,o as s,c as u,d as c,w as o,a as t,b as e,r as h}from"./app.0e1565ce.js";const f={},b=t("h2",{id:"folding",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#folding","aria-hidden":"true"},"#"),e(" Folding")],-1),m=t("p",null,"If you want to see an overview of the structure of the code file you're working on, folding can be a helpful tool. Folding hides blocks of code such as functions or looping blocks in order to simplify what is on your screen.",-1),p=t("p",null,[e("You can fold blocks of code by clicking the arrows that appear when you hover your mouse cursor over the gutter. You can also fold and unfold from the keyboard with the "),t("kbd",null,"Alt+Ctrl+["),e(" and "),t("kbd",null,"Alt+Ctrl+]"),e(" keybindings.")],-1),_=t("p",null,[t("img",{src:l,alt:"Code folding example",title:"Code folding example"})],-1),g=t("p",null,[e("To fold everything, use "),t("kbd",null,"Alt+Ctrl+Shift+["),e(" and to unfold everything use "),t("kbd",null,"Alt+Ctrl+Shift+]"),e(". You can also fold at a specific indentation level with "),t("kbd",null,"Ctrl+K"),e(),t("kbd",null,"Ctrl+0-9"),e(" where the number is the indentation depth.")],-1),y=t("p",null,[e("Finally, you can fold arbitrary sections of your code or text by making a selection and then typing "),t("kbd",null,"Alt+Ctrl+F"),e(' or choosing "Fold Selection" in the Command Palette.')],-1),k=t("p",null,[e("You can fold blocks of code by clicking the arrows that appear when you hover your mouse cursor over the gutter. You can also fold and unfold from the keyboard with the "),t("kbd",null,"Alt+Cmd+["),e(" and "),t("kbd",null,"Alt+Cmd+]"),e(" keybindings.")],-1),C=t("p",null,[t("img",{src:l,alt:"Code folding example",title:"Code folding example"})],-1),v=t("p",null,[e("To fold everything, use "),t("kbd",null,"Alt+Cmd+Shift+["),e(" and to unfold everything use "),t("kbd",null,"Alt+Cmd+Shift+]"),e(". You can also fold at a specific indentation level with "),t("kbd",null,"Cmd+K"),e(),t("kbd",null,"Cmd+0-9"),e(" where the number is the indentation depth.")],-1),w=t("p",null,[e("Finally, you can fold arbitrary sections of your code or text by making a selection and then typing "),t("kbd",null,"Alt+Cmd+Ctrl+F"),e(' or choosing "Fold Selection" in the Command Palette.')],-1),A=t("p",null,[e("You can fold blocks of code by clicking the arrows that appear when you hover your mouse cursor over the gutter. You can also fold and unfold from the keyboard with the "),t("kbd",null,"Alt+Ctrl+["),e(" and "),t("kbd",null,"Alt+Ctrl+]"),e(" keybindings.")],-1),x=t("p",null,[t("img",{src:l,alt:"Code folding example",title:"Code folding example"})],-1),F=t("p",null,[e("To fold everything, use "),t("kbd",null,"Alt+Ctrl+Shift+["),e(" and to unfold everything use "),t("kbd",null,"Alt+Ctrl+Shift+]"),e(". You can also fold at a specific indentation level with "),t("kbd",null,"Ctrl+K"),e(),t("kbd",null,"Ctrl+0-9"),e(" where the number is the indentation depth.")],-1),S=t("p",null,[e("Finally, you can fold arbitrary sections of your code or text by making a selection and then typing "),t("kbd",null,"Alt+Ctrl+F"),e(' or choosing "Fold Selection" in the Command Palette.')],-1);function Y(T,B){const a=h("Tabs");return s(),u("div",null,[b,m,c(a,{id:"6",data:[{title:"linux"},{title:"macOS"},{title:"Windows"}],"tab-id":"using-pulsar"},{tab0:o(({title:n,value:i,isActive:d})=>[p,_,g,y]),tab1:o(({title:n,value:i,isActive:d})=>[k,C,v,w]),tab2:o(({title:n,value:i,isActive:d})=>[A,x,F,S]),_:1})])}const P=r(f,[["render",Y],["__file","folding.html.vue"]]);export{P as default}; diff --git a/assets/folding.html.e4e0e277.js b/assets/folding.html.e4e0e277.js new file mode 100644 index 0000000000..4f45a95127 --- /dev/null +++ b/assets/folding.html.e4e0e277.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-6252cab2","path":"/docs/atom-archive/using-atom/sections/folding.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"Folding","slug":"folding","link":"#folding","children":[]}],"git":{"updatedTime":1664634443000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1},{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":0.87,"words":262},"filePathRelative":"docs/atom-archive/using-atom/sections/folding.md"}');export{e as data}; diff --git a/assets/fonts-in-use.d1baabe5.png b/assets/fonts-in-use.d1baabe5.png new file mode 100644 index 0000000000..7bcb33d5f5 Binary files /dev/null and b/assets/fonts-in-use.d1baabe5.png differ diff --git a/assets/fork.425ac14f.png b/assets/fork.425ac14f.png new file mode 100644 index 0000000000..54d5ce8acb Binary files /dev/null and b/assets/fork.425ac14f.png differ diff --git a/assets/get-help.html.8b9cc126.js b/assets/get-help.html.8b9cc126.js new file mode 100644 index 0000000000..3ec1bfe6f8 --- /dev/null +++ b/assets/get-help.html.8b9cc126.js @@ -0,0 +1 @@ +import{_ as r,o as n,c as s,a as t,b as e,d as o,r as l}from"./app.0e1565ce.js";const i={},u=t("h2",{id:"having-trouble",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#having-trouble","aria-hidden":"true"},"#"),e(" Having trouble?")],-1),h=t("p",null,[e("If you have any issues then please feel free to ask for help from the Pulsar Team or the wider community via any of our "),t("a",{href:"/docs/community"},"Community areas")],-1),c={href:"https://github.com/pulsar-edit/pulsar/issues",target:"_blank",rel:"noopener noreferrer"},p={href:"https://github.com/pulsar-edit/pulsar/issues/new?assignees=&labels=bug%2Ctriage&projects=&template=bug-report.yml",target:"_blank",rel:"noopener noreferrer"};function d(_,f){const a=l("ExternalLinkIcon");return n(),s("div",null,[u,h,t("p",null,[e("If you think you have found a bug then please have a look through our existing "),t("a",c,[e("issues"),o(a)]),e(" and if you can't find anything then please "),t("a",p,[e("create a new bug report"),o(a)]),e(".")])])}const g=r(i,[["render",d],["__file","get-help.html.vue"]]);export{g as default}; diff --git a/assets/get-help.html.a5704781.js b/assets/get-help.html.a5704781.js new file mode 100644 index 0000000000..35659d1606 --- /dev/null +++ b/assets/get-help.html.a5704781.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-495d25f0","path":"/docs/launch-manual/sections/faq/sections/get-help.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Having trouble?","slug":"having-trouble","link":"#having-trouble","children":[]}],"git":{"updatedTime":1694883547000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":3},{"name":"confused-Techie","email":"dev@lhbasics.com","commits":1}]},"readingTime":{"minutes":0.28,"words":84},"filePathRelative":"docs/launch-manual/sections/faq/sections/get-help.md"}');export{e as data}; diff --git a/assets/gh-discussions.62e6b3a4.png b/assets/gh-discussions.62e6b3a4.png new file mode 100644 index 0000000000..db403e5e17 Binary files /dev/null and b/assets/gh-discussions.62e6b3a4.png differ diff --git a/assets/ghtests.c6b1b477.png b/assets/ghtests.c6b1b477.png new file mode 100644 index 0000000000..b7c8e2237e Binary files /dev/null and b/assets/ghtests.c6b1b477.png differ diff --git a/assets/git-checkout-head.1f356cbf.gif b/assets/git-checkout-head.1f356cbf.gif new file mode 100644 index 0000000000..ad3016cf99 Binary files /dev/null and b/assets/git-checkout-head.1f356cbf.gif differ diff --git a/assets/git-lines.8152cd74.png b/assets/git-lines.8152cd74.png new file mode 100644 index 0000000000..22aa64392d Binary files /dev/null and b/assets/git-lines.8152cd74.png differ diff --git a/assets/git-message.9e959a5d.gif b/assets/git-message.9e959a5d.gif new file mode 100644 index 0000000000..e564366f89 Binary files /dev/null and b/assets/git-message.9e959a5d.gif differ diff --git a/assets/git-status-bar.774ed823.png b/assets/git-status-bar.774ed823.png new file mode 100644 index 0000000000..873305fd22 Binary files /dev/null and b/assets/git-status-bar.774ed823.png differ diff --git a/assets/git-status.748c4f3e.gif b/assets/git-status.748c4f3e.gif new file mode 100644 index 0000000000..1caf44b561 Binary files /dev/null and b/assets/git-status.748c4f3e.gif differ diff --git a/assets/github-amend.a10e79cb.png b/assets/github-amend.a10e79cb.png new file mode 100644 index 0000000000..6272ceae02 Binary files /dev/null and b/assets/github-amend.a10e79cb.png differ diff --git a/assets/github-branch.a90f3b5c.png b/assets/github-branch.a90f3b5c.png new file mode 100644 index 0000000000..79bf7e8b4d Binary files /dev/null and b/assets/github-branch.a90f3b5c.png differ diff --git a/assets/github-checkout.80044dbf.png b/assets/github-checkout.80044dbf.png new file mode 100644 index 0000000000..8d2a7b3e21 Binary files /dev/null and b/assets/github-checkout.80044dbf.png differ diff --git a/assets/github-clone.477d3f4e.png b/assets/github-clone.477d3f4e.png new file mode 100644 index 0000000000..917668d393 Binary files /dev/null and b/assets/github-clone.477d3f4e.png differ diff --git a/assets/github-commit-detail.705f4c6d.png b/assets/github-commit-detail.705f4c6d.png new file mode 100644 index 0000000000..82d63f4d82 Binary files /dev/null and b/assets/github-commit-detail.705f4c6d.png differ diff --git a/assets/github-commit-preview.be41e452.png b/assets/github-commit-preview.be41e452.png new file mode 100644 index 0000000000..c5346b3c25 Binary files /dev/null and b/assets/github-commit-preview.be41e452.png differ diff --git a/assets/github-commit-with-co-authors.45bf6f00.png b/assets/github-commit-with-co-authors.45bf6f00.png new file mode 100644 index 0000000000..159b0181bd Binary files /dev/null and b/assets/github-commit-with-co-authors.45bf6f00.png differ diff --git a/assets/github-commit.f31064ae.png b/assets/github-commit.f31064ae.png new file mode 100644 index 0000000000..c8a2e56065 Binary files /dev/null and b/assets/github-commit.f31064ae.png differ diff --git a/assets/github-create-a-pull-request.7504093b.png b/assets/github-create-a-pull-request.7504093b.png new file mode 100644 index 0000000000..7345a2c425 Binary files /dev/null and b/assets/github-create-a-pull-request.7504093b.png differ diff --git a/assets/github-desktop.a5a19b8f.png b/assets/github-desktop.a5a19b8f.png new file mode 100644 index 0000000000..2635ca5cab Binary files /dev/null and b/assets/github-desktop.a5a19b8f.png differ diff --git a/assets/github-discard.120eb488.png b/assets/github-discard.120eb488.png new file mode 100644 index 0000000000..ca8cbdabb8 Binary files /dev/null and b/assets/github-discard.120eb488.png differ diff --git a/assets/github-fetch-pull.d98174e7.png b/assets/github-fetch-pull.d98174e7.png new file mode 100644 index 0000000000..a6e4746e57 Binary files /dev/null and b/assets/github-fetch-pull.d98174e7.png differ diff --git a/assets/github-initialize.dc7a1ee0.png b/assets/github-initialize.dc7a1ee0.png new file mode 100644 index 0000000000..d993ef590f Binary files /dev/null and b/assets/github-initialize.dc7a1ee0.png differ diff --git a/assets/github-open-git-panel.15e560ef.png b/assets/github-open-git-panel.15e560ef.png new file mode 100644 index 0000000000..d6e45415e9 Binary files /dev/null and b/assets/github-open-git-panel.15e560ef.png differ diff --git a/assets/github-open-issue-or-pull-request.7503ac93.png b/assets/github-open-issue-or-pull-request.7503ac93.png new file mode 100644 index 0000000000..a0a66deebc Binary files /dev/null and b/assets/github-open-issue-or-pull-request.7503ac93.png differ diff --git a/assets/github-open-review-from-diff.193039f2.png b/assets/github-open-review-from-diff.193039f2.png new file mode 100644 index 0000000000..8cbb783f41 Binary files /dev/null and b/assets/github-open-review-from-diff.193039f2.png differ diff --git a/assets/github-package.html.3c4fde2e.js b/assets/github-package.html.3c4fde2e.js new file mode 100644 index 0000000000..9f01889857 --- /dev/null +++ b/assets/github-package.html.3c4fde2e.js @@ -0,0 +1,2 @@ +import{_ as i,a as s,b as r,c as n,d as l,e as c,f as h,g as u,h as m,i as p,j as d,k as g,l as b,m as f,n as k,o as w,p as q,q as v,r as y,s as _,t as C,u as R,v as P,w as I,x,y as G}from"./github-review-reply.6d405e4b.js";import{_ as T,o as H,c as O,a as t,b as e,d as A,f as o,r as S}from"./app.0e1565ce.js";const N={},V=o('

    GitHub package

    The GitHub package brings Git and GitHub integration right inside Pulsar.

    Most of the functionality lives within the Git and GitHub dock items.

    The Git and GitHub panels

    There are different ways to access them, probably the most common way is through their keybindings:

    • Open the Git panel: Ctrl+9
    • Open the GitHub panel: Ctrl+8

    Another way is from the menu: Packages -> GitHub -> Toggle Git Tab and Toggle GitHub Tab

    Or you can also toggle the Git panel from the Status Bar by clicking on the changed files icon:

    Open Git panel


    Initialize repositories

    In case a project doesn't have a Git repository yet, you can create one from the Git panel.

    Initialize repositories

    Clone repositories

    To clone a repository, open the GitHub panel while you have no project folders open in Pulsar and click "Clone an existing GitHub repository". In the dialog, paste the URL of a repository and click "Clone". The new project will be added to the Tree View.

    GitHub panel

    Clone dialog

    Alternately, run the GitHub: Clone command to open the Clone dialog any time.

    Branch

    To open the branch tooltip, click the branch icon in the Status Bar. From there you can create or switch branches.

    Create or switch branches

    Stage

    After making some changes, stage anything you want to be part of the next commit. Choose between staging...

    • All changes: Click the "Stage All" button in the "Unstaged Changes" bar.
    • Files: Double-click a file or select a file and press Enter.
    • Hunk: Click on the "Stage Hunk" button or select a hunk and press Enter.
    • Lines: Click on a line (or drag on multiple lines) to select, then click on the "Stage Selection" button. Or use the LNX/WIN: Ctrl+/ - MAC: Cmd-/ key to toggle from hunk mode to line mode, then press LNX/WIN: Ctrl-Enter - MAC: Cmd-Enter to stage just a single line.

    Use the LNX/WIN: Cmd-Left - MAC: Ctrl-Left or LNX/WIN: Ctrl-Right - MAC: Cmd-Right arrow key to switch between file list and the diff view. Unstaging can be done in the same way.

    Stage changes

    Discard changes

    If you no longer want to keep some changes, you can discard them. It's similar to staging, but accessible behind a context menu.

    • All changes: Click the ... menu in the "Unstaged Changes" header and choose "Discard All Changes".
    • Files: Right-click a file (or multiple) and choose "Discard Changes".
    • Hunk: Click on the trash icon in the top bar of a hunk.
    • Lines: Right-click on a line (or multiple) and choose "Discard Selection".

    Discard changes

    Commit Preview

    To double check all changes that are going into your next commit, click the "See All Staged Changes" button above the commit message box. It lets you see all of your staged changes in a single pane. This "commit preview" can also serve as an inspiration for writing the commit message.

    Commit Preview

    Commit

    Once you've staged your changes, enter a commit message. Feel free to describe the commit in more detail after leaving an empty line. Finalize by clicking the Commit button. If you need more space, click the expand icon at the bottom right. It will open a commit editor in the center.

    Commit changes

    To add multiple co-authors to a commit, click the "\u{1F464}\u2795" icon in the bottom left corner of the commit message editor. Now you can search by name, email or GitHub username to give credit to a co-author.

    Commit with co-authors

    Amend and undo

    In case you forgot to commit a change and would like to add it to your previous commit, right-click on the last commit, then choose "Amend" from the context menu.

    Amend previous commit

    If you want to edit the commit message of your last commit, or add/remove changes, click on the "Undo" button. It will roll back to the state just before you clicked on the commit button.

    Undo previous commit

    View commits

    Once you've made some commits, click on a commit message in the recent commit list to see the full diff and commit message associated with each:

    View commit detai

    Publish and push

    When you're ready to share your changes with your team members, click the Publish button in the Status Bar. It will push your local branch to the remote repository. After making more commits, you can Push them as well from the Status Bar.

    Publish and push commits

    Fetch and pull

    From time to time it's a good idea to click on the Fetch button to see if any other team member pushed changes. If so, click on Pull to merge the changes into your local branch.

    Fetch and pull commits

    If you prefer to rebase when pulling, you can configure Git to make it the default behavior:

    git config --global --bool pull.rebase true
    +
    `,55),F={href:"https://mislav.net/2013/02/merge-vs-rebase/",target:"_blank",rel:"noopener noreferrer"},L=o('

    Resolve conflicts

    Sometimes there can be conflicts when trying to merge. Files that have merge conflicts will show up in the "Merge Conflicts" list. Click on a file to open the editor. There you can resolve the conflict by picking a version or make further edits. Once done, stage the file and commit.

    Resolve conflicts

    Create a Pull Request

    When your changes are ready to be reviewed by your team members, open the "GitHub" panel Ctrl+8 and click on Open new pull request. It will open the browser where you can continue creating a pull request. If commits haven't been pushed or the branch isn't published yet, the GitHub package will do that automatically for you.

    Create a Pull Request

    View Pull Requests

    Once the pull request is created, it will appear under Current pull request at the top of the panel. Underneath is a list of Open pull requests. It lets you quickly find a pull request by avatar, title or PR number. It also lets you keep an eye on the CI status. Clicking on a pull request in the list opens a center pane with more details, the timeline and conversations.

    View Pull Requests

    Open any Issue or Pull Request

    You can open issues or pull requests from any repo on GitHub. To do so, run the GitHub: Open Issue Or Pull Request command and paste the URL from an issue or pull request. Then press the Open Issue or Pull Request button and it will open a center pane. This lets you keep an issue or pull request as a reference, when working in another repo.

    Open Issue or Pull Request

    Checkout a Pull Request

    To test a pull request locally, open it in the workspace center by clicking on the pull request in the "open pull requests" list from the GitHub tab, then click on the Checkout button. It will automatically create a local branch and pull all the changes. If you would like to contribute to that pull request, start making changes, commit and push. Your contribution is now part of that pull request.

    Checkout a pull request

    View Pull Request review comments

    To view review comments on a Pull Request, open the Reviews Tab from the See Reviews button from the footer of a Pull Request Pane. Alternatively, if the pull request has already been checked out, Reviews Tab can also be open from the same button on GitHub Tab.

    Open review tab from footer

    You can see all the review summaries and comments of a pull request in the Reviews Tab. The comment section has a progress bar to help you keep track of how close are you to finish addressing the Pull Request comments (i.e. marking all comment threads on a Pull Request as "resolved"). Comment threads are greyed out after they have been resolved.

    Review tab

    After the pull request branch has been checked out, you can click Jump To File to open the commented on file and make changes as per the review comment right in the editor. If you would like to get the full context of the review comment, click Open Diff to open the diff view with line highlighting.

    Jump to file from review tab

    Conversely, in-editor comments are indicated by the comment icon in the gutter. Clicking the icon, either from within the editor or the diff view, will take you back to the Reviews Tab.

    Open review tab from diff

    Respond to a Pull Request review comment

    To respond to a Pull Request review comment, type your message and click Comment; a single line comment will be created in the same thread as the comment you responded to. After addressing a Pull Request review comment, click Resolve conversation to mark the whole thread as "resolved". The progress bar in the "Comments" section will update accordingly.

    Respond to a Pull Request review comment

    ',28);function U(B,D){const a=S("ExternalLinkIcon");return H(),O("div",null,[V,t("p",null,[e("Learn more about "),t("a",F,[e("merge vs. rebase"),A(a)]),e(".")]),L])}const E=T(N,[["render",U],["__file","github-package.html.vue"]]);export{E as default}; diff --git a/assets/github-package.html.4c719713.js b/assets/github-package.html.4c719713.js new file mode 100644 index 0000000000..49e68c2991 --- /dev/null +++ b/assets/github-package.html.4c719713.js @@ -0,0 +1,2 @@ +import{_ as i,a as s,b as r,c as n,d as l,e as c,f as h,g as u,h as m,i as p,j as d,k as g,l as b,m as f,n as w,o as k,p as q,q as v,r as y,s as _,t as C,u as R,v as P,w as x,x as I,y as G}from"./github-review-reply.6d405e4b.js";import{_ as T,o as O,c as H,a as t,b as e,d as A,f as a,r as S}from"./app.0e1565ce.js";const V={},F=a('

    GitHub package

    The github package brings Git and GitHub integration right inside Atom.

    Most of the functionality lives within the Git and GitHub dock items.

    The Git and GitHub panels

    There are different ways to access them, probably the most common way is through their keybindings:

    • Open the Git panel: Ctrl+9
    • Open the GitHub panel: Ctrl+8

    Another way is from the menu: Packages -> GitHub -> Toggle Git Tab and Toggle GitHub Tab

    Or you can also toggle the Git panel from the Status Bar by clicking on the changed files icon:

    Open Git panel


    Initialize repositories

    In case a project doesn't have a Git repository yet, you can create one from the Git panel.

    Initialize repositories

    Clone repositories

    To clone a repository, open the GitHub panel while you have no project folders open in Atom and click "Clone an existing GitHub repository". In the dialog, paste the URL of a repository and click "Clone". The new project will be added to the Tree View.

    GitHub panel

    Clone dialog

    Alternately, run the GitHub: Clone command to open the Clone dialog any time.

    Branch

    To open the branch tooltip, click the branch icon in the Status Bar. From there you can create or switch branches.

    Create or switch branches

    Stage

    After making some changes, stage anything you want to be part of the next commit. Choose between staging...

    • All changes: Click the "Stage All" button in the "Unstaged Changes" bar.
    • Files: Double-click a file or select a file and press Enter.
    • Hunk: Click on the "Stage Hunk" button or select a hunk and press Enter.
    • Lines: Click on a line (or drag on multiple lines) to select, then click on the "Stage Selection" button. Or use the Cmd-/Cmd-/ key to toggle from hunk mode to line mode, then press Cmd-EnterCtrl-Enter to stage just a single line.

    Use the Cmd-LeftCtrl-Left or Cmd-RightCtrl-Right arrow key to switch between file list and the diff view. Unstaging can be done in the same way.

    Stage changes

    Discard changes

    If you no longer want to keep some changes, you can discard them. It's similar to staging, but accessible behind a context menu.

    • All changes: Click the ... menu in the "Unstaged Changes" header and choose "Discard All Changes".
    • Files: Right-click a file (or multiple) and choose "Discard Changes".
    • Hunk: Click on the trash icon in the top bar of a hunk.
    • Lines: Right-click on a line (or multiple) and choose "Discard Selection".

    Discard changes

    Commit Preview

    To double check all changes that are going into your next commit, click the "See All Staged Changes" button above the commit message box. It lets you see all of your staged changes in a single pane. This "commit preview" can also serve as an inspiration for writing the commit message.

    Commit Preview

    Commit

    Once you've staged your changes, enter a commit message. Feel free to describe the commit in more detail after leaving an empty line. Finalize by clicking the Commit button. If you need more space, click the expand icon at the bottom right. It will open a commit editor in the center.

    Commit changes

    To add multiple co-authors to a commit, click the "\u{1F464}\u2795" icon in the bottom left corner of the commit message editor. Now you can search by name, email or GitHub username to give credit to a co-author.

    Commit with co-authors

    Amend and undo

    In case you forgot to commit a change and would like to add it to your previous commit, right-click on the last commit, then choose "Amend" from the context menu.

    Amend previous commit

    If you want to edit the commit message of your last commit, or add/remove changes, click on the "Undo" button. It will roll back to the state just before you clicked on the commit button.

    Undo previous commit

    View commits

    Once you've made some commits, click on a commit message in the recent commit list to see the full diff and commit message associated with each:

    View commit detai

    Publish and push

    When you're ready to share your changes with your team members, click the Publish button in the Status Bar. It will push your local branch to the remote repository. After making more commits, you can Push them as well from the Status Bar.

    Publish and push commits

    Fetch and pull

    From time to time it's a good idea to click on the Fetch button to see if any other team member pushed changes. If so, click on Pull to merge the changes into your local branch.

    Fetch and pull commits

    If you prefer to rebase when pulling, you can configure Git to make it the default behavior:

    git config --global --bool pull.rebase true
    +
    `,55),U={href:"https://mislav.net/2013/02/merge-vs-rebase/",target:"_blank",rel:"noopener noreferrer"},B=a('

    Resolve conflicts

    Sometimes there can be conflicts when trying to merge. Files that have merge conflicts will show up in the "Merge Conflicts" list. Click on a file to open the editor. There you can resolve the conflict by picking a version or make further edits. Once done, stage the file and commit.

    Resolve conflicts

    Create a Pull Request

    When your changes are ready to be reviewed by your team members, open the "GitHub" panel Ctrl+8 and click on Open new pull request. It will open the browser where you can continue creating a pull request. If commits haven't been pushed or the branch isn't published yet, the GitHub package will do that automatically for you.

    Create a Pull Request

    View Pull Requests

    Once the pull request is created, it will appear under Current pull request at the top of the panel. Underneath is a list of Open pull requests. It lets you quickly find a pull request by avatar, title or PR number. It also lets you keep an eye on the CI status. Clicking on a pull request in the list opens a center pane with more details, the timeline and conversations.

    View Pull Requests

    Open any Issue or Pull Request

    You can open issues or pull requests from any repo on GitHub. To do so, run the GitHub: Open Issue Or Pull Request command and paste the URL from an issue or pull request. Then press the Open Issue or Pull Request button and it will open a center pane. This lets you keep an issue or pull request as a reference, when working in another repo.

    Open Issue or Pull Request

    Checkout a Pull Request

    To test a pull request locally, open it in the workspace center by clicking on the pull request in the "open pull requests" list from the GitHub tab, then click on the Checkout button. It will automatically create a local branch and pull all the changes. If you would like to contribute to that pull request, start making changes, commit and push. Your contribution is now part of that pull request.

    Checkout a pull request

    View Pull Request review comments

    To view review comments on a Pull Request, open the Reviews Tab from the See Reviews button from the footer of a Pull Request Pane. Alternatively, if the pull request has already been checked out, Reviews Tab can also be open from the same button on GitHub Tab.

    Open review tab from footer

    You can see all the review summaries and comments of a pull request in the Reviews Tab. The comment section has a progress bar to help you keep track of how close are you to finish addressing the Pull Request comments (i.e. marking all comment threads on a Pull Request as "resolved"). Comment threads are greyed out after they have been resolved.

    Review tab

    After the pull request branch has been checked out, you can click Jump To File to open the commented on file and make changes as per the review comment right in the editor. If you would like to get the full context of the review comment, click Open Diff to open the diff view with line highlighting.

    Jump to file from review tab

    Conversely, in-editor comments are indicated by the comment icon in the gutter. Clicking the icon, either from within the editor or the diff view, will take you back to the Reviews Tab.

    Open review tab from diff

    Respond to a Pull Request review comment

    To respond to a Pull Request review comment, type your message and click Comment; a single line comment will be created in the same thread as the comment you responded to. After addressing a Pull Request review comment, click Resolve conversation to mark the whole thread as "resolved". The progress bar in the "Comments" section will update accordingly.

    Respond to a Pull Request review comment

    ',28);function D(L,z){const o=S("ExternalLinkIcon");return O(),H("div",null,[F,t("p",null,[e("Learn more about "),t("a",U,[e("merge vs. rebase"),A(o)]),e(".")]),B])}const N=T(V,[["render",D],["__file","github-package.html.vue"]]);export{N as default}; diff --git a/assets/github-package.html.90ba8ca2.js b/assets/github-package.html.90ba8ca2.js new file mode 100644 index 0000000000..cf5f0af197 --- /dev/null +++ b/assets/github-package.html.90ba8ca2.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-a1dd9864","path":"/docs/launch-manual/sections/using-pulsar/sections/github-package.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"GitHub package","slug":"github-package","link":"#github-package","children":[{"level":3,"title":"Initialize repositories","slug":"initialize-repositories","link":"#initialize-repositories","children":[]},{"level":3,"title":"Clone repositories","slug":"clone-repositories","link":"#clone-repositories","children":[]},{"level":3,"title":"Branch","slug":"branch","link":"#branch","children":[]},{"level":3,"title":"Stage","slug":"stage","link":"#stage","children":[]},{"level":3,"title":"Discard changes","slug":"discard-changes","link":"#discard-changes","children":[]},{"level":3,"title":"Commit Preview","slug":"commit-preview","link":"#commit-preview","children":[]},{"level":3,"title":"Commit","slug":"commit","link":"#commit","children":[]},{"level":3,"title":"Amend and undo","slug":"amend-and-undo","link":"#amend-and-undo","children":[]},{"level":3,"title":"View commits","slug":"view-commits","link":"#view-commits","children":[]},{"level":3,"title":"Publish and push","slug":"publish-and-push","link":"#publish-and-push","children":[]},{"level":3,"title":"Fetch and pull","slug":"fetch-and-pull","link":"#fetch-and-pull","children":[]},{"level":3,"title":"Resolve conflicts","slug":"resolve-conflicts","link":"#resolve-conflicts","children":[]},{"level":3,"title":"Create a Pull Request","slug":"create-a-pull-request","link":"#create-a-pull-request","children":[]},{"level":3,"title":"View Pull Requests","slug":"view-pull-requests","link":"#view-pull-requests","children":[]},{"level":3,"title":"Open any Issue or Pull Request","slug":"open-any-issue-or-pull-request","link":"#open-any-issue-or-pull-request","children":[]},{"level":3,"title":"Checkout a Pull Request","slug":"checkout-a-pull-request","link":"#checkout-a-pull-request","children":[]},{"level":3,"title":"View Pull Request review comments","slug":"view-pull-request-review-comments","link":"#view-pull-request-review-comments","children":[]},{"level":3,"title":"Navigate Pull Request review comments","slug":"navigate-pull-request-review-comments","link":"#navigate-pull-request-review-comments","children":[]},{"level":3,"title":"Respond to a Pull Request review comment","slug":"respond-to-a-pull-request-review-comment","link":"#respond-to-a-pull-request-review-comment","children":[]}]}],"git":{"updatedTime":1669229414000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":3}]},"readingTime":{"minutes":6.19,"words":1856},"filePathRelative":"docs/launch-manual/sections/using-pulsar/sections/github-package.md"}');export{e as data}; diff --git a/assets/github-package.html.9d0daecf.js b/assets/github-package.html.9d0daecf.js new file mode 100644 index 0000000000..a41c9093d3 --- /dev/null +++ b/assets/github-package.html.9d0daecf.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-faca997a","path":"/docs/atom-archive/using-atom/sections/github-package.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"GitHub package","slug":"github-package","link":"#github-package","children":[]}],"git":{"updatedTime":1664634443000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1},{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":6.29,"words":1887},"filePathRelative":"docs/atom-archive/using-atom/sections/github-package.md"}');export{e as data}; diff --git a/assets/github-panels.e631add8.png b/assets/github-panels.e631add8.png new file mode 100644 index 0000000000..9e6da861a6 Binary files /dev/null and b/assets/github-panels.e631add8.png differ diff --git a/assets/github-publish-push.15c66f27.png b/assets/github-publish-push.15c66f27.png new file mode 100644 index 0000000000..ab83b93dda Binary files /dev/null and b/assets/github-publish-push.15c66f27.png differ diff --git a/assets/github-resolve-conflicts.0affcba5.png b/assets/github-resolve-conflicts.0affcba5.png new file mode 100644 index 0000000000..5e4cddc4ea Binary files /dev/null and b/assets/github-resolve-conflicts.0affcba5.png differ diff --git a/assets/github-review-jump-to-file.cfe8297d.png b/assets/github-review-jump-to-file.cfe8297d.png new file mode 100644 index 0000000000..cbd769cd58 Binary files /dev/null and b/assets/github-review-jump-to-file.cfe8297d.png differ diff --git a/assets/github-review-reply.6d405e4b.js b/assets/github-review-reply.6d405e4b.js new file mode 100644 index 0000000000..16e097a5a6 --- /dev/null +++ b/assets/github-review-reply.6d405e4b.js @@ -0,0 +1 @@ +const s="/assets/github-panels.e631add8.png",t="/assets/github-open-git-panel.15e560ef.png",e="/assets/github-initialize.dc7a1ee0.png",i="/assets/github-without-projects.5bfb7e81.png",a="/assets/github-clone.477d3f4e.png",o="/assets/github-branch.a90f3b5c.png",p="/assets/github-stage.1eafd494.png",n="/assets/github-discard.120eb488.png",g="/assets/github-commit-preview.be41e452.png",c="/assets/github-commit.f31064ae.png",_="/assets/github-commit-with-co-authors.45bf6f00.png",r="/assets/github-amend.a10e79cb.png",u="/assets/github-undo.f452c764.png",b="/assets/github-commit-detail.705f4c6d.png",m="/assets/github-publish-push.15c66f27.png",h="/assets/github-fetch-pull.d98174e7.png",f="/assets/github-resolve-conflicts.0affcba5.png",l="/assets/github-create-a-pull-request.7504093b.png",d="/assets/github-view-pull-requests.b30c3ff9.png",w="/assets/github-open-issue-or-pull-request.7503ac93.png",v="/assets/github-checkout.80044dbf.png",q="/assets/github-see-review-footer.7732a216.png",j="/assets/github-review-tab.0e98fe9c.png",k="/assets/github-review-jump-to-file.cfe8297d.png",x="/assets/github-open-review-from-diff.193039f2.png",y="/assets/github-review-reply.737d081e.png";export{s as _,t as a,e as b,i as c,a as d,o as e,p as f,n as g,g as h,c as i,_ as j,r as k,u as l,b as m,m as n,h as o,f as p,l as q,d as r,w as s,v as t,q as u,j as v,k as w,x,y}; diff --git a/assets/github-review-reply.737d081e.png b/assets/github-review-reply.737d081e.png new file mode 100644 index 0000000000..2340bd19e5 Binary files /dev/null and b/assets/github-review-reply.737d081e.png differ diff --git a/assets/github-review-tab.0e98fe9c.png b/assets/github-review-tab.0e98fe9c.png new file mode 100644 index 0000000000..5406871185 Binary files /dev/null and b/assets/github-review-tab.0e98fe9c.png differ diff --git a/assets/github-see-review-footer.7732a216.png b/assets/github-see-review-footer.7732a216.png new file mode 100644 index 0000000000..596f05694a Binary files /dev/null and b/assets/github-see-review-footer.7732a216.png differ diff --git a/assets/github-stage.1eafd494.png b/assets/github-stage.1eafd494.png new file mode 100644 index 0000000000..07295d97ba Binary files /dev/null and b/assets/github-stage.1eafd494.png differ diff --git a/assets/github-undo.f452c764.png b/assets/github-undo.f452c764.png new file mode 100644 index 0000000000..24128955e0 Binary files /dev/null and b/assets/github-undo.f452c764.png differ diff --git a/assets/github-view-pull-requests.b30c3ff9.png b/assets/github-view-pull-requests.b30c3ff9.png new file mode 100644 index 0000000000..a15983081e Binary files /dev/null and b/assets/github-view-pull-requests.b30c3ff9.png differ diff --git a/assets/github-without-projects.5bfb7e81.png b/assets/github-without-projects.5bfb7e81.png new file mode 100644 index 0000000000..2b98ae9b39 Binary files /dev/null and b/assets/github-without-projects.5bfb7e81.png differ diff --git a/assets/glossary.html.3aca2517.js b/assets/glossary.html.3aca2517.js new file mode 100644 index 0000000000..b033674454 --- /dev/null +++ b/assets/glossary.html.3aca2517.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-53169d12","path":"/docs/atom-archive/resources/sections/glossary.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"Glossary","slug":"glossary","link":"#glossary","children":[]}],"git":{"updatedTime":1667691698000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1},{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":1.58,"words":474},"filePathRelative":"docs/atom-archive/resources/sections/glossary.md"}');export{e as data}; diff --git a/assets/glossary.html.4f5f8a73.js b/assets/glossary.html.4f5f8a73.js new file mode 100644 index 0000000000..c1b17de4cb --- /dev/null +++ b/assets/glossary.html.4f5f8a73.js @@ -0,0 +1 @@ +import{_ as r,o as l,c as h,a as e,b as a,d as t,w as c,f as i,r as n}from"./app.0e1565ce.js";const d={},p=i('

    Glossary

    Below is a list of some useful terms we use with regard to Atom.

    Buffer

    A buffer is the text content of a file in Atom. It's basically the same as a file for most descriptions, but it's the version Atom has in memory. For instance, you can change the text of a buffer and it isn't written to its associated file until you save it.

    Command

    A command is a bit of functionality in Atom that can be triggered by the user either through a keybinding or a menu item.

    Dock

    Docks are collapsible pane containers that attach to the left, right, and bottom sides of the Atom window.

    Examples:

    • Tree View
    • Git
    • GitHub

    Key Combination

    A key combination is some combination or sequence of keys that are pressed to perform a task.

    Examples:

    • A
    • Ctrl+Enter
    • Ctrl+K Right

    Key Sequence

    A key sequence is a special case of a key combination. It is a key combination that consists of keys that must be pressed and released in sequence. Ctrl+K Down is a key sequence. Alt+S is not a key sequence because it is two keys that are pressed and released together rather than in succession.

    Keybinding

    A keybinding is the mapping of a key combination, such as Ctrl+Enter to an Atom command.

    Keymap

    A keymap is a collection of keybindings. It can also refer to a file or files containing keybindings for an Atom package or Atom itself.

    Package

    ',21),m=i('

    Pane

    A pane is a visual section of the editor space. Each pane can hold multiple pane items. There is always at least one pane in each Atom window.

    Pane Container

    A section of the Atom UI that can contain multiple panes.

    Pane Item

    Some item, often an editor, that is displayed within a pane. In the default configuration of Atom, pane items are represented by tabs at the top of each pane.

    ',6),f={class:"custom-container note"},u=e("p",{class:"custom-container-title"},"Note",-1),b=e("strong",null,"Note:",-1),k={href:"https://github.com/atom/tabs",target:"_blank",rel:"noopener noreferrer"},y=e("h4",{id:"panel",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#panel","aria-hidden":"true"},"#"),a(" Panel")],-1),g=e("p",null,"A piece of the Atom UI that is outside the editor space.",-1),_=e("p",null,"Examples:",-1),A=e("ul",null,[e("li",null,"Find and Replace"),e("li",null,"Keybinding Resolver")],-1);function x(w,C){const o=n("RouterLink"),s=n("ExternalLinkIcon");return l(),h("div",null,[p,e("p",null,[a("An Atom plugin. There is a bunch more information in the section on "),t(o,{to:"/using-atom/sections/atom-packages/"},{default:c(()=>[a("Atom Packages")]),_:1}),a(".")]),m,e("div",f,[u,e("p",null,[b,a(` The reason why we don't call them "tabs" is because you can disable the `),e("a",k,[a("tabs package"),t(s)]),a(" and then there aren't any tabs. For a similar reason, we don't call them files because some things can be shown in a pane that aren't files, like the Settings View.")])]),y,g,_,A])}const I=r(d,[["render",x],["__file","glossary.html.vue"]]);export{I as default}; diff --git a/assets/go-to-declaration-example.62447add.mp4 b/assets/go-to-declaration-example.62447add.mp4 new file mode 100644 index 0000000000..89890c1b93 Binary files /dev/null and b/assets/go-to-declaration-example.62447add.mp4 differ diff --git a/assets/go-to-declaration-example.87adc60b.webm b/assets/go-to-declaration-example.87adc60b.webm new file mode 100644 index 0000000000..13f0f8f3a8 Binary files /dev/null and b/assets/go-to-declaration-example.87adc60b.webm differ diff --git a/assets/goto.8ea021d6.png b/assets/goto.8ea021d6.png new file mode 100644 index 0000000000..cbe6bc7a09 Binary files /dev/null and b/assets/goto.8ea021d6.png differ diff --git a/assets/grammar.a72cafde.png b/assets/grammar.a72cafde.png new file mode 100644 index 0000000000..cd991e676c Binary files /dev/null and b/assets/grammar.a72cafde.png differ diff --git a/assets/grammar.c16b8cd6.js b/assets/grammar.c16b8cd6.js new file mode 100644 index 0000000000..f55ff8a623 --- /dev/null +++ b/assets/grammar.c16b8cd6.js @@ -0,0 +1 @@ +const a="/assets/grammar.a72cafde.png";export{a as _}; diff --git a/assets/grammar.html.63ce206c.js b/assets/grammar.html.63ce206c.js new file mode 100644 index 0000000000..f274055158 --- /dev/null +++ b/assets/grammar.html.63ce206c.js @@ -0,0 +1 @@ +const a=JSON.parse('{"key":"v-3ae8d9e8","path":"/docs/atom-archive/using-atom/sections/grammar.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"Grammar","slug":"grammar","link":"#grammar","children":[]}],"git":{"updatedTime":1664634443000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1},{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":0.7,"words":211},"filePathRelative":"docs/atom-archive/using-atom/sections/grammar.md"}');export{a as data}; diff --git a/assets/grammar.html.b1bb0d35.js b/assets/grammar.html.b1bb0d35.js new file mode 100644 index 0000000000..3f15fc6b93 --- /dev/null +++ b/assets/grammar.html.b1bb0d35.js @@ -0,0 +1 @@ +import{_ as n}from"./grammar.c16b8cd6.js";import{_ as s,o as l,c as m,a as e,b as t,d as a,w as c,r as o}from"./app.0e1565ce.js";const h={},d=e("h3",{id:"grammar",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#grammar","aria-hidden":"true"},"#"),t(" Grammar")],-1),f=e("p",null,[t("When you load a file, Atom does a little work to try to figure out what type of file it is. Largely this is accomplished by looking at its file extension ("),e("code",null,".md"),t(" is generally a Markdown file, etc), though sometimes it has to inspect the content a bit to figure it out.")],-1),u=e("p",null,[t(`When you open a file and Atom can't determine a grammar for the file, it will default to "Plain Text", which is the simplest one. If it does default to "Plain Text", picks the wrong grammar for the file, or if for any reason you wish to change the selected grammar, you can pull up the Grammar Selector with `),e("kbd",{class:"platform-all"},"Ctrl+Shift+L"),t(".")],-1),p=e("p",null,[e("img",{src:n,alt:"Grammar Selector",title:"Grammar Selector"})],-1),_=e("p",null,"When the grammar of a file is changed, Atom will remember that for the current session.",-1),g={href:"https://github.com/atom/atom/tree/master/packages/grammar-selector",target:"_blank",rel:"noopener noreferrer"};function w(k,y){const r=o("RouterLink"),i=o("ExternalLinkIcon");return l(),m("div",null,[d,e("p",null,[t('The "grammar" of a file is what language Atom has associated with that file. Types of grammars would include "Java" or "GitHub-Flavored Markdown". We looked at this a bit when we created some snippets in '),a(r,{to:"/using-atom/sections/snippets/"},{default:c(()=>[t("Snippets")]),_:1}),t(".")]),f,u,p,_,e("p",null,[t("The Grammar Selector functionality is implemented in the "),e("a",g,[t("grammar-selector"),a(i)]),t(" package.")])])}const G=s(h,[["render",w],["__file","grammar.html.vue"]]);export{G as default}; diff --git a/assets/grammar.html.bc810e1f.js b/assets/grammar.html.bc810e1f.js new file mode 100644 index 0000000000..783e42adbe --- /dev/null +++ b/assets/grammar.html.bc810e1f.js @@ -0,0 +1 @@ +const a=JSON.parse('{"key":"v-0af9c8e1","path":"/docs/launch-manual/sections/using-pulsar/sections/grammar.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Grammar","slug":"grammar","link":"#grammar","children":[]}],"git":{"updatedTime":1669229414000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":3}]},"readingTime":{"minutes":0.69,"words":206},"filePathRelative":"docs/launch-manual/sections/using-pulsar/sections/grammar.md"}');export{a as data}; diff --git a/assets/grammar.html.e25e2511.js b/assets/grammar.html.e25e2511.js new file mode 100644 index 0000000000..e7fcd7ae9a --- /dev/null +++ b/assets/grammar.html.e25e2511.js @@ -0,0 +1 @@ +import{_ as r}from"./grammar.c16b8cd6.js";import{_ as o,o as i,c as s,a as t,b as e,d as n,f as l,r as m}from"./app.0e1565ce.js";const c={},h=l('

    Grammar

    The "grammar" of a file is what language Pulsar has associated with that file. Types of grammars would include "Java" or "GitHub-Flavored Markdown". We looked at this a bit when we created some snippets in Snippets.

    When you load a file, Pulsar does a little work to try to figure out what type of file it is. Largely this is accomplished by looking at its file extension (.md is generally a Markdown file, etc.), though sometimes it has to inspect the content a bit to figure it out.

    When you open a file and Pulsar can't determine a grammar for the file, it will default to "Plain Text", which is the simplest one. If it does default to "Plain Text", picks the wrong grammar for the file, or if for any reason you wish to change the selected grammar, you can pull up the Grammar Selector with Ctrl+Shift+L.

    Grammar Selector

    When the grammar of a file is changed, Atom will remember that for the current session.

    ',6),u={href:"https://github.com/pulsar-edit/pulsar/tree/master/packages/grammar-selector",target:"_blank",rel:"noopener noreferrer"};function d(p,f){const a=m("ExternalLinkIcon");return i(),s("div",null,[h,t("p",null,[e("The Grammar Selector functionality is implemented in the "),t("a",u,[e("grammar-selector"),n(a)]),e(" package.")])])}const k=o(c,[["render",d],["__file","grammar.html.vue"]]);export{k as default}; diff --git a/assets/hacking-on-atom-core.html.65b1ff88.js b/assets/hacking-on-atom-core.html.65b1ff88.js new file mode 100644 index 0000000000..bd07aa130a --- /dev/null +++ b/assets/hacking-on-atom-core.html.65b1ff88.js @@ -0,0 +1,31 @@ +import{_ as u,o as h,c as m,a as e,b as n,d as o,w as l,f as d,r as c}from"./app.0e1565ce.js";const p={},b=e("h3",{id:"hacking-on-atom-core",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#hacking-on-atom-core","aria-hidden":"true"},"#"),n(" Hacking on Atom Core")],-1),g=e("p",null,"If you're hitting a bug in Atom or just want to experiment with adding a feature to the core of the system, you'll want to run Atom in Dev Mode with access to a local copy of the Atom source.",-1),_=e("h4",{id:"fork-the-atom-atom-repository",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#fork-the-atom-atom-repository","aria-hidden":"true"},"#"),n(" Fork the atom/atom repository")],-1),v={href:"https://help.github.com/articles/fork-a-repo/",target:"_blank",rel:"noopener noreferrer"},f=d(`

    Cloning and bootstrapping

    Once you've set up your fork of the atom/atom repository, you can clone it to your local machine:

    $ git clone git@github.com:<em>your-username</em>/atom.git
    +

    From there, you can navigate into the directory where you've cloned the Atom source code and run the bootstrap script to install all the required dependencies:

    `,4),y=e("div",{class:"language-command-line ext-command-line line-numbers-mode"},[e("pre",{class:"language-command-line"},[e("code",null,`$ cd where-you-cloned-atom +$ script/bootstrap +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),w=e("div",{class:"language-command-line ext-command-line line-numbers-mode"},[e("pre",{class:"language-command-line"},[e("code",null,`$ cd where-you-cloned-atom +$ script/bootstrap +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),x=e("div",{class:"language-command-line ext-command-line line-numbers-mode"},[e("pre",{class:"language-command-line"},[e("code",null,`$ cd where-you-cloned-atom +$ script\\bootstrap +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),k=d(`

    Running in Development Mode

    Once you have a local copy of Atom cloned and bootstrapped, you can then run Atom in Development Mode. But first, if you cloned Atom to somewhere other than ~/github/atom%USERPROFILE%\\github\\atom you will need to set the ATOM_DEV_RESOURCE_PATH environment variable to point to the folder in which you cloned Atom. To run Atom in Dev Mode, use the --dev parameter from the terminal:

    $ atom --dev <em>path-to-open</em>
    +

    Note

    Note: If the atom command does not respond in the terminal, then try atom-dev or atom-beta. The suffix depends upon the particular source code that was cloned.

    There are a couple benefits of running Atom in Dev Mode:

    `,5),A=d('
  • When the ATOM_DEV_RESOURCE_PATH environment variable is set correctly, Atom is run using the source code from your local atom/atom repository. This means that you don't have to run script/buildscript\\build every time you change code. Just restart Atom \u{1F44D}
  • Packages that exist in ~/.atom/dev/packages%USERPROFILE%\\.atom\\dev\\packages are loaded instead of packages of the same name normally loaded from other locations. This means that you can have development versions of packages you use loaded but easily go back to the stable versions by launching without Dev Mode.
  • ',2),S={href:"https://github.com/atom/dev-live-reload",target:"_blank",rel:"noopener noreferrer"},T=e("code",null,"window:reload",-1),P=d(`

    Running Atom Core Tests Locally

    In order to run Atom Core tests from the terminal, first be certain to set the ATOM_DEV_RESOURCE_PATH environment variable as mentioned above and then:

    $ cd <em>path-to-your-local-atom-repo</em>
    +$ atom --test spec
    +

    Building

    In order to build Atom from source, you need to have a number of other requirements and take additional steps.

    `,5),E=e("p",null,"Ubuntu LTS 16.04 64-bit is the recommended platform.",-1),I=e("h5",{id:"requirements",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#requirements","aria-hidden":"true"},"#"),n(" Requirements")],-1),C=e("li",null,"OS with 64-bit or 32-bit architecture",-1),$=e("li",null,"C++11 toolchain",-1),D=e("li",null,"Git",-1),O={href:"https://github.com/creationix/nvm",target:"_blank",rel:"noopener noreferrer"},R=e("li",null,[n("npm 6.12 or later (run "),e("code",null,"npm install -g npm"),n(")")],-1),M=e("li",null,"Python 2.6.x, 2.7.x or 3.5+",-1),V={href:"https://wiki.gnome.org/Projects/Libsecret",target:"_blank",rel:"noopener noreferrer"},j=e("p",null,"For more details, scroll down to find how to setup a specific Linux distro.",-1),N=e("h6",{id:"ubuntu-debian",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#ubuntu-debian","aria-hidden":"true"},"#"),n(" Ubuntu / Debian")],-1),L=e("ul",null,[e("li",null,[e("p",null,"Install GNOME headers and other basic prerequisites:"),e("div",{class:"language-command-line ext-command-line line-numbers-mode"},[e("pre",{class:"language-command-line"},[e("code",null,`$ sudo apt-get install build-essential git libsecret-1-dev fakeroot rpm libx11-dev libxkbfile-dev +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"})])])]),e("li",null,[e("p",null,[n("If "),e("code",null,"script/build"),n(" exits with an error, you may need to install a newer C++ compiler with C++11:")]),e("div",{class:"language-command-line ext-command-line line-numbers-mode"},[e("pre",{class:"language-command-line"},[e("code",null,`$ sudo add-apt-repository ppa:ubuntu-toolchain-r/test +$ sudo apt-get update +$ sudo apt-get install gcc-5 g++-5 +$ sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-5 80 --slave /usr/bin/g++ g++ /usr/bin/g++-5 +$ sudo update-alternatives --config gcc # choose gcc-5 from the list +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"})])])])],-1),W=e("h6",{id:"fedora-22",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#fedora-22","aria-hidden":"true"},"#"),n(" Fedora 22+")],-1),U=e("div",{class:"language-command-line ext-command-line line-numbers-mode"},[e("pre",{class:"language-command-line"},[e("code",null,`$ sudo dnf --assumeyes install make gcc gcc-c++ glibc-devel git-core libsecret-devel rpmdevtools libX11-devel libxkbfile-devel +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"})])],-1),F=e("h6",{id:"fedora-21-centos-rhel",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#fedora-21-centos-rhel","aria-hidden":"true"},"#"),n(" Fedora 21 / CentOS / RHEL")],-1),B=e("div",{class:"language-command-line ext-command-line line-numbers-mode"},[e("pre",{class:"language-command-line"},[e("code",null,`$ sudo yum install -y make gcc gcc-c++ glibc-devel git-core libsecret-devel rpmdevtools +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"})])],-1),G=e("h6",{id:"arch",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#arch","aria-hidden":"true"},"#"),n(" Arch")],-1),q=e("div",{class:"language-command-line ext-command-line line-numbers-mode"},[e("pre",{class:"language-command-line"},[e("code",null,`sudo pacman -S --needed gconf base-devel git nodejs npm libsecret python libx11 libxkbfile +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"})])],-1),z=e("h6",{id:"slackware",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#slackware","aria-hidden":"true"},"#"),n(" Slackware")],-1),H=e("div",{class:"language-command-line ext-command-line line-numbers-mode"},[e("pre",{class:"language-command-line"},[e("code",null,`$ sbopkg -k -i node -i atom +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"})])],-1),K=e("h6",{id:"opensuse",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#opensuse","aria-hidden":"true"},"#"),n(" openSUSE")],-1),Y=e("div",{class:"language-command-line ext-command-line line-numbers-mode"},[e("pre",{class:"language-command-line"},[e("code",null,`$ sudo zypper install nodejs nodejs-devel make gcc gcc-c++ glibc-devel git-core libsecret-devel rpmdevtools libX11-devel libxkbfile-devel +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"})])],-1),X=e("h5",{id:"requirements-1",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#requirements-1","aria-hidden":"true"},"#"),n(" Requirements")],-1),J=e("li",null,"macOS 10.9 or later",-1),Q={href:"https://github.com/creationix/nvm",target:"_blank",rel:"noopener noreferrer"},Z=e("li",null,[n("npm 6.12 or later (run "),e("code",null,"npm install -g npm"),n(")")],-1),ee=e("li",null,"Python v2.6.x, v2.7.x or v3.5+",-1),ne={href:"https://developer.apple.com/xcode/downloads/",target:"_blank",rel:"noopener noreferrer"},oe=e("code",null,"xcode-select --install",-1),te=e("li",null,[e("p",null,"Node.js 10.12 or later (the architecture of node available to the build system will determine whether you build 32-bit or 64-bit Atom)")],-1),le=e("li",null,[e("p",null,[n("npm 6.12 or later (run "),e("code",null,"npm install -g npm"),n(")")])],-1),ie=e("p",null,"Python v2.6.x, v2.7.x, or v3.5+",-1),ae={href:"https://www.microsoft.com/en-us/search/shop/apps?q=python+software+foundation&devicetype=pc&category=Developer+tools%5cDevelopment+kits&Price=0&MaturityRating=ESRB%3aE",target:"_blank",rel:"noopener noreferrer"},se=e("li",null,[n("Download Python from https://www.python.org/downloads/. "),e("ul",null,[e("li",null,'For Python 2, be sure to install in the default location, or check "Add Python 2.x to PATH" before installing.'),e("li",null,[n('For Python 3, check "Add Python 3.x to PATH", or change the install path to '),e("code",null,"[Your_Drive_Letter]:\\Python37"),n(" e.g. "),e("code",null,"C:\\Python37"),n(", (even if your version of Python 3 isn't 3.7, that's one place where the scripts will look.)")]),e("li",null,[n("If python isn't found by the bootstrap script, create a symbolic link to the directory containing "),e("code",null,"python.exe"),n(" using e.g.: "),e("code",null,"mklink /d %SystemDrive%\\Python27 D:\\elsewhere\\Python27"),n("(Links should be set at either "),e("code",null,"%SystemDrive%\\Python27"),n(" or "),e("code",null,"%SystemDrive%\\Python37"),n(", regardless of what version of Python you actually have.)")])])],-1),re=e("p",null,"C++ build tools:",-1),de=e("strong",null,"Option 1:",-1),ce={href:"https://www.npmjs.com/package/windows-build-tools",target:"_blank",rel:"noopener noreferrer"},ue=e("code",null,"npm install --global windows-build-tools@4",-1),he=e("strong",null,"Option 2:",-1),me={href:"https://visualstudio.microsoft.com/visual-cpp-build-tools/",target:"_blank",rel:"noopener noreferrer"},pe=e("strong",null,"Option 3:",-1),be={href:"https://www.visualstudio.com/downloads/",target:"_blank",rel:"noopener noreferrer"},ge=e("p",null,"Also ensure that:",-1),_e=e("ul",null,[e("li",null,"The default installation folder is chosen so the build tools can find it"),e("li",null,"If using Visual Studio make sure Visual C++ support is selected/installed"),e("li",null,"If using Visual C++ Build Tools make sure a Windows SDK (Windows 8 SDK or Windows 10 SDK) is selected/installed"),e("li",null,[n("A "),e("code",null,"git"),n(" command is in your path")]),e("li",null,[n("Set the "),e("code",null,"GYP_MSVS_VERSION"),n(" environment variable to the Visual Studio/Build Tools version ("),e("code",null,"2015"),n(" or "),e("code",null,"2017"),n(".) e.g. "),e("code",null,'[Environment]::SetEnvironmentVariable("GYP_MSVS_VERSION", "2015", "User")'),n(" in PowerShell (or set it in Windows advanced system settings).")])],-1),ve=e("h5",{id:"instructions",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#instructions","aria-hidden":"true"},"#"),n(" Instructions")],-1),fe=e("div",{class:"language-command-line ext-command-line line-numbers-mode"},[e("pre",{class:"language-command-line"},[e("code",null,`$ script/build +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"})])],-1),ye=e("p",null,[n("To also install the newly built application, use the "),e("code",null,"--create-debian-package"),n(" or "),e("code",null,"--create-rpm-package"),n(" option and then install the generated package via the system package manager.")],-1),we=e("div",{class:"language-command-line ext-command-line line-numbers-mode"},[e("pre",{class:"language-command-line"},[e("code",null,`$ script/build +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"})])],-1),xe=e("p",null,[n("To also install the newly built application, use "),e("code",null,"script/build --install"),n(".")],-1),ke=e("div",{class:"language-command-line ext-command-line line-numbers-mode"},[e("pre",{class:"language-command-line"},[e("code",null,`$ script\\build +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"})])],-1),Ae=e("p",null,[n("To also install the newly built application, use "),e("code",null,"script\\build --create-windows-installer"),n(" and launch one of the generated installers.")],-1),Se=e("h5",{id:"script-build-options",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#script-build-options","aria-hidden":"true"},"#"),n(),e("code",null,"script/build"),n(" Options")],-1),Te=e("ul",null,[e("li",null,[e("code",null,"--compress-artifacts"),n(": zips the generated application as "),e("code",null,"out/atom-{arch}.tar.gz"),n(".")]),e("li",null,[e("code",null,"--create-debian-package"),n(": creates a .deb package as "),e("code",null,"out/atom-{arch}.deb")]),e("li",null,[e("code",null,"--create-rpm-package"),n(": creates a .rpm package as "),e("code",null,"out/atom-{arch}.rpm")]),e("li",null,[e("code",null,"--install[=dir]"),n(": installs the application in "),e("code",null,"${dir}"),n("; "),e("code",null,"${dir}"),n(" defaults to "),e("code",null,"/usr/local"),n(".")])],-1),Pe=e("ul",null,[e("li",null,[e("code",null,"--code-sign"),n(": signs the application with the GitHub certificate specified in "),e("code",null,"$ATOM_MAC_CODE_SIGNING_CERT_DOWNLOAD_URL"),n(".")]),e("li",null,[e("code",null,"--compress-artifacts"),n(": zips the generated application as "),e("code",null,"out/atom-mac.zip"),n(".")]),e("li",null,[e("code",null,"--install[=dir]"),n(": installs the application at "),e("code",null,"${dir}/Atom.app"),n(" for dev and stable versions or at "),e("code",null,"${dir}/Atom-Beta.app"),n(" for beta versions; "),e("code",null,"${dir}"),n(" defaults to "),e("code",null,"/Applications"),n(".")])],-1),Ee=e("ul",null,[e("li",null,[e("code",null,"--code-sign"),n(": signs the application with the GitHub certificate specified in "),e("code",null,"$WIN_P12KEY_URL"),n(".")]),e("li",null,[e("code",null,"--compress-artifacts"),n(": zips the generated application as "),e("code",null,"out\\atom-windows.zip"),n(".")]),e("li",null,[e("code",null,"--create-windows-installer"),n(": creates an "),e("code",null,".exe"),n(" and two "),e("code",null,".nupkg"),n(" packages in the "),e("code",null,"out"),n(" directory.")]),e("li",null,[e("code",null,"--install[=dir]"),n(": installs the application in "),e("code",null,"${dir}\\Atom\\app-dev"),n("; "),e("code",null,"${dir}"),n(" defaults to "),e("code",null,"%LOCALAPPDATA%"),n(".")])],-1),Ie=e("h5",{id:"troubleshooting",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#troubleshooting","aria-hidden":"true"},"#"),n(" Troubleshooting")],-1),Ce=e("h6",{id:"typeerror-unable-to-watch-path",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#typeerror-unable-to-watch-path","aria-hidden":"true"},"#"),n(" TypeError: Unable to watch path")],-1),$e=e("p",null,"If you get following error with a big traceback right after Atom starts:",-1),De=e("div",{class:"language-text ext-text line-numbers-mode"},[e("pre",{class:"language-text"},[e("code",null,`TypeError: Unable to watch path +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"})])],-1),Oe=e("p",null,"you have to increase number of watched files by inotify. For testing if this is the reason for this error you can execute:",-1),Re=e("div",{class:"language-command-line ext-command-line line-numbers-mode"},[e("pre",{class:"language-command-line"},[e("code",null,`$ sudo sysctl fs.inotify.max_user_watches=32768 +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"})])],-1),Me=e("p",null,"then restart Atom. If Atom now works fine, you can make this setting permanent:",-1),Ve=e("div",{class:"language-command-line ext-command-line line-numbers-mode"},[e("pre",{class:"language-command-line"},[e("code",null,`$ echo 32768 | sudo tee -a /proc/sys/fs/inotify/max_user_watches +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"})])],-1),je={href:"https://github.com/atom/atom/issues/2082",target:"_blank",rel:"noopener noreferrer"},Ne=e("h6",{id:"usr-bin-env-node-no-such-file-or-directory",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#usr-bin-env-node-no-such-file-or-directory","aria-hidden":"true"},"#"),n(" /usr/bin/env: node: No such file or directory")],-1),Le=e("p",null,[n("If you get this notice when attempting to run any script, you either do not have Node.js installed, or node isn't identified as Node.js on your machine. If it's the latter, this might be caused by installing Node.js via the distro package manager and not nvm, so entering "),e("code",null,"sudo ln -s /usr/bin/nodejs /usr/bin/node"),n(" into your terminal may fix the issue. On some variants (mostly Debian based distros) you can use "),e("code",null,"update-alternatives"),n(" too:")],-1),We=e("div",{class:"language-command-line ext-command-line line-numbers-mode"},[e("pre",{class:"language-command-line"},[e("code",null,`$ sudo update-alternatives --install /usr/bin/node node /usr/bin/nodejs 1 --slave /usr/bin/js js /usr/bin/nodejs +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"})])],-1),Ue=e("h6",{id:"attributeerror-module-object-has-no-attribute-script-main",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#attributeerror-module-object-has-no-attribute-script-main","aria-hidden":"true"},"#"),n(" AttributeError: 'module' object has no attribute 'script_main'")],-1),Fe=e("p",null,"If you get following error with a big traceback while building Atom:",-1),Be=e("div",{class:"language-text ext-text line-numbers-mode"},[e("pre",{class:"language-text"},[e("code",null,`sys.exit(gyp.script_main()) AttributeError: 'module' object has no attribute 'script_main' gyp ERR! +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"})])],-1),Ge=e("p",null,"you need to uninstall the system version of gyp.",-1),qe=e("p",null,"On Fedora you would do the following:",-1),ze=e("div",{class:"language-command-line ext-command-line line-numbers-mode"},[e("pre",{class:"language-command-line"},[e("code",null,`$ sudo yum remove gyp +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"})])],-1),He=e("h6",{id:"linux-build-error-reports",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#linux-build-error-reports","aria-hidden":"true"},"#"),n(" Linux build error reports")],-1),Ke={href:"https://github.com/atom/atom/search?q=label%3Abuild-error+label%3Alinux&type=Issues",target:"_blank",rel:"noopener noreferrer"},Ye={href:"https://github.com/atom/atom/search?q=label%3Abuild-error+label%3Amac&type=Issues",target:"_blank",rel:"noopener noreferrer"},Xe=e("h6",{id:"common-errors",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#common-errors","aria-hidden":"true"},"#"),n(" Common Errors")],-1),Je=e("li",null,[e("p",null,[e("code",null,"node is not recognized")]),e("ul",null,[e("li",null,[n("If you just installed Node.js, you'll need to restart Command Prompt before the "),e("code",null,"node"),n(" command is available on your path.")])])],-1),Qe=e("li",null,[e("p",null,[e("code",null,"msbuild.exe failed with exit code: 1")]),e("ul",null,[e("li",null,[n("If using "),e("strong",null,"Visual Studio"),n(", ensure you have the "),e("strong",null,"Visual C++"),n(" component installed. Go into Add/Remove Programs, select Visual Studio, press Modify, and then check the Visual C++ box.")]),e("li",null,[n("If using "),e("strong",null,"Visual C++ Build Tools"),n(", ensure you have the "),e("strong",null,"Windows 8 SDK"),n(" or "),e("strong",null,"Windows 10 SDK"),n(' component installed. Go into Add/Remove Programs, select Visual C++ Build Tools, press Modify and then check the "Windows 8 SDK" or "Windows 10 SDK" box.')])])],-1),Ze=e("p",null,[e("code",null,"script\\build"),n(" stops with no error or warning shortly after displaying the versions of node, npm and Python")],-1),en=e("li",null,[n("Make sure that the path where you have checked out Atom does not include a space. For example, use "),e("code",null,"C:\\atom"),n(" instead of "),e("code",null,"C:\\my stuff\\atom"),n(".")],-1),nn=e("code",null,"C:\\atom",-1),on={href:"https://github.com/atom/atom/issues/2200",target:"_blank",rel:"noopener noreferrer"},tn=e("p",null,[e("code",null,"error MSB4025: The project file could not be loaded. Invalid character in the given encoding.")],-1),ln=e("code",null,"%USERPROFILE%",-1),an={href:"https://bugs.chromium.org/p/gyp/issues/list",target:"_blank",rel:"noopener noreferrer"},sn=e("ul",null,[e("li",null,"https://github.com/TooTallNate/node-gyp/issues/297"),e("li",null,"https://bugs.chromium.org/p/gyp/issues/detail?id=393")],-1),rn=e("li",null,[e("p",null,[e("code",null,"'node_modules\\.bin\\npm' is not recognized as an internal or external command, operable program or batch file.")]),e("ul",null,[e("li",null,[n("This occurs if the previous build left things in a bad state. Run "),e("code",null,"script\\clean"),n(" and then "),e("code",null,"script\\build"),n(" again.")])])],-1),dn=e("li",null,[e("p",null,[e("code",null,"script\\build"),n(" stops at installing runas with "),e("code",null,"Failed at the runas@x.y.z install script.")]),e("ul",null,[e("li",null,"See the next item.")])],-1),cn=e("li",null,[e("p",null,[e("code",null,"error MSB8020: The build tools for Visual Studio 201? (Platform Toolset = 'v1?0') cannot be found.")]),e("ul",null,[e("li",null,[n("Try setting the "),e("code",null,"GYP_MSVS_VERSION"),n(" environment variable to "),e("strong",null,"2015"),n(" or "),e("strong",null,"2017"),n(" depending on what version of Visual Studio/Build Tools is installed and then "),e("code",null,"script\\clean"),n(" followed by "),e("code",null,"script\\build"),n(" (re-open the Command Prompt if you set the variable using the GUI).")])])],-1),un=e("li",null,[e("p",null,[e("code",null,"'node-gyp' is not recognized as an internal or external command, operable program or batch file.")]),e("ul",null,[e("li",null,[n("Try running "),e("code",null,"npm install -g node-gyp"),n(", and run "),e("code",null,"script\\build"),n(" again.")])])],-1),hn=e("li",null,[e("p",null,[n("Other "),e("code",null,"node-gyp"),n(" errors on first build attempt, even though the right Node.js and Python versions are installed.")]),e("ul",null,[e("li",null,"Do try the build command one more time as experience shows it often works on second try in many cases.")])],-1),mn=e("h6",{id:"windows-build-error-reports",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#windows-build-error-reports","aria-hidden":"true"},"#"),n(" Windows build error reports")],-1),pn={href:"https://github.com/atom/atom/search?q=label%3Abuild-error+label%3Awindows&type=Issues",target:"_blank",rel:"noopener noreferrer"},bn=e("li",null,"If it hasn't, please open a new issue with your Windows version, architecture (x86 or x64), and a text dump of your build output, including the Node.js and Python versions.",-1);function gn(_n,vn){const t=c("ExternalLinkIcon"),r=c("Tabs");return h(),m("div",null,[b,g,_,e("p",null,[n("Follow the "),e("a",v,[n("GitHub Help instructions on how to fork a repo"),o(t)]),n(".")]),f,o(r,{id:"22",data:[{title:"Linux"},{title:"Mac"},{title:"Windows"}],"tab-id":"hacking-atom"},{tab0:l(({title:i,value:a,isActive:s})=>[y]),tab1:l(({title:i,value:a,isActive:s})=>[w]),tab2:l(({title:i,value:a,isActive:s})=>[x]),_:1}),k,e("ol",null,[A,e("li",null,[n("Packages that contain stylesheets, such as syntax themes, will have those stylesheets automatically reloaded by the "),e("a",S,[n("dev-live-reload"),o(t)]),n(" package. This does not live reload JavaScript or CoffeeScript files \u2014 you'll need to reload the window ("),T,n(") to see changes to those.")])]),P,o(r,{id:"78",data:[{title:"Linux"},{title:"Mac"},{title:"Windows"}],"tab-id":"hacking-atom"},{tab0:l(({title:i,value:a,isActive:s})=>[E,I,e("ul",null,[C,$,D,e("li",null,[n("Node.js 10.12 or later (we recommend installing it via "),e("a",O,[n("nvm"),o(t)]),n(")")]),R,M,e("li",null,[n("Development headers for "),e("a",V,[n("libsecret"),o(t)]),n(".")])]),j,N,L,W,U,F,B,G,q,z,H,K,Y]),tab1:l(({title:i,value:a,isActive:s})=>[X,e("ul",null,[J,e("li",null,[n("Node.js 10.12 or later (we recommend installing it via "),e("a",Q,[n("nvm"),o(t)]),n(")")]),Z,ee,e("li",null,[n("Command Line Tools for "),e("a",ne,[n("Xcode"),o(t)]),n(" (run "),oe,n(" to install)")])])]),tab2:l(({title:i,value:a,isActive:s})=>[e("ul",null,[te,le,e("li",null,[ie,e("ul",null,[e("li",null,[e("a",ae,[n("Get Python from the Microsoft Store"),o(t)]),n(", or")]),se])]),e("li",null,[re,e("ul",null,[e("li",null,[de,n(),e("a",ce,[n("windows-build-tools"),o(t)]),n(' - From an elevated Powershell window (right click and "run as Administrator") do: '),ue,n(" to install")]),e("li",null,[he,n(),e("a",me,[n("Visual C++ Build Tools 2015 or 2017"),o(t)])]),e("li",null,[pe,n(),e("a",be,[n("Visual Studio 2015 or 2017"),o(t)]),n(" (Community Edition or better)")])]),ge,_e])])]),_:1}),ve,o(r,{id:"300",data:[{title:"Linux"},{title:"Mac"},{title:"Windows"}],"tab-id":"hacking-atom"},{tab0:l(({title:i,value:a,isActive:s})=>[fe,ye]),tab1:l(({title:i,value:a,isActive:s})=>[we,xe]),tab2:l(({title:i,value:a,isActive:s})=>[ke,Ae]),_:1}),Se,o(r,{id:"323",data:[{title:"Linux"},{title:"Mac"},{title:"Windows"}],"tab-id":"hacking-atom"},{tab0:l(({title:i,value:a,isActive:s})=>[Te]),tab1:l(({title:i,value:a,isActive:s})=>[Pe]),tab2:l(({title:i,value:a,isActive:s})=>[Ee]),_:1}),Ie,o(r,{id:"395",data:[{title:"Linux"},{title:"Mac"},{title:"Windows"}],"tab-id":"hacking-atom"},{tab0:l(({title:i,value:a,isActive:s})=>[Ce,$e,De,Oe,Re,Me,Ve,e("p",null,[n("See also "),e("a",je,[n("#2082"),o(t)]),n(".")]),Ne,Le,We,Ue,Fe,Be,Ge,qe,ze,He,e("p",null,[n("Use "),e("a",Ke,[n("this search"),o(t)]),n(" to get a list of reports about build errors on Linux.")])]),tab1:l(({title:i,value:a,isActive:s})=>[e("p",null,[n("Use "),e("a",Ye,[n("this search"),o(t)]),n(" to get a list of reports about build errors on macOS.")])]),tab2:l(({title:i,value:a,isActive:s})=>[Xe,e("ul",null,[Je,Qe,e("li",null,[Ze,e("ul",null,[en,e("li",null,[n("Try moving the repository to "),nn,n(". Most likely, the path is too long. See "),e("a",on,[n("issue #2200"),o(t)]),n(".")])])]),e("li",null,[tn,e("ul",null,[e("li",null,[n("This can occur because your home directory ("),ln,n(") has non-ASCII characters in it. This is a bug in "),e("a",an,[n("gyp"),o(t)]),n(" which is used to build native Node.js modules and there is no known workaround. "),sn])])]),rn,dn,cn,un,hn]),mn,e("ul",null,[e("li",null,[n("If all else fails, use "),e("a",pn,[n("this search"),o(t)]),n(" to get a list of reports about build errors on Windows, and see if yours has already been reported.")]),bn])]),_:1})])}const yn=u(p,[["render",gn],["__file","hacking-on-atom-core.html.vue"]]);export{yn as default}; diff --git a/assets/hacking-on-atom-core.html.db966159.js b/assets/hacking-on-atom-core.html.db966159.js new file mode 100644 index 0000000000..1c4ae5edad --- /dev/null +++ b/assets/hacking-on-atom-core.html.db966159.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-bb85f9fa","path":"/docs/atom-archive/hacking-atom/sections/hacking-on-atom-core.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"Hacking on Atom Core","slug":"hacking-on-atom-core","link":"#hacking-on-atom-core","children":[]}],"git":{"updatedTime":1664050274000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":3}]},"readingTime":{"minutes":7.57,"words":2272},"filePathRelative":"docs/atom-archive/hacking-atom/sections/hacking-on-atom-core.md"}');export{e as data}; diff --git a/assets/hacking-on-the-core.html.5c30ae63.js b/assets/hacking-on-the-core.html.5c30ae63.js new file mode 100644 index 0000000000..be708ecbc5 --- /dev/null +++ b/assets/hacking-on-the-core.html.5c30ae63.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-143cd798","path":"/docs/launch-manual/sections/core-hacking/sections/hacking-on-the-core.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Hacking on the Core","slug":"hacking-on-the-core","link":"#hacking-on-the-core","children":[{"level":3,"title":"Running in Development Mode","slug":"running-in-development-mode","link":"#running-in-development-mode","children":[]},{"level":3,"title":"Running Pulsar Core Tests Locally","slug":"running-pulsar-core-tests-locally","link":"#running-pulsar-core-tests-locally","children":[]}]}],"git":{"updatedTime":1670466847000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":0.98,"words":295},"filePathRelative":"docs/launch-manual/sections/core-hacking/sections/hacking-on-the-core.md"}');export{e as data}; diff --git a/assets/hacking-on-the-core.html.e53f325e.js b/assets/hacking-on-the-core.html.e53f325e.js new file mode 100644 index 0000000000..985d35efb4 --- /dev/null +++ b/assets/hacking-on-the-core.html.e53f325e.js @@ -0,0 +1,4 @@ +import{_ as s,o as t,c as r,a,b as e,d as l,f as o,r as i}from"./app.0e1565ce.js";const c={},d=o(`

    Hacking on the Core

    You will first want to build and run Pulsar from source.

    Running in Development Mode

    Once you have a local copy of Pulsar cloned and built, you can then run Pulsar in Development Mode. But first, if you cloned Pulsar to somewhere other than LNX/MAC: ~/github/pulsar - WIN: %USERPROFILE%\\github\\pulsar you will need to set the ATOM_DEV_RESOURCE_PATH environment variable to point to the folder in which you cloned Pulsar. To run Pulsar in Dev Mode, use the --dev parameter from the terminal:

    $ pulsar --dev <path-to-open>
    +

    There are a couple benefits of running Pulsar in Dev Mode:

    `,6),u=o("
  • When the ATOM_DEV_RESOURCE_PATH environment variable is set correctly, Pulsar is run using the source code from your local pulsar-edit/pulsar repository. This means you don't have to rebuild after every change, just restart Pulsar \u{1F44D}
  • Packages that exist in LNX/MAC: ~/.pulsar/dev/packages - WIN: %USERPROFILE%\\.pulsar\\dev\\packages are loaded instead of packages of the same name normally loaded from other locations. This means that you can have development versions of packages you use loaded but easily go back to the stable versions by launching without Dev Mode.
  • ",2),h={href:"https://github.com/pulsar-edit/pulsar/tree/master/packages/dev-live-reload",target:"_blank",rel:"noopener noreferrer"},p=a("code",null,"window:reload",-1),m=o(`

    Running Pulsar Core Tests Locally

    In order to run Pulsar Core tests from the terminal, first be certain to set the ATOM_DEV_RESOURCE_PATH environment variable as mentioned above and then:

    $ cd <path-to-your-local-pulsar-repo>
    +$ pulsar --test spec
    +
    `,3);function v(g,b){const n=i("ExternalLinkIcon");return t(),r("div",null,[d,a("ol",null,[u,a("li",null,[e("Packages that contain stylesheets, such as syntax themes, will have those stylesheets automatically reloaded by the "),a("a",h,[e("dev-live-reload"),l(n)]),e(" package. This does not live reload JavaScript or CoffeeScript files \u2014 you'll need to reload the window ("),p,e(") to see changes to those.")])]),m])}const _=s(c,[["render",v],["__file","hacking-on-the-core.html.vue"]]);export{_ as default}; diff --git a/assets/handling-uris.html.5686dc7c.js b/assets/handling-uris.html.5686dc7c.js new file mode 100644 index 0000000000..3a44063aa6 --- /dev/null +++ b/assets/handling-uris.html.5686dc7c.js @@ -0,0 +1,31 @@ +import{_ as t,o,c as r,a as e,b as a,d as p,f as n,r as l}from"./app.0e1565ce.js";const i={},c=n(`

    Handling URIs

    Packages have the ability to handle special URIs triggered from the system; for example, a package named my-package can register itself to handle any URI starting with atom://my-package/.

    WARNING

    Handling URIs triggered from other applications, like a web browser, is a powerful tool, but also one that can be jarring. You should shape your package's user experience to handle this well. In general, you should avoid taking direct action on behalf of a user. For example, a URI handler that immediately installs a package is too invasive, but a URI handler that shows the package's pane in the settings view is useful. A URI handler that begins to clone a repo is overly aggressive, but a URI handler that prompts the user to clone a repo is okay.

    Any package with a URI handler that we feel violates this guideline is subject to removal from the Pulsar package registry at our discretion.

    Modifying your package.json

    The first step to handling URIs from your package is to modify its package.json file. You should add a new key called uriHandler, and its value should be an object.

    The uriHandler object must contain a key called method with a string value that tells Pulsar which method in your package to call when a URI needs to be handled. The object can optionally include a key called deferActivation which can be set to the boolean false to prevent Pulsar from deferring activation of your package \u2014\xA0see more below.

    For example, if we want our package my-package to handle URIs with a method on our package's main module called handleURI, we could add the following to our package.json:

    "uriHandler": {
    +  "method": "handleURI"
    +}
    +

    Modifying your Main Module

    `,9),d=e("code",null,"atom://my-package/",-1),u=e("code",null,"handleURI",-1),h={href:"https://nodejs.org/api/url.html#url_url_parse_urlstring_parsequerystring_slashesdenotehost",target:"_blank",rel:"noopener noreferrer"},m=e("code",null,"url.parse(uri, true)",-1),k=n(`

    Here's a sample package, written in JavaScript, that handles URIs with the package.json configuration we saw above.

    export default {
    +	activate() {
    +		// normal activation code here
    +	},
    +
    +	handleURI(parsedUri) {
    +		console.log(parsedUri);
    +	},
    +};
    +

    When Pulsar handles, for example, the URI atom://my-package/my/test/url?value=42&other=false, the package would log out something like the following:

    {
    +  protocol: 'atom:',
    +  slashes: true,
    +  auth: null,
    +  host: 'my-package',
    +  port: null,
    +  hostname: 'my-package',
    +  hash: null,
    +  search: '?value=true&other=false',
    +  query: { value: '42', other: 'false' },
    +  pathname: '/my/test/url',
    +  path: '/my/test/url?value=true&other=false',
    +  href: 'atom://my-package/my/test/url?value=true&other=false'
    +}
    +

    Notice that the query string arguments are available in the query property, but are strings \u2014\xA0you'll have to convert to other native types yourself.

    Controlling Activation Deferral

    For performance reasons, adding a uriHandler entry to your package's package.json will enable deferred activation. This means that Pulsar will not activate your package until it has a URI for it to handle \u2014\xA0it will then activate your package and then immediately call the URI handler method. If you want to disable the deferred activation, ensuring your package is activated upon startup, you can add "deferActivation": false to the URI handler config. For example,

    "uriHandler": {
    +  "method": "handleURI",
    +  "deferActivation": false
    +}
    +

    Before doing this, make sure your package actually needs to be activated immediately \u2014\xA0disabling deferred activation means Pulsar takes longer to start since it has to activate all packages without deferred activation.

    Linux Support

    Because URI handling is different across operating systems and distributions, there is no built-in URI handler support for Pulsar on Linux. If you want to configure URI handling on your system yourself, then you should configure atom: protocol URI's to trigger Pulsar with the --uri-handler flag; for example, the URI atom://test/uri should launch Atom via atom --uri-handler atom://test/uri.

    Core URIs

    Pulsar provides a core URI to handle opening files with the syntax atom://core/open/file?filename=<filepath>&line=<line>&column=<col>

    `,13);function g(v,y){const s=l("ExternalLinkIcon");return o(),r("div",null,[c,e("p",null,[a("Now that we've told Pulsar that we want our package to handle URIs beginning with "),d,a(" via our "),u,a(" method, we need to actually write this method. Pulsar passes two arguments to your URI handler method; the first one is the fully-parsed URI plus query string, "),e("a",h,[a("parsed with Node's "),m,p(s)]),a(". The second argument is the raw, string URI; this is normally not needed since the first argument gives you structured information about the URI.")]),k])}const b=t(i,[["render",g],["__file","handling-uris.html.vue"]]);export{b as default}; diff --git a/assets/handling-uris.html.595a8f9e.js b/assets/handling-uris.html.595a8f9e.js new file mode 100644 index 0000000000..0740e9c925 --- /dev/null +++ b/assets/handling-uris.html.595a8f9e.js @@ -0,0 +1,31 @@ +import{_ as t,o,c as r,a as e,b as a,d as p,f as n,r as i}from"./app.0e1565ce.js";const l={},c=n(`

    Handling URIs

    Beginning in Atom 1.23, packages have the ability to handle special URIs triggered from the system; for example, a package named my-package can register itself to handle any URI starting with atom://my-package/.

    WARNING

    Warning: Handling URIs triggered from other applications, like a web browser, is a powerful tool, but also one that can be jarring. You should shape your package's user experience to handle this well. In general, you should avoid taking direct action on behalf of a user. For example, a URI handler that immediately installs a package is too invasive, but a URI handler that shows the package's pane in the settings view is useful. A URI handler that begins to clone a repo is overly aggressive, but a URI handler that prompts the user to clone a repo is okay.

    Any package with a URI handler that we feel violates this guideline is subject to removal from the Atom package registry at our discretion.

    Modifying your package.json

    The first step to handling URIs from your package is to modify its package.json file. You should add a new key called uriHandler, and its value should be an object.

    The uriHandler object must contain a key called method with a string value that tells Atom which method in your package to call when a URI needs to be handled. The object can optionally include a key called deferActivation which can be set to the boolean false to prevent Atom from deferring activation of your package \u2014\xA0see more below.

    For example, if we want our package my-package to handle URIs with a method on our package's main module called handleURI, we could add the following to our package.json:

    "uriHandler": {
    +  "method": "handleURI"
    +}
    +

    Modifying your Main Module

    `,9),d=e("code",null,"atom://my-package/",-1),u=e("code",null,"handleURI",-1),h={href:"https://nodejs.org/api/url.html#url_url_parse_urlstring_parsequerystring_slashesdenotehost",target:"_blank",rel:"noopener noreferrer"},m=e("code",null,"url.parse(uri, true)",-1),k=n(`

    Here's a sample package, written in JavaScript, that handles URIs with the package.json configuration we saw above.

    export default {
    +	activate() {
    +		// normal activation code here
    +	},
    +
    +	handleURI(parsedUri) {
    +		console.log(parsedUri);
    +	},
    +};
    +

    When Atom handles, for example, the URI atom://my-package/my/test/url?value=42&other=false, the package would log out something like the following:

    {
    +  protocol: 'atom:',
    +  slashes: true,
    +  auth: null,
    +  host: 'my-package',
    +  port: null,
    +  hostname: 'my-package',
    +  hash: null,
    +  search: '?value=true&other=false',
    +  query: { value: '42', other: 'false' },
    +  pathname: '/my/test/url',
    +  path: '/my/test/url?value=true&other=false',
    +  href: 'atom://my-package/my/test/url?value=true&other=false'
    +}
    +

    Notice that the query string arguments are available in the query property, but are strings \u2014\xA0you'll have to convert to other native types yourself.

    Controlling Activation Deferral

    For performance reasons, adding a uriHandler entry to your package's package.json will enable deferred activation. This means that Atom will not activate your package until it has a URI for it to handle \u2014\xA0it will then activate your package and then immediately call the URI handler method. If you want to disable the deferred activation, ensuring your package is activated upon startup, you can add "deferActivation": false to the URI handler config. For example,

    "uriHandler": {
    +  "method": "handleURI",
    +  "deferActivation": false
    +}
    +

    Before doing this, make sure your package actually needs to be activated immediately \u2014\xA0disabling deferred activation means Atom takes longer to start since it has to activate all packages without deferred activation.

    Linux Support

    Because URI handling is different across operating systems and distributions, there is no built-in URI handler support for Atom on Linux. If you want to configure URI handling on your system yourself, then you should configure atom: protocol URI's to trigger atom with the --uri-handler flag; for example, the URI atom://test/uri should launch Atom via atom --uri-handler atom://test/uri.

    Core URIs

    Atom provides a core URI to handle opening files with the syntax atom://core/open/file?filename=<filepath>&line=<line>&column=<col>

    `,13);function g(v,y){const s=i("ExternalLinkIcon");return o(),r("div",null,[c,e("p",null,[a("Now that we've told Atom that we want our package to handle URIs beginning with "),d,a(" via our "),u,a(" method, we need to actually write this method. Atom passes two arguments to your URI handler method; the first one is the fully-parsed URI plus query string, "),e("a",h,[a("parsed with Node's "),m,p(s)]),a(". The second argument is the raw, string URI; this is normally not needed since the first argument gives you structured information about the URI.")]),k])}const b=t(l,[["render",g],["__file","handling-uris.html.vue"]]);export{b as default}; diff --git a/assets/handling-uris.html.a6645854.js b/assets/handling-uris.html.a6645854.js new file mode 100644 index 0000000000..ee663555e8 --- /dev/null +++ b/assets/handling-uris.html.a6645854.js @@ -0,0 +1 @@ +const i=JSON.parse('{"key":"v-c277d734","path":"/docs/launch-manual/sections/core-hacking/sections/handling-uris.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Handling URIs","slug":"handling-uris","link":"#handling-uris","children":[{"level":3,"title":"Modifying your package.json","slug":"modifying-your-package-json","link":"#modifying-your-package-json","children":[]},{"level":3,"title":"Modifying your Main Module","slug":"modifying-your-main-module","link":"#modifying-your-main-module","children":[]},{"level":3,"title":"Controlling Activation Deferral","slug":"controlling-activation-deferral","link":"#controlling-activation-deferral","children":[]},{"level":3,"title":"Linux Support","slug":"linux-support","link":"#linux-support","children":[]}]},{"level":2,"title":"Core URIs","slug":"core-uris","link":"#core-uris","children":[]}],"git":{"updatedTime":1670466847000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":2.45,"words":734},"filePathRelative":"docs/launch-manual/sections/core-hacking/sections/handling-uris.md"}');export{i as data}; diff --git a/assets/handling-uris.html.ac6b4f1b.js b/assets/handling-uris.html.ac6b4f1b.js new file mode 100644 index 0000000000..bae9b9bc92 --- /dev/null +++ b/assets/handling-uris.html.ac6b4f1b.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-06c8b972","path":"/docs/atom-archive/hacking-atom/sections/handling-uris.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"Handling URIs","slug":"handling-uris","link":"#handling-uris","children":[]},{"level":3,"title":"Core URIs","slug":"core-uris","link":"#core-uris","children":[]}],"git":{"updatedTime":1664050274000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":3}]},"readingTime":{"minutes":2.46,"words":739},"filePathRelative":"docs/atom-archive/hacking-atom/sections/handling-uris.md"}');export{e as data}; diff --git a/assets/hero.197a9d2d.jpg b/assets/hero.197a9d2d.jpg new file mode 100644 index 0000000000..7f3f61accd Binary files /dev/null and b/assets/hero.197a9d2d.jpg differ diff --git a/assets/how-atom-uses-chromium-snapshots.html.1d190b2c.js b/assets/how-atom-uses-chromium-snapshots.html.1d190b2c.js new file mode 100644 index 0000000000..ea956e57a1 --- /dev/null +++ b/assets/how-atom-uses-chromium-snapshots.html.1d190b2c.js @@ -0,0 +1 @@ +const s=JSON.parse('{"key":"v-778fbb37","path":"/docs/atom-archive/behind-atom/sections/how-atom-uses-chromium-snapshots.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"How Atom Uses Chromium Snapshots","slug":"how-atom-uses-chromium-snapshots","link":"#how-atom-uses-chromium-snapshots","children":[]}],"git":{"updatedTime":1668309800000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":3},{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":1.04,"words":313},"filePathRelative":"docs/atom-archive/behind-atom/sections/how-atom-uses-chromium-snapshots.md"}');export{s as data}; diff --git a/assets/how-atom-uses-chromium-snapshots.html.80b272b6.js b/assets/how-atom-uses-chromium-snapshots.html.80b272b6.js new file mode 100644 index 0000000000..d6c13562e4 --- /dev/null +++ b/assets/how-atom-uses-chromium-snapshots.html.80b272b6.js @@ -0,0 +1 @@ +import{_ as a,o as s,c as r,a as t,b as e,d as n,r as i}from"./app.0e1565ce.js";const l={},h=t("h3",{id:"how-atom-uses-chromium-snapshots",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#how-atom-uses-chromium-snapshots","aria-hidden":"true"},"#"),e(" How Atom Uses Chromium Snapshots")],-1),c={href:"https://v8project.blogspot.it/2015/09/custom-startup-snapshots.html",target:"_blank",rel:"noopener noreferrer"},p={href:"https://github.com/atom/electron-link",target:"_blank",rel:"noopener noreferrer"},d=t("code",null,"require",-1),m={href:"https://github.com/v8/v8/wiki/Embedder%27s-Guide#contexts",target:"_blank",rel:"noopener noreferrer"},u={href:"https://github.com/atom/atom/blob/74ff9fdb91205b89673209caf1e2ceb373e9c59f/script/lib/generate-startup-snapshot.js#L19-L65",target:"_blank",rel:"noopener noreferrer"},f={href:"https://github.com/atom/atom/blob/74ff9fdb91205b89673209caf1e2ceb373e9c59f/script/lib/generate-startup-snapshot.js#L73-L78",target:"_blank",rel:"noopener noreferrer"},b={href:"https://github.com/atom/atom/blob/74ff9fdb91205b89673209caf1e2ceb373e9c59f/script/lib/generate-startup-snapshot.js#L80-L89",target:"_blank",rel:"noopener noreferrer"};function g(_,w){const o=i("ExternalLinkIcon");return s(),r("div",null,[h,t("p",null,[e("In order to improve startup time, when Atom is built we create a "),t("a",c,[e("V8 snapshot"),n(o)]),e(" in which we preload core services and packages. Then, at runtime, we finish loading Atom by supplying all the information we didn't have during the compilation phase (e.g. loading third party packages, custom style sheets, configuration, etc.).")]),t("p",null,[t("a",p,[e("electron-link"),n(o)]),e(" is the tool that powers snapshots, as it enables us to traverse the entire require graph (starting at the entry point) and replace all the forbidden "),d,e(" calls (e.g. require calls to native modules, node core modules or other modules that can't be accessed in the snapshot "),t("a",m,[e("V8 context"),n(o)]),e(") with a function that will be called at runtime. When adding new code to Atom, we always try to put it inside the snapshot by, for example, deferring the usage of DOM APIs or native node modules to a later moment in time when those facilities are available. If that is not possible, we will add the unsupported code paths to the "),t("a",u,[e("list of files that get excluded from the snapshot"),n(o)]),e(", ensuring we only exclude those ones that are not supported as opposed to skipping an entire Node module.")]),t("p",null,[e("The output of electron-link is a single script containing the code for all the modules reachable from the entry point, which we then "),t("a",f,[e("supply to mksnapshot"),n(o)]),e(" to generate a snapshot blob.")]),t("p",null,[e("The generated blob is finally "),t("a",b,[e("copied into the application bundle"),n(o)]),e(" and will be automatically loaded by Electron when running Atom.")])])}const y=a(l,[["render",g],["__file","how-atom-uses-chromium-snapshots.html.vue"]]);export{y as default}; diff --git a/assets/how-can-i-contribute-to-atom.html.49db6f72.js b/assets/how-can-i-contribute-to-atom.html.49db6f72.js new file mode 100644 index 0000000000..8f77658881 --- /dev/null +++ b/assets/how-can-i-contribute-to-atom.html.49db6f72.js @@ -0,0 +1 @@ +const t=JSON.parse('{"key":"v-bc28a444","path":"/docs/atom-archive/faq/sections/how-can-i-contribute-to-atom.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"How can I contribute to Atom?","slug":"how-can-i-contribute-to-atom","link":"#how-can-i-contribute-to-atom","children":[]}],"git":{"updatedTime":1667690985000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1},{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":0.26,"words":79},"filePathRelative":"docs/atom-archive/faq/sections/how-can-i-contribute-to-atom.md"}');export{t as data}; diff --git a/assets/how-can-i-contribute-to-atom.html.6405f755.js b/assets/how-can-i-contribute-to-atom.html.6405f755.js new file mode 100644 index 0000000000..b6159687e0 --- /dev/null +++ b/assets/how-can-i-contribute-to-atom.html.6405f755.js @@ -0,0 +1 @@ +import{_ as n,o as r,c,a as e,b as t,d as a,r as i}from"./app.0e1565ce.js";const s={},h=e("h3",{id:"how-can-i-contribute-to-atom",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#how-can-i-contribute-to-atom","aria-hidden":"true"},"#"),t(" How can I contribute to Atom?")],-1),l={href:"https://flight-manual.atom.io/hacking-atom/sections/tools-of-the-trade/",target:"_blank",rel:"noopener noreferrer"},d={href:"https://github.com/atom",target:"_blank",rel:"noopener noreferrer"},m={href:"https://github.com/atom/atom/blob/master/CONTRIBUTING.md",target:"_blank",rel:"noopener noreferrer"};function u(_,b){const o=i("ExternalLinkIcon");return r(),c("div",null,[h,e("p",null,[t("You can contribute by "),e("a",l,[t("creating a package"),a(o)]),t(" that adds something awesome to Atom!")]),e("p",null,[t("Also, if you\u2019d like to contribute to the core editor, one of the bundled packages, or one of the libraries that power Atom, just go to "),e("a",d,[t("github.com/atom"),a(o)]),t(".")]),e("p",null,[t("You should also read the "),e("a",m,[t("contributing guide"),a(o)]),t(" before getting started.")])])}const p=n(s,[["render",u],["__file","how-can-i-contribute-to-atom.html.vue"]]);export{p as default}; diff --git a/assets/how-can-i-tell-if-subpixel-antialiasing-is-working.html.ee25faf8.js b/assets/how-can-i-tell-if-subpixel-antialiasing-is-working.html.ee25faf8.js new file mode 100644 index 0000000000..76ecff2429 --- /dev/null +++ b/assets/how-can-i-tell-if-subpixel-antialiasing-is-working.html.ee25faf8.js @@ -0,0 +1 @@ +import{_ as t}from"./zoom.50f0dc7b.js";import{_ as a,o,c as s,a as e,b as i,d as l,r}from"./app.0e1565ce.js";const c={},d=e("h3",{id:"how-can-i-tell-if-subpixel-antialiasing-is-working",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#how-can-i-tell-if-subpixel-antialiasing-is-working","aria-hidden":"true"},"#"),i(" How can I tell if subpixel antialiasing is working?")],-1),h=e("p",null,"If you take a screenshot and blow it up you'll see something like this:",-1),_=e("p",null,[e("img",{src:t,alt:"Subpixel antialiasing enlarged"})],-1),p=e("p",null,"The text of lines 34-36 are subpixel antialiased. Line 37 is not. You can tell it is subpixel antialiased because one side of the characters will be shifted red and the other side will be shifted blue. The direction of the shift is dependent on the monitor being used.",-1),u={href:"https://en.wikipedia.org/wiki/Subpixel_rendering",target:"_blank",rel:"noopener noreferrer"};function f(b,g){const n=r("ExternalLinkIcon");return o(),s("div",null,[d,h,_,p,e("p",null,[i("You can find more information on "),e("a",u,[i("subpixel rendering on Wikipedia"),l(n)]),i(".")])])}const k=a(c,[["render",f],["__file","how-can-i-tell-if-subpixel-antialiasing-is-working.html.vue"]]);export{k as default}; diff --git a/assets/how-can-i-tell-if-subpixel-antialiasing-is-working.html.f1514fae.js b/assets/how-can-i-tell-if-subpixel-antialiasing-is-working.html.f1514fae.js new file mode 100644 index 0000000000..29e1d32558 --- /dev/null +++ b/assets/how-can-i-tell-if-subpixel-antialiasing-is-working.html.f1514fae.js @@ -0,0 +1 @@ +const i=JSON.parse('{"key":"v-71e09326","path":"/docs/atom-archive/faq/sections/how-can-i-tell-if-subpixel-antialiasing-is-working.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"How can I tell if subpixel antialiasing is working?","slug":"how-can-i-tell-if-subpixel-antialiasing-is-working","link":"#how-can-i-tell-if-subpixel-antialiasing-is-working","children":[]}],"git":{"updatedTime":1667690985000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":2},{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":0.32,"words":95},"filePathRelative":"docs/atom-archive/faq/sections/how-can-i-tell-if-subpixel-antialiasing-is-working.md"}');export{i as data}; diff --git a/assets/how-do-i-accept-input-from-my-program-or-script-when-using-the-script-package.html.cc97b768.js b/assets/how-do-i-accept-input-from-my-program-or-script-when-using-the-script-package.html.cc97b768.js new file mode 100644 index 0000000000..2d338ca4b4 --- /dev/null +++ b/assets/how-do-i-accept-input-from-my-program-or-script-when-using-the-script-package.html.cc97b768.js @@ -0,0 +1 @@ +import{_ as a,o as n,c,a as t,b as e,d as o,r as s}from"./app.0e1565ce.js";const i={},p=t("h3",{id:"how-do-i-accept-input-from-my-program-or-script-when-using-the-script-package",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#how-do-i-accept-input-from-my-program-or-script-when-using-the-script-package","aria-hidden":"true"},"#"),e(" How do I accept input from my program or script when using the script package?")],-1),h={href:"https://atom.io/packages/script",target:"_blank",rel:"noopener noreferrer"},m={href:"https://atom.io/packages/search?q=terminal",target:"_blank",rel:"noopener noreferrer"},l={href:"https://github.com/rgbkrk/atom-script/issues/743",target:"_blank",rel:"noopener noreferrer"};function g(u,d){const r=s("ExternalLinkIcon");return n(),c("div",null,[p,t("p",null,[e("The "),t("a",h,[e("script package"),o(r)]),e(" doesn't support accepting input from the user in the scripts it runs. The option with the best chance of success is to run the script or program from the terminal that comes with your operating system. If that isn't something you want to do, you could try one of the many "),t("a",m,[e("terminal packages"),o(r)]),e(" that are available.")]),t("p",null,[e("See "),t("a",l,[e("rgbkrk/atom-script#743"),o(r)]),e(" for details.")])])}const f=a(i,[["render",g],["__file","how-do-i-accept-input-from-my-program-or-script-when-using-the-script-package.html.vue"]]);export{f as default}; diff --git a/assets/how-do-i-accept-input-from-my-program-or-script-when-using-the-script-package.html.eea6eb04.js b/assets/how-do-i-accept-input-from-my-program-or-script-when-using-the-script-package.html.eea6eb04.js new file mode 100644 index 0000000000..41473ca393 --- /dev/null +++ b/assets/how-do-i-accept-input-from-my-program-or-script-when-using-the-script-package.html.eea6eb04.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-83ab2196","path":"/docs/atom-archive/faq/sections/how-do-i-accept-input-from-my-program-or-script-when-using-the-script-package.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"How do I accept input from my program or script when using the script package?","slug":"how-do-i-accept-input-from-my-program-or-script-when-using-the-script-package","link":"#how-do-i-accept-input-from-my-program-or-script-when-using-the-script-package","children":[]}],"git":{"updatedTime":1667690985000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1},{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":0.33,"words":100},"filePathRelative":"docs/atom-archive/faq/sections/how-do-i-accept-input-from-my-program-or-script-when-using-the-script-package.md"}');export{e as data}; diff --git a/assets/how-do-i-build-or-execute-code-i-ve-written-in-atom.html.7293d923.js b/assets/how-do-i-build-or-execute-code-i-ve-written-in-atom.html.7293d923.js new file mode 100644 index 0000000000..b838f3ae6e --- /dev/null +++ b/assets/how-do-i-build-or-execute-code-i-ve-written-in-atom.html.7293d923.js @@ -0,0 +1 @@ +import{_ as r,o as a,c as i,a as e,b as o,d as n,r as l}from"./app.0e1565ce.js";const s={},c=e("h3",{id:"how-do-i-build-or-execute-code-i-ve-written-in-atom",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#how-do-i-build-or-execute-code-i-ve-written-in-atom","aria-hidden":"true"},"#"),o(" How do I build or execute code I've written in Atom?")],-1),u={href:"https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop",target:"_blank",rel:"noopener noreferrer"},d=e("ol",null,[e("li",null,"Launch Atom"),e("li",null,[o("Select the menu "),e("code",null,"View > Developer > Toggle Developer Tools")]),e("li",null,'Click the "Console" tab')],-1),h=e("p",null,"If you're looking for a JavaScript execution environment beyond a REPL, Atom doesn't come with anything built-in for that purpose.",-1),p=e("p",null,"If you want to build code or execute scripts from within Atom there are a number of packages available including:",-1),_={href:"https://atom.io/packages/build",target:"_blank",rel:"noopener noreferrer"},m={href:"https://atom.io/packages/script",target:"_blank",rel:"noopener noreferrer"},f=e("hr",null,null,-1),b=e("p",null,"Resources on getting started with languages that are commonly asked about:",-1),g={href:"https://www.python.org/about/gettingstarted/",target:"_blank",rel:"noopener noreferrer"};function v(w,k){const t=l("ExternalLinkIcon");return a(),i("div",null,[c,e("p",null,[o("Atom doesn't have built-in support for building any type of code nor does it have built-in support for executing any kind of code other than JavaScript. Atom has a JavaScript interactive command-line (also known as a "),e("a",u,[o("REPL"),n(t)]),o(") available through the Developer Tools. You can access the JavaScript REPL by using the following steps:")]),d,h,p,e("ul",null,[e("li",null,[e("a",_,[o("build"),n(t)])]),e("li",null,[e("a",m,[o("script"),n(t)])])]),f,b,e("ul",null,[e("li",null,[e("a",g,[o("Python"),n(t)])])])])}const y=r(s,[["render",v],["__file","how-do-i-build-or-execute-code-i-ve-written-in-atom.html.vue"]]);export{y as default}; diff --git a/assets/how-do-i-build-or-execute-code-i-ve-written-in-atom.html.7afea0b7.js b/assets/how-do-i-build-or-execute-code-i-ve-written-in-atom.html.7afea0b7.js new file mode 100644 index 0000000000..e1e9b6829c --- /dev/null +++ b/assets/how-do-i-build-or-execute-code-i-ve-written-in-atom.html.7afea0b7.js @@ -0,0 +1 @@ +const e=JSON.parse(`{"key":"v-58dbd8e4","path":"/docs/atom-archive/faq/sections/how-do-i-build-or-execute-code-i-ve-written-in-atom.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"How do I build or execute code I've written in Atom?","slug":"how-do-i-build-or-execute-code-i-ve-written-in-atom","link":"#how-do-i-build-or-execute-code-i-ve-written-in-atom","children":[]}],"git":{"updatedTime":1667690985000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1},{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":0.55,"words":165},"filePathRelative":"docs/atom-archive/faq/sections/how-do-i-build-or-execute-code-i-ve-written-in-atom.md"}`);export{e as data}; diff --git a/assets/how-do-i-make-atom-recognize-a-file-with-extension-x-as-language-y.html.45ca3345.js b/assets/how-do-i-make-atom-recognize-a-file-with-extension-x-as-language-y.html.45ca3345.js new file mode 100644 index 0000000000..7c94320836 --- /dev/null +++ b/assets/how-do-i-make-atom-recognize-a-file-with-extension-x-as-language-y.html.45ca3345.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-69d7b7aa","path":"/docs/atom-archive/faq/sections/how-do-i-make-atom-recognize-a-file-with-extension-x-as-language-y.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"How do I make Atom recognize a file with extension X as language Y?","slug":"how-do-i-make-atom-recognize-a-file-with-extension-x-as-language-y","link":"#how-do-i-make-atom-recognize-a-file-with-extension-x-as-language-y","children":[]}],"git":{"updatedTime":1667690985000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1},{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":0.31,"words":94},"filePathRelative":"docs/atom-archive/faq/sections/how-do-i-make-atom-recognize-a-file-with-extension-x-as-language-y.md"}');export{e as data}; diff --git a/assets/how-do-i-make-atom-recognize-a-file-with-extension-x-as-language-y.html.cf5f66f2.js b/assets/how-do-i-make-atom-recognize-a-file-with-extension-x-as-language-y.html.cf5f66f2.js new file mode 100644 index 0000000000..72e3a63620 --- /dev/null +++ b/assets/how-do-i-make-atom-recognize-a-file-with-extension-x-as-language-y.html.cf5f66f2.js @@ -0,0 +1,10 @@ +import{_ as s,o,c as t,a as n,b as e,d as i,f as c,r}from"./app.0e1565ce.js";const l={},p=c(`

    How do I make Atom recognize a file with extension X as language Y?

    Atom includes a feature called "custom file types" which you can use by adding some entries into your config.cson that look like this:

    core:
    +  customFileTypes:
    +    'source.ruby': [
    +      'Cheffile'
    +      'this-is-also-ruby'
    +    ]
    +    'source.cpp': [
    +      'h'
    +    ]
    +
    `,3),d=n("code",null,"source.ruby",-1),u={href:"http://flight-manual.atom.io/using-atom/sections/basic-customization/#finding-a-languages-scope-name",target:"_blank",rel:"noopener noreferrer"};function m(h,g){const a=r("ExternalLinkIcon");return o(),t("div",null,[p,n("p",null,[e("The key (for example "),d,e(" in the above snippet) is the language's "),n("a",u,[e("scope name"),i(a)]),e(". The value is an array of file extensions, without the period, to match to that scope name.")])])}const k=s(l,[["render",m],["__file","how-do-i-make-atom-recognize-a-file-with-extension-x-as-language-y.html.vue"]]);export{k as default}; diff --git a/assets/how-do-i-make-the-welcome-screen-stop-showing-up.html.2f9c8a6b.js b/assets/how-do-i-make-the-welcome-screen-stop-showing-up.html.2f9c8a6b.js new file mode 100644 index 0000000000..13e4d22959 --- /dev/null +++ b/assets/how-do-i-make-the-welcome-screen-stop-showing-up.html.2f9c8a6b.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-6d023415","path":"/docs/atom-archive/faq/sections/how-do-i-make-the-welcome-screen-stop-showing-up.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"How do I make the Welcome screen stop showing up?","slug":"how-do-i-make-the-welcome-screen-stop-showing-up","link":"#how-do-i-make-the-welcome-screen-stop-showing-up","children":[]}],"git":{"updatedTime":1667690985000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":2},{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":0.16,"words":48},"filePathRelative":"docs/atom-archive/faq/sections/how-do-i-make-the-welcome-screen-stop-showing-up.md"}');export{e as data}; diff --git a/assets/how-do-i-make-the-welcome-screen-stop-showing-up.html.ff75e4bd.js b/assets/how-do-i-make-the-welcome-screen-stop-showing-up.html.ff75e4bd.js new file mode 100644 index 0000000000..af4732354e --- /dev/null +++ b/assets/how-do-i-make-the-welcome-screen-stop-showing-up.html.ff75e4bd.js @@ -0,0 +1 @@ +import{_ as o}from"./welcome-screen-checkbox.604a29ce.js";import{_ as t,o as s,c,a as e,b as n}from"./app.0e1565ce.js";const h={},a=e("h3",{id:"how-do-i-make-the-welcome-screen-stop-showing-up",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#how-do-i-make-the-welcome-screen-stop-showing-up","aria-hidden":"true"},"#"),n(" How do I make the Welcome screen stop showing up?")],-1),i=e("p",null,"You can make the Welcome screen stop showing up by unchecking this box in the welcome screen itself:",-1),r=e("p",null,[e("img",{src:o,alt:"Box to uncheck to make the Welcome screen not show next time Atom is launched"})],-1),m=[a,i,r];function l(_,d){return s(),c("div",null,m)}const w=t(h,[["render",l],["__file","how-do-i-make-the-welcome-screen-stop-showing-up.html.vue"]]);export{w as default}; diff --git a/assets/how-do-i-preview-web-page-changes-automatically.html.05d87b35.js b/assets/how-do-i-preview-web-page-changes-automatically.html.05d87b35.js new file mode 100644 index 0000000000..8107dbf0ea --- /dev/null +++ b/assets/how-do-i-preview-web-page-changes-automatically.html.05d87b35.js @@ -0,0 +1 @@ +import{_ as t,o as n,c as l,a as e,b as a,d as r,r as s}from"./app.0e1565ce.js";const i={},c=e("h3",{id:"how-do-i-preview-web-page-changes-automatically",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#how-do-i-preview-web-page-changes-automatically","aria-hidden":"true"},"#"),a(" How do I preview web page changes automatically?")],-1),p=e("p",null,"There are a couple different approaches, for example:",-1),h={href:"https://atom.io/packages/browser-plus",target:"_blank",rel:"noopener noreferrer"},_={href:"https://atom.io/packages/livereload",target:"_blank",rel:"noopener noreferrer"},u={href:"https://atom.io/packages",target:"_blank",rel:"noopener noreferrer"};function d(m,f){const o=s("ExternalLinkIcon");return n(),l("div",null,[c,p,e("ul",null,[e("li",null,[e("a",h,[a("browser-plus"),r(o)]),a(" gives a reasonably full browser implementation within Atom")]),e("li",null,[e("a",_,[a("livereload"),r(o)]),a(" gives you a preview in any browser, but requires you to save the file first.")])]),e("p",null,[a("Other packages may be available now, you can search for Atom packages on the "),e("a",u,[a("packages site"),r(o)]),a(".")])])}const w=t(i,[["render",d],["__file","how-do-i-preview-web-page-changes-automatically.html.vue"]]);export{w as default}; diff --git a/assets/how-do-i-preview-web-page-changes-automatically.html.7240862c.js b/assets/how-do-i-preview-web-page-changes-automatically.html.7240862c.js new file mode 100644 index 0000000000..8d9c1b21e2 --- /dev/null +++ b/assets/how-do-i-preview-web-page-changes-automatically.html.7240862c.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-58d2c851","path":"/docs/atom-archive/faq/sections/how-do-i-preview-web-page-changes-automatically.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"How do I preview web page changes automatically?","slug":"how-do-i-preview-web-page-changes-automatically","link":"#how-do-i-preview-web-page-changes-automatically","children":[]}],"git":{"updatedTime":1667690985000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1},{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":0.24,"words":71},"filePathRelative":"docs/atom-archive/faq/sections/how-do-i-preview-web-page-changes-automatically.md"}');export{e as data}; diff --git a/assets/how-do-i-turn-on-line-wrap.html.0f348516.js b/assets/how-do-i-turn-on-line-wrap.html.0f348516.js new file mode 100644 index 0000000000..d5dda04789 --- /dev/null +++ b/assets/how-do-i-turn-on-line-wrap.html.0f348516.js @@ -0,0 +1 @@ +import{_ as o,o as n,c as a,a as t,b as e}from"./app.0e1565ce.js";const i={},l=t("h3",{id:"how-do-i-turn-on-line-wrap",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#how-do-i-turn-on-line-wrap","aria-hidden":"true"},"#"),e(" How do I turn on line wrap?")],-1),r=t("ol",null,[t("li",null,[e("Open the Settings View using "),t("kbd",null,"Cmd+,"),e(" on macOS or "),t("kbd",null,"Ctrl+,"),e(" on other platforms")]),t("li",null,"Click the \u201CEditor\u201D tab on the left of the settings view"),t("li",null,"Put a check in the \u201CSoft Wrap\u201D setting")],-1),s=t("p",null,"For more details about soft wrap, see: https://flight-manual.atom.io/getting-started/sections/atom-basics/#soft-wrap.",-1),c=[l,r,s];function d(h,u){return n(),a("div",null,c)}const p=o(i,[["render",d],["__file","how-do-i-turn-on-line-wrap.html.vue"]]);export{p as default}; diff --git a/assets/how-do-i-turn-on-line-wrap.html.743574d9.js b/assets/how-do-i-turn-on-line-wrap.html.743574d9.js new file mode 100644 index 0000000000..4343602c89 --- /dev/null +++ b/assets/how-do-i-turn-on-line-wrap.html.743574d9.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-446213fa","path":"/docs/atom-archive/faq/sections/how-do-i-turn-on-line-wrap.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"How do I turn on line wrap?","slug":"how-do-i-turn-on-line-wrap","link":"#how-do-i-turn-on-line-wrap","children":[]}],"git":{"updatedTime":1667690985000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1},{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":0.22,"words":65},"filePathRelative":"docs/atom-archive/faq/sections/how-do-i-turn-on-line-wrap.md"}');export{e as data}; diff --git a/assets/how-do-i-uninstall-atom-on-macos.html.2069a8eb.js b/assets/how-do-i-uninstall-atom-on-macos.html.2069a8eb.js new file mode 100644 index 0000000000..6fab435d53 --- /dev/null +++ b/assets/how-do-i-uninstall-atom-on-macos.html.2069a8eb.js @@ -0,0 +1,11 @@ +import{_ as n,o as i,c as a,f as o}from"./app.0e1565ce.js";const e={},r=o(`

    How do I uninstall Atom on macOS?

    To uninstall Atom on macOS, run the following commands from the command line:

    rm -rf ~/.atom
    +rm -rf /usr/local/bin/atom
    +rm -rf /usr/local/bin/apm
    +rm -rf /Applications/Atom.app
    +rm -rf ~/Library/Preferences/com.github.atom.plist
    +rm -rf "~/Library/Application Support/com.github.atom.ShipIt"
    +rm -rf "~/Library/Application Support/Atom"
    +rm -rf "~/Library/Saved Application State/com.github.atom.savedState"
    +rm -rf ~/Library/Caches/com.github.atom
    +rm -rf ~/Library/Caches/Atom
    +
    `,3),t=[r];function m(l,s){return i(),a("div",null,t)}const d=n(e,[["render",m],["__file","how-do-i-uninstall-atom-on-macos.html.vue"]]);export{d as default}; diff --git a/assets/how-do-i-uninstall-atom-on-macos.html.8c93daae.js b/assets/how-do-i-uninstall-atom-on-macos.html.8c93daae.js new file mode 100644 index 0000000000..43eb7b2c7b --- /dev/null +++ b/assets/how-do-i-uninstall-atom-on-macos.html.8c93daae.js @@ -0,0 +1 @@ +const o=JSON.parse('{"key":"v-671ef772","path":"/docs/atom-archive/faq/sections/how-do-i-uninstall-atom-on-macos.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"How do I uninstall Atom on macOS?","slug":"how-do-i-uninstall-atom-on-macos","link":"#how-do-i-uninstall-atom-on-macos","children":[]}],"git":{"updatedTime":1667690985000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1},{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":0.25,"words":75},"filePathRelative":"docs/atom-archive/faq/sections/how-do-i-uninstall-atom-on-macos.md"}');export{o as data}; diff --git a/assets/how-do-i-use-a-newline-in-the-result-of-find-and-replace.html.0763323b.js b/assets/how-do-i-use-a-newline-in-the-result-of-find-and-replace.html.0763323b.js new file mode 100644 index 0000000000..91670dde06 --- /dev/null +++ b/assets/how-do-i-use-a-newline-in-the-result-of-find-and-replace.html.0763323b.js @@ -0,0 +1,5 @@ +import{_ as e}from"./find-and-replace-newline.08e81a06.js";import{_ as n,o as t,c as i,f as a}from"./app.0e1565ce.js";const l={},d=a(`

    How do I use a newline in the result of find and replace?

    To use a newline in the result of find and replace, enable the Use Regex option and use "\\n" in your replacement text. For example, given this text:

    hello, world, goodbye
    +

    If you'd like to replace the ", " with a newline so you end up with this text:

    hello
    +world
    +goodbye
    +

    In the find and replace settings, enable Use Regex, enter ", " as the find text, and enter "\\n" as the replace text:

    Find and replace with newline replace

    Then click Find All and finally, click Replace All.

    ',8),o=[d];function s(c,r){return t(),i("div",null,o)}const p=n(l,[["render",s],["__file","how-do-i-use-a-newline-in-the-result-of-find-and-replace.html.vue"]]);export{p as default}; diff --git a/assets/how-do-i-use-a-newline-in-the-result-of-find-and-replace.html.faceab9d.js b/assets/how-do-i-use-a-newline-in-the-result-of-find-and-replace.html.faceab9d.js new file mode 100644 index 0000000000..99b72a1063 --- /dev/null +++ b/assets/how-do-i-use-a-newline-in-the-result-of-find-and-replace.html.faceab9d.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-6e77009f","path":"/docs/atom-archive/faq/sections/how-do-i-use-a-newline-in-the-result-of-find-and-replace.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"How do I use a newline in the result of find and replace?","slug":"how-do-i-use-a-newline-in-the-result-of-find-and-replace","link":"#how-do-i-use-a-newline-in-the-result-of-find-and-replace","children":[]}],"git":{"updatedTime":1667690985000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":2},{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":0.37,"words":112},"filePathRelative":"docs/atom-archive/faq/sections/how-do-i-use-a-newline-in-the-result-of-find-and-replace.md"}');export{e as data}; diff --git a/assets/i-am-unable-to-update-to-the-latest-version-of-atom-on-macos-how-do-i-fix-this.html.2508a414.js b/assets/i-am-unable-to-update-to-the-latest-version-of-atom-on-macos-how-do-i-fix-this.html.2508a414.js new file mode 100644 index 0000000000..e17664140f --- /dev/null +++ b/assets/i-am-unable-to-update-to-the-latest-version-of-atom-on-macos-how-do-i-fix-this.html.2508a414.js @@ -0,0 +1 @@ +import{_ as o}from"./update-atom-macos.07b32cce.js";import{_ as e,o as t,c as i,f as a}from"./app.0e1565ce.js";const r={},s=a('

    I am unable to update to the latest version of Atom on macOS. How do I fix this?

    Atom shows there is a new version available but the version fails to install. You might have an error message showing a permissions error for example:

    Updating Atom on macOS

    or it will say downloading but forever loops without restarting or updating.

    You need to fix one or more of the following directories:

    • /Applications/Atom.app/
    • ~/Library/Caches/com.github.atom.ShipIt
    • ~/Library/Application Support/com.github.atom.ShipIt

    Do the following:

    1. Completely exit Atom
    2. Open a terminal
    3. Execute: whoami
    4. Write down the result of the above command, this is your user name

    And then execute these steps for each directory listed above in order:

    1. Execute: stat -f "%Su" [directory]
    2. It should output either your username or root
    3. If it says root then execute: sudo chown -R $(whoami) [directory]

    Once you've done the above for both directories, start Atom normally and attempt to update \u{1F44D}

    ',11),l=[s];function n(c,d){return t(),i("div",null,l)}const p=e(r,[["render",n],["__file","i-am-unable-to-update-to-the-latest-version-of-atom-on-macos-how-do-i-fix-this.html.vue"]]);export{p as default}; diff --git a/assets/i-am-unable-to-update-to-the-latest-version-of-atom-on-macos-how-do-i-fix-this.html.7f10693d.js b/assets/i-am-unable-to-update-to-the-latest-version-of-atom-on-macos-how-do-i-fix-this.html.7f10693d.js new file mode 100644 index 0000000000..0a0a381676 --- /dev/null +++ b/assets/i-am-unable-to-update-to-the-latest-version-of-atom-on-macos-how-do-i-fix-this.html.7f10693d.js @@ -0,0 +1 @@ +const t=JSON.parse('{"key":"v-50328086","path":"/docs/atom-archive/faq/sections/i-am-unable-to-update-to-the-latest-version-of-atom-on-macos-how-do-i-fix-this.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"I am unable to update to the latest version of Atom on macOS. How do I fix this?","slug":"i-am-unable-to-update-to-the-latest-version-of-atom-on-macos-how-do-i-fix-this","link":"#i-am-unable-to-update-to-the-latest-version-of-atom-on-macos-how-do-i-fix-this","children":[]}],"git":{"updatedTime":1667690985000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":2},{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":0.56,"words":169},"filePathRelative":"docs/atom-archive/faq/sections/i-am-unable-to-update-to-the-latest-version-of-atom-on-macos-how-do-i-fix-this.md"}');export{t as data}; diff --git a/assets/i-have-a-question-about-a-specific-atom-community-package-where-is-the-best-place-to-ask-it.html.2fe045ab.js b/assets/i-have-a-question-about-a-specific-atom-community-package-where-is-the-best-place-to-ask-it.html.2fe045ab.js new file mode 100644 index 0000000000..81a92a0f6f --- /dev/null +++ b/assets/i-have-a-question-about-a-specific-atom-community-package-where-is-the-best-place-to-ask-it.html.2fe045ab.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-52c5c70a","path":"/docs/atom-archive/faq/sections/i-have-a-question-about-a-specific-atom-community-package-where-is-the-best-place-to-ask-it.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"I have a question about a specific Atom community package. Where is the best place to ask it?","slug":"i-have-a-question-about-a-specific-atom-community-package-where-is-the-best-place-to-ask-it","link":"#i-have-a-question-about-a-specific-atom-community-package-where-is-the-best-place-to-ask-it","children":[]}],"git":{"updatedTime":1667690985000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":2},{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":0.4,"words":119},"filePathRelative":"docs/atom-archive/faq/sections/i-have-a-question-about-a-specific-atom-community-package-where-is-the-best-place-to-ask-it.md"}');export{e as data}; diff --git a/assets/i-have-a-question-about-a-specific-atom-community-package-where-is-the-best-place-to-ask-it.html.9271651f.js b/assets/i-have-a-question-about-a-specific-atom-community-package-where-is-the-best-place-to-ask-it.html.9271651f.js new file mode 100644 index 0000000000..1d9919012d --- /dev/null +++ b/assets/i-have-a-question-about-a-specific-atom-community-package-where-is-the-best-place-to-ask-it.html.9271651f.js @@ -0,0 +1 @@ +import{_ as o}from"./package-issue-link.51bb6d85.js";import{_ as s,o as i,c as n,a as e,b as t,d as c,r}from"./app.0e1565ce.js";const l={},h=e("h3",{id:"i-have-a-question-about-a-specific-atom-community-package-where-is-the-best-place-to-ask-it",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#i-have-a-question-about-a-specific-atom-community-package-where-is-the-best-place-to-ask-it","aria-hidden":"true"},"#"),t(" I have a question about a specific Atom community package. Where is the best place to ask it?")],-1),m=e("p",null,"The best place to get a question answered quickly is probably the Issues list for that specific package. You can find the Issues list for a package by going to that package's page on https://atom.io and clicking the Bugs button:",-1),u=e("p",null,[e("img",{src:o,alt:"Bugs button link"})],-1),p={href:"https://github.com/atom/atom/discussions",target:"_blank",rel:"noopener noreferrer"};function k(_,f){const a=r("ExternalLinkIcon");return i(),n("div",null,[h,m,u,e("p",null,[t("And you can always ask Atom-related questions in the "),e("a",p,[t("official Atom message board"),c(a)]),t(". Someone here may know the answer! It's just with over 3,500 packages (as of early February 2016), the forum members may not know all answers for all packages \u{1F600}")])])}const g=s(l,[["render",k],["__file","i-have-a-question-about-a-specific-atom-community-package-where-is-the-best-place-to-ask-it.html.vue"]]);export{g as default}; diff --git a/assets/i-m-getting-an-error-about-a-self-signed-certificate-what-do-i-do.html.18a6988d.js b/assets/i-m-getting-an-error-about-a-self-signed-certificate-what-do-i-do.html.18a6988d.js new file mode 100644 index 0000000000..a232baa112 --- /dev/null +++ b/assets/i-m-getting-an-error-about-a-self-signed-certificate-what-do-i-do.html.18a6988d.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-779d7601","path":"/docs/atom-archive/faq/sections/i-m-getting-an-error-about-a-self-signed-certificate-what-do-i-do.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"I\u2019m getting an error about a \u201Cself-signed certificate\u201D. What do I do?","slug":"i-m-getting-an-error-about-a-self-signed-certificate-what-do-i-do","link":"#i-m-getting-an-error-about-a-self-signed-certificate-what-do-i-do","children":[]}],"git":{"updatedTime":1667690985000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":2},{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":0.53,"words":160},"filePathRelative":"docs/atom-archive/faq/sections/i-m-getting-an-error-about-a-self-signed-certificate-what-do-i-do.md"}');export{e as data}; diff --git a/assets/i-m-getting-an-error-about-a-self-signed-certificate-what-do-i-do.html.689f5508.js b/assets/i-m-getting-an-error-about-a-self-signed-certificate-what-do-i-do.html.689f5508.js new file mode 100644 index 0000000000..6749bc1f7d --- /dev/null +++ b/assets/i-m-getting-an-error-about-a-self-signed-certificate-what-do-i-do.html.689f5508.js @@ -0,0 +1,2 @@ +import{_ as e,o as t,c as o,f as n}from"./app.0e1565ce.js";const i={},a=n(`

    I\u2019m getting an error about a \u201Cself-signed certificate\u201D. What do I do?

    This means that there is a proxy between you and our servers where someone (typically your employer) has installed a "self-signed" security certificate in the proxy. A self-signed certificate is one that isn't trusted by anyone but the person who created the certificate. Most security certificates are backed by known, trusted and certified companies. So Atom is warning you that your connection to our servers can be snooped and even hacked by whoever created the self-signed certificate. Since it is self-signed, Atom has no way of knowing who that is.

    If you decide that unsecured connections to our servers is acceptable to you, you can use the following instructions.

    DANGER

    \u{1F6A8} Danger: If you decide that unsecured connections to our servers is acceptable to you, you can use the following command:

    apm config set strict-ssl false
    +
    `,4),s=[a];function r(c,d){return t(),o("div",null,s)}const u=e(i,[["render",r],["__file","i-m-getting-an-error-about-a-self-signed-certificate-what-do-i-do.html.vue"]]);export{u as default}; diff --git a/assets/i-m-having-a-problem-with-julia-what-do-i-do.html.acdafc71.js b/assets/i-m-having-a-problem-with-julia-what-do-i-do.html.acdafc71.js new file mode 100644 index 0000000000..55460e5143 --- /dev/null +++ b/assets/i-m-having-a-problem-with-julia-what-do-i-do.html.acdafc71.js @@ -0,0 +1 @@ +const a=JSON.parse('{"key":"v-6bf81d3a","path":"/docs/atom-archive/faq/sections/i-m-having-a-problem-with-julia-what-do-i-do.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"I\u2019m having a problem with Julia! What do I do?","slug":"i-m-having-a-problem-with-julia-what-do-i-do","link":"#i-m-having-a-problem-with-julia-what-do-i-do","children":[]}],"git":{"updatedTime":1667690985000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1},{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":0.17,"words":51},"filePathRelative":"docs/atom-archive/faq/sections/i-m-having-a-problem-with-julia-what-do-i-do.md"}');export{a as data}; diff --git a/assets/i-m-having-a-problem-with-julia-what-do-i-do.html.e48effb5.js b/assets/i-m-having-a-problem-with-julia-what-do-i-do.html.e48effb5.js new file mode 100644 index 0000000000..02f0b7b9bc --- /dev/null +++ b/assets/i-m-having-a-problem-with-julia-what-do-i-do.html.e48effb5.js @@ -0,0 +1 @@ +import{_ as n,o as r,c as i,a as o,b as e,d as t,r as l}from"./app.0e1565ce.js";const h={},s=o("h3",{id:"i-m-having-a-problem-with-julia-what-do-i-do",tabindex:"-1"},[o("a",{class:"header-anchor",href:"#i-m-having-a-problem-with-julia-what-do-i-do","aria-hidden":"true"},"#"),e(" I\u2019m having a problem with Julia! What do I do?")],-1),c={href:"http://junolab.org/",target:"_blank",rel:"noopener noreferrer"},d={href:"https://discourse.julialang.org/c/tools/juno",target:"_blank",rel:"noopener noreferrer"};function u(m,_){const a=l("ExternalLinkIcon");return r(),i("div",null,[s,o("p",null,[o("a",c,[e("Juno"),t(a)]),e(" is a development environment built on top of Atom but has enough separate customizations that they have their own "),o("a",d,[e("message board"),t(a)]),e(". You will probably have better luck asking your question there.")])])}const b=n(h,[["render",u],["__file","i-m-having-a-problem-with-julia-what-do-i-do.html.vue"]]);export{b as default}; diff --git a/assets/i-m-having-a-problem-with-platformio-what-do-i-do.html.4bea6f02.js b/assets/i-m-having-a-problem-with-platformio-what-do-i-do.html.4bea6f02.js new file mode 100644 index 0000000000..23e47a3cb8 --- /dev/null +++ b/assets/i-m-having-a-problem-with-platformio-what-do-i-do.html.4bea6f02.js @@ -0,0 +1 @@ +const a=JSON.parse('{"key":"v-103f9060","path":"/docs/atom-archive/faq/sections/i-m-having-a-problem-with-platformio-what-do-i-do.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"I\u2019m having a problem with PlatformIO! What do I do?","slug":"i-m-having-a-problem-with-platformio-what-do-i-do","link":"#i-m-having-a-problem-with-platformio-what-do-i-do","children":[]}],"git":{"updatedTime":1667690985000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1},{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":0.19,"words":56},"filePathRelative":"docs/atom-archive/faq/sections/i-m-having-a-problem-with-platformio-what-do-i-do.md"}');export{a as data}; diff --git a/assets/i-m-having-a-problem-with-platformio-what-do-i-do.html.6d67c3ad.js b/assets/i-m-having-a-problem-with-platformio-what-do-i-do.html.6d67c3ad.js new file mode 100644 index 0000000000..722d443da1 --- /dev/null +++ b/assets/i-m-having-a-problem-with-platformio-what-do-i-do.html.6d67c3ad.js @@ -0,0 +1 @@ +import{_ as r,o as n,c as i,a as o,b as e,d as a,r as h}from"./app.0e1565ce.js";const l={},s=o("h3",{id:"i-m-having-a-problem-with-platformio-what-do-i-do",tabindex:"-1"},[o("a",{class:"header-anchor",href:"#i-m-having-a-problem-with-platformio-what-do-i-do","aria-hidden":"true"},"#"),e(" I\u2019m having a problem with PlatformIO! What do I do?")],-1),m={href:"http://platformio.org/",target:"_blank",rel:"noopener noreferrer"},c={href:"https://community.platformio.org/",target:"_blank",rel:"noopener noreferrer"};function d(f,p){const t=h("ExternalLinkIcon");return n(),i("div",null,[s,o("p",null,[o("a",m,[e("PlatformIO"),a(t)]),e(" is a development environment built on top of Atom but has enough separate customizations that they have their own "),o("a",c,[e("message board"),a(t)]),e(". If your question has to do with PlatformIO specifically, you may have better luck getting your answer there.")])])}const u=r(l,[["render",d],["__file","i-m-having-a-problem-with-platformio-what-do-i-do.html.vue"]]);export{u as default}; diff --git a/assets/i-m-trying-to-change-my-syntax-colors-from-styles-less-but-it-isn-t-working.html.9615ee41.js b/assets/i-m-trying-to-change-my-syntax-colors-from-styles-less-but-it-isn-t-working.html.9615ee41.js new file mode 100644 index 0000000000..8436d1c623 --- /dev/null +++ b/assets/i-m-trying-to-change-my-syntax-colors-from-styles-less-but-it-isn-t-working.html.9615ee41.js @@ -0,0 +1 @@ +const t=JSON.parse('{"key":"v-f400a296","path":"/docs/atom-archive/faq/sections/i-m-trying-to-change-my-syntax-colors-from-styles-less-but-it-isn-t-working.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"I\u2019m trying to change my syntax colors from styles.less, but it isn\u2019t working!","slug":"i-m-trying-to-change-my-syntax-colors-from-styles-less-but-it-isn-t-working","link":"#i-m-trying-to-change-my-syntax-colors-from-styles-less-but-it-isn-t-working","children":[]}],"git":{"updatedTime":1667690985000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1},{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":0.21,"words":64},"filePathRelative":"docs/atom-archive/faq/sections/i-m-trying-to-change-my-syntax-colors-from-styles-less-but-it-isn-t-working.md"}');export{t as data}; diff --git a/assets/i-m-trying-to-change-my-syntax-colors-from-styles-less-but-it-isn-t-working.html.bc280728.js b/assets/i-m-trying-to-change-my-syntax-colors-from-styles-less-but-it-isn-t-working.html.bc280728.js new file mode 100644 index 0000000000..3472944b09 --- /dev/null +++ b/assets/i-m-trying-to-change-my-syntax-colors-from-styles-less-but-it-isn-t-working.html.bc280728.js @@ -0,0 +1,6 @@ +import{_ as s,o as n,c as e,f as t}from"./app.0e1565ce.js";const a={},o=t(`

    I\u2019m trying to change my syntax colors from styles.less, but it isn\u2019t working!

    The best way to tweak the syntax is to wrap your syntax style rules with atom-text-editor and then prepend every scope with syntax--. If you want your comments to be blue, for example, you would do the following:

    atom-text-editor {
    +	.syntax--comment {
    +		color: blue;
    +	}
    +}
    +
    `,3),i=[o];function l(c,r){return n(),e("div",null,i)}const u=s(a,[["render",l],["__file","i-m-trying-to-change-my-syntax-colors-from-styles-less-but-it-isn-t-working.html.vue"]]);export{u as default}; diff --git a/assets/i-m-using-an-international-keyboard-and-keys-that-use-altgr-or-ctrl-alt-aren-t-working.html.610e3ec4.js b/assets/i-m-using-an-international-keyboard-and-keys-that-use-altgr-or-ctrl-alt-aren-t-working.html.610e3ec4.js new file mode 100644 index 0000000000..612b1f89db --- /dev/null +++ b/assets/i-m-using-an-international-keyboard-and-keys-that-use-altgr-or-ctrl-alt-aren-t-working.html.610e3ec4.js @@ -0,0 +1 @@ +import{_ as r,o as a,c as o,a as t,b as e,d as l,r as i}from"./app.0e1565ce.js";const s={},d=t("h3",{id:"i-m-using-an-international-keyboard-and-keys-that-use-altgr-or-ctrl-alt-aren-t-working",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#i-m-using-an-international-keyboard-and-keys-that-use-altgr-or-ctrl-alt-aren-t-working","aria-hidden":"true"},"#"),e(" I\u2019m using an international keyboard and keys that use AltGr or Ctrl+Alt aren\u2019t working")],-1),c={href:"http://blog.atom.io/2016/10/17/the-wonderful-world-of-keyboards.html",target:"_blank",rel:"noopener noreferrer"};function h(f,k){const n=i("ExternalLinkIcon");return a(),o("div",null,[d,t("p",null,[e("As of Atom v1.12, a fix is available for this. See "),t("a",c,[e('the blog post "The Wonderful World of Keyboards"'),l(n)]),e(" for more information.")])])}const g=r(s,[["render",h],["__file","i-m-using-an-international-keyboard-and-keys-that-use-altgr-or-ctrl-alt-aren-t-working.html.vue"]]);export{g as default}; diff --git a/assets/i-m-using-an-international-keyboard-and-keys-that-use-altgr-or-ctrl-alt-aren-t-working.html.d12cc459.js b/assets/i-m-using-an-international-keyboard-and-keys-that-use-altgr-or-ctrl-alt-aren-t-working.html.d12cc459.js new file mode 100644 index 0000000000..1e6d3f56c3 --- /dev/null +++ b/assets/i-m-using-an-international-keyboard-and-keys-that-use-altgr-or-ctrl-alt-aren-t-working.html.d12cc459.js @@ -0,0 +1 @@ +const a=JSON.parse('{"key":"v-69a4dea8","path":"/docs/atom-archive/faq/sections/i-m-using-an-international-keyboard-and-keys-that-use-altgr-or-ctrl-alt-aren-t-working.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"I\u2019m using an international keyboard and keys that use AltGr or Ctrl+Alt aren\u2019t working","slug":"i-m-using-an-international-keyboard-and-keys-that-use-altgr-or-ctrl-alt-aren-t-working","link":"#i-m-using-an-international-keyboard-and-keys-that-use-altgr-or-ctrl-alt-aren-t-working","children":[]}],"git":{"updatedTime":1667690985000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1},{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":0.16,"words":49},"filePathRelative":"docs/atom-archive/faq/sections/i-m-using-an-international-keyboard-and-keys-that-use-altgr-or-ctrl-alt-aren-t-working.md"}');export{a as data}; diff --git a/assets/i18n.77f15fa4.png b/assets/i18n.77f15fa4.png new file mode 100644 index 0000000000..ee41605206 Binary files /dev/null and b/assets/i18n.77f15fa4.png differ diff --git a/assets/iconography.5f83320e.png b/assets/iconography.5f83320e.png new file mode 100644 index 0000000000..f149fb5782 Binary files /dev/null and b/assets/iconography.5f83320e.png differ diff --git a/assets/iconography.bf3cea92.js b/assets/iconography.bf3cea92.js new file mode 100644 index 0000000000..e95e4012eb --- /dev/null +++ b/assets/iconography.bf3cea92.js @@ -0,0 +1 @@ +const s="/assets/iconography.5f83320e.png";export{s as _}; diff --git a/assets/iconography.html.41f9df89.js b/assets/iconography.html.41f9df89.js new file mode 100644 index 0000000000..0fa2a6cbdf --- /dev/null +++ b/assets/iconography.html.41f9df89.js @@ -0,0 +1,2 @@ +import{_ as s}from"./iconography.bf3cea92.js";import{_ as n,o as i,c,a,b as e,d as o,e as r,f as l,r as d}from"./app.0e1565ce.js";const p={},h=a("h2",{id:"iconography",tabindex:"-1"},[a("a",{class:"header-anchor",href:"#iconography","aria-hidden":"true"},"#"),e(" Iconography")],-1),u={href:"https://github.com/github/octicons/tree/v4.4.0",target:"_blank",rel:"noopener noreferrer"},m=l('

    NOTE: Some older icons from version 2.1.2 are still kept for backwards compatibility.

    Overview

    In the Styleguide under the "Icons" section you'll find all the Octicons that are available.

    Octicons in the Styleguide

    Usage

    Octicons can be added with simple CSS classes in your markup. Prefix the icon names with icon icon-.

    As an example, to add a monitor icon (device-desktop), use the icon icon-device-desktop classes:

    <span class="icon icon-device-desktop"></span>
    +

    Size

    Octicons look best with a font-size of 16px. It's already used as the default, so you don't need to worry about it. In case you prefer a different icon size, try to use multiples of 16 (32px, 48px etc.) for the sharpest result. Sizes in between are ok too, but might look a bit blurry for icons with straight lines.

    Usability

    `,11),g={href:"https://atom.io/docs/api/latest/TooltipManager",target:"_blank",rel:"noopener noreferrer"},b=a("code",null,'title="label"',-1);function f(k,_){const t=d("ExternalLinkIcon");return i(),c("div",null,[h,a("p",null,[e("Pulsar comes bundled with the "),a("a",u,[e("Octicons 4.4.0"),o(t)]),e(" icon set. Use them to add icons to your packages.")]),m,a("p",null,[e("Although icons can make your UI visually appealing, when used without a text label, it can be hard to guess its meaning. In cases where space for a text label is insufficient, consider adding a "),a("a",g,[e("tooltip"),o(t)]),r("TODO: Needs updating once we have the API documented"),e(" that appears on hover. Or a more subtle "),b,e(" attribute would help as well.")])])}const x=n(p,[["render",f],["__file","iconography.html.vue"]]);export{x as default}; diff --git a/assets/iconography.html.cb0903f5.js b/assets/iconography.html.cb0903f5.js new file mode 100644 index 0000000000..d2eca3770a --- /dev/null +++ b/assets/iconography.html.cb0903f5.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-5746d296","path":"/docs/atom-archive/hacking-atom/sections/iconography.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"Iconography","slug":"iconography","link":"#iconography","children":[]}],"git":{"updatedTime":1664050274000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":3}]},"readingTime":{"minutes":0.81,"words":243},"filePathRelative":"docs/atom-archive/hacking-atom/sections/iconography.md"}');export{e as data}; diff --git a/assets/iconography.html.d80c4c08.js b/assets/iconography.html.d80c4c08.js new file mode 100644 index 0000000000..71f4c7f5b5 --- /dev/null +++ b/assets/iconography.html.d80c4c08.js @@ -0,0 +1,2 @@ +import{_ as i}from"./iconography.bf3cea92.js";import{_ as c,o as r,c as l,a as t,b as e,d as a,w as d,f as p,r as n}from"./app.0e1565ce.js";const h={},u=t("h3",{id:"iconography",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#iconography","aria-hidden":"true"},"#"),e(" Iconography")],-1),m={href:"https://github.com/github/octicons/tree/v4.4.0",target:"_blank",rel:"noopener noreferrer"},g=t("blockquote",null,[t("p",null,[e("NOTE: Some older icons from version "),t("code",null,"2.1.2"),e(" are still kept for backwards compatibility.")])],-1),_=t("h4",{id:"overview",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#overview","aria-hidden":"true"},"#"),e(" Overview")],-1),f=p('

    Octicons in the Styleguide

    Usage

    Octicons can be added with simple CSS classes in your markup. Prefix the icon names with icon icon-.

    As an example, to add a monitor icon (device-desktop), use the icon icon-device-desktop classes:

    <span class="icon icon-device-desktop"></span>
    +

    Size

    Octicons look best with a font-size of 16px. It's already used as the default, so you don't need to worry about it. In case you prefer a different icon size, try to use multiples of 16 (32px, 48px etc.) for the sharpest result. Sizes in between are ok too, but might look a bit blurry for icons with straight lines.

    Usability

    `,8),b={href:"https://atom.io/docs/api/latest/TooltipManager",target:"_blank",rel:"noopener noreferrer"},k=t("code",null,'title="label"',-1);function v(y,x){const o=n("ExternalLinkIcon"),s=n("RouterLink");return r(),l("div",null,[u,t("p",null,[e("Atom comes bundled with the "),t("a",m,[e("Octicons 4.4.0"),a(o)]),e(" icon set. Use them to add icons to your packages.")]),g,_,t("p",null,[e("In the "),a(s,{to:"/hacking-atom/sections/creating-a-theme/#atom-styleguide"},{default:d(()=>[e("Styleguide")]),_:1}),e(` under the "Icons" section you'll find all the Octicons that are available.`)]),f,t("p",null,[e("Although icons can make your UI visually appealing, when used without a text label, it can be hard to guess its meaning. In cases where space for a text label is insufficient, consider adding a "),t("a",b,[e("tooltip"),a(o)]),e(" that appears on hover. Or a more subtle "),k,e(" attribute would help as well.")])])}const O=c(h,[["render",v],["__file","iconography.html.vue"]]);export{O as default}; diff --git a/assets/iconography.html.f6f37c01.js b/assets/iconography.html.f6f37c01.js new file mode 100644 index 0000000000..c0133e1c60 --- /dev/null +++ b/assets/iconography.html.f6f37c01.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-544678d8","path":"/docs/launch-manual/sections/core-hacking/sections/iconography.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Iconography","slug":"iconography","link":"#iconography","children":[{"level":3,"title":"Overview","slug":"overview","link":"#overview","children":[]},{"level":3,"title":"Usage","slug":"usage","link":"#usage","children":[]},{"level":3,"title":"Size","slug":"size","link":"#size","children":[]},{"level":3,"title":"Usability","slug":"usability","link":"#usability","children":[]}]}],"git":{"updatedTime":1670466847000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":0.82,"words":246},"filePathRelative":"docs/launch-manual/sections/core-hacking/sections/iconography.md"}');export{e as data}; diff --git a/assets/incomplete-classpath-warning.html.cee3657e.js b/assets/incomplete-classpath-warning.html.cee3657e.js new file mode 100644 index 0000000000..ddd1337059 --- /dev/null +++ b/assets/incomplete-classpath-warning.html.cee3657e.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-3e52d25b","path":"/docs/packages/core/ide-java/incomplete-classpath-warning.html","title":"Incomplete Classpath Warning","lang":"en-us","frontmatter":{"lang":"en-us","title":"Incomplete Classpath Warning"},"excerpt":"","headers":[],"git":{"updatedTime":1664399092000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":2}]},"readingTime":{"minutes":0.33,"words":98},"filePathRelative":"docs/packages/core/ide-java/incomplete-classpath-warning.md"}');export{e as data}; diff --git a/assets/incomplete-classpath-warning.html.efd2128c.js b/assets/incomplete-classpath-warning.html.efd2128c.js new file mode 100644 index 0000000000..0ce24be3c5 --- /dev/null +++ b/assets/incomplete-classpath-warning.html.efd2128c.js @@ -0,0 +1 @@ +import{_ as t,o as a,c as s,a as e,b as n}from"./app.0e1565ce.js";const o={},i=e("h1",{id:"incomplete-classpath-warning",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#incomplete-classpath-warning","aria-hidden":"true"},"#"),n(" Incomplete Classpath Warning")],-1),l=e("div",{class:"custom-container warning"},[e("p",{class:"custom-container-title"},"Note"),e("p",null,"Please note that its possible this is outdated, as its original version was published by @'Damein Guard' on Feb 16, 2018.")],-1),c=e("p",null,"In order to properly analyze Java files a project definition must be found that indicates the packages, paths to search for classes etc.",-1),r=e("p",null,"You should open the folder that contains the pom.xml or build.gradle file if you want full diagnostics, errors, auto-completion etc.",-1),d=e("p",null,"If you don't need the full analysis you can ignore this warning and work with a subset of the available features.",-1),h=[i,l,c,r,d];function p(u,_){return a(),s("div",null,h)}const m=t(o,[["render",p],["__file","incomplete-classpath-warning.html.vue"]]);export{m as default}; diff --git a/assets/index.html.00cec056.js b/assets/index.html.00cec056.js new file mode 100644 index 0000000000..fe016d4581 --- /dev/null +++ b/assets/index.html.00cec056.js @@ -0,0 +1 @@ +import{_ as e,o as c,c as t}from"./app.0e1565ce.js";const n={};function _(o,r){return c(),t("div")}const a=e(n,[["render",_],["__file","index.html.vue"]]);export{a as default}; diff --git a/assets/index.html.01c707f9.js b/assets/index.html.01c707f9.js new file mode 100644 index 0000000000..168360a245 --- /dev/null +++ b/assets/index.html.01c707f9.js @@ -0,0 +1 @@ +const a=JSON.parse('{"key":"v-54365ad2","path":"/tag/socials/","title":"socials Tag","lang":"en-US","frontmatter":{"title":"socials Tag","blog":{"type":"category","name":"socials","key":"tag"},"layout":"Blog"},"excerpt":"","headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null}');export{a as data}; diff --git a/assets/index.html.05af89eb.js b/assets/index.html.05af89eb.js new file mode 100644 index 0000000000..e81e8c7101 --- /dev/null +++ b/assets/index.html.05af89eb.js @@ -0,0 +1,308 @@ +import{_ as d}from"./keybinding.964a4e0d.js";import{_ as k}from"./markup.30f112ce.js";import{_ as m,o as h,c as v,a as n,b as s,d as e,e as o,w as i,f as t,r as u}from"./app.0e1565ce.js";const g={},b=t(`

    Behind Pulsar

    Under Construction

    This document is under construction, please check back soon for updates. Please see our socials and feel free to ask for assistance or inquire as to the status of this document.

    Now that we've written a number of packages and themes, let's take minute to take a closer look at some of the ways that Pulsar works in greater depth. Here we'll go into more of a deep dive on individual internal APIs and systems of Pulsar, even looking at some Atom source to see how things are really getting done.

    Configuration API

    Reading Config Settings

    If you are writing a package that you want to make configurable, you'll need to read config settings via the atom.config global. You can read the current value of a namespaced config key with atom.config.get:

    // read a value with \`config.get\`
    +if (atom.config.get("editor.showInvisibles")) {
    +	this.showInvisibles();
    +}
    +

    Or you can subscribe via atom.config.observe to track changes from any view object.

    const {View} = require('space-pen')
    +
    +class MyView extends View {
    +  function attached() {
    +    this.fontSizeObserveSubscription =
    +      atom.config.observe('editor.fontSize', (newValue, {previous}) => {
    +        this.adjustFontSize(newValue)
    +      })
    +  }
    +
    +  function detached() {
    +    this.fontSizeObserveSubscription.dispose()
    +  }
    +}
    +

    The atom.config.observe method will call the given callback immediately with the current value for the specified key path, and it will also call it in the future whenever the value of that key path changes. If you only want to invoke the callback the next time the value changes, use atom.config.onDidChange instead.

    `,10),f={href:"https://atom.io/docs/api/latest/Disposable",target:"_blank",rel:"noopener noreferrer"},y=n("code",null,"Disposable",-1),w=n("code",null,"@fontSizeObserveSubscription",-1),_={href:"https://atom.io/docs/api/latest/CompositeDisposable",target:"_blank",rel:"noopener noreferrer"},x=n("code",null,"CompositeDisposable",-1),j=t(`

    Writing Config Settings

    The atom.config database is populated on startup from LNX/MAC: ~/.pulsar/config.cson - WIN: %USERPROFILE%\\.pulsar\\config.cson but you can programmatically write to it with atom.config.set:

    // basic key update
    +atom.config.set("core.showInvisibles", true);
    +
    `,3),q={href:"https://atom.io/docs/api/latest/Config",target:"_blank",rel:"noopener noreferrer"},S=n("h2",{id:"keymaps-in-depth",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#keymaps-in-depth","aria-hidden":"true"},"#"),s(" Keymaps In-Depth")],-1),O=n("h3",{id:"structure-of-a-keymap-file",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#structure-of-a-keymap-file","aria-hidden":"true"},"#"),s(" Structure of a Keymap File")],-1),T=n("p",null,[s("Keymap files are encoded as JSON or CSON files containing nested hashes. They work much like style sheets, but instead of applying style properties to elements matching the selector, they specify the meaning of keystrokes on elements matching the selector. Here is an example of some bindings that apply when keystrokes pass through "),n("code",null,"atom-text-editor"),s(" elements:")],-1),P=n("div",{class:"language-coffee ext-coffee line-numbers-mode"},[n("pre",{class:"language-coffee"},[n("code",null,[n("span",{class:"token string-property property"},"'atom-text-editor'"),n("span",{class:"token operator"},":"),s(` + `),n("span",{class:"token string-property property"},"'ctrl-left'"),n("span",{class:"token operator"},":"),s(),n("span",{class:"token string"},"'editor:move-to-beginning-of-word'"),s(` + `),n("span",{class:"token string-property property"},"'ctrl-right'"),n("span",{class:"token operator"},":"),s(),n("span",{class:"token string"},"'editor:move-to-end-of-word'"),s(` + `),n("span",{class:"token string-property property"},"'ctrl-shift-left'"),n("span",{class:"token operator"},":"),s(),n("span",{class:"token string"},"'editor:select-to-beginning-of-word'"),s(` + `),n("span",{class:"token string-property property"},"'ctrl-shift-right'"),n("span",{class:"token operator"},":"),s(),n("span",{class:"token string"},"'editor:select-to-end-of-word'"),s(` + `),n("span",{class:"token string-property property"},"'ctrl-backspace'"),n("span",{class:"token operator"},":"),s(),n("span",{class:"token string"},"'editor:delete-to-beginning-of-word'"),s(` + `),n("span",{class:"token string-property property"},"'ctrl-delete'"),n("span",{class:"token operator"},":"),s(),n("span",{class:"token string"},"'editor:delete-to-end-of-word'"),s(` + +`),n("span",{class:"token string-property property"},"'atom-text-editor:not([mini])'"),n("span",{class:"token operator"},":"),s(` + `),n("span",{class:"token string-property property"},"'ctrl-alt-['"),n("span",{class:"token operator"},":"),s(),n("span",{class:"token string"},"'editor:fold-current-row'"),s(` + `),n("span",{class:"token string-property property"},"'ctrl-alt-]'"),n("span",{class:"token operator"},":"),s(),n("span",{class:"token string"},"'editor:unfold-current-row'"),s(` +`)])]),n("div",{class:"line-numbers","aria-hidden":"true"},[n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"})])],-1),z=n("div",{class:"language-coffee ext-coffee line-numbers-mode"},[n("pre",{class:"language-coffee"},[n("code",null,[n("span",{class:"token string-property property"},"'atom-text-editor'"),n("span",{class:"token operator"},":"),s(` + `),n("span",{class:"token string-property property"},"'cmd-delete'"),n("span",{class:"token operator"},":"),s(),n("span",{class:"token string"},"'editor:delete-to-beginning-of-line'"),s(` + `),n("span",{class:"token string-property property"},"'alt-backspace'"),n("span",{class:"token operator"},":"),s(),n("span",{class:"token string"},"'editor:delete-to-beginning-of-word'"),s(` + `),n("span",{class:"token string-property property"},"'ctrl-A'"),n("span",{class:"token operator"},":"),s(),n("span",{class:"token string"},"'editor:select-to-first-character-of-line'"),s(` + `),n("span",{class:"token string-property property"},"'ctrl-shift-e'"),n("span",{class:"token operator"},":"),s(),n("span",{class:"token string"},"'editor:select-to-end-of-line'"),s(` + `),n("span",{class:"token string-property property"},"'cmd-left'"),n("span",{class:"token operator"},":"),s(),n("span",{class:"token string"},"'editor:move-to-first-character-of-line'"),s(` + +`),n("span",{class:"token string-property property"},"'atom-text-editor:not([mini])'"),n("span",{class:"token operator"},":"),s(` + `),n("span",{class:"token string-property property"},"'cmd-alt-['"),n("span",{class:"token operator"},":"),s(),n("span",{class:"token string"},"'editor:fold-current-row'"),s(` + `),n("span",{class:"token string-property property"},"'cmd-alt-]'"),n("span",{class:"token operator"},":"),s(),n("span",{class:"token string"},"'editor:unfold-current-row'"),s(` +`)])]),n("div",{class:"line-numbers","aria-hidden":"true"},[n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"})])],-1),C=n("div",{class:"language-coffee ext-coffee line-numbers-mode"},[n("pre",{class:"language-coffee"},[n("code",null,[n("span",{class:"token string-property property"},"'atom-text-editor'"),n("span",{class:"token operator"},":"),s(` + `),n("span",{class:"token string-property property"},"'ctrl-left'"),n("span",{class:"token operator"},":"),s(),n("span",{class:"token string"},"'editor:move-to-beginning-of-word'"),s(` + `),n("span",{class:"token string-property property"},"'ctrl-right'"),n("span",{class:"token operator"},":"),s(),n("span",{class:"token string"},"'editor:move-to-end-of-word'"),s(` + `),n("span",{class:"token string-property property"},"'ctrl-shift-left'"),n("span",{class:"token operator"},":"),s(),n("span",{class:"token string"},"'editor:select-to-beginning-of-word'"),s(` + `),n("span",{class:"token string-property property"},"'ctrl-shift-right'"),n("span",{class:"token operator"},":"),s(),n("span",{class:"token string"},"'editor:select-to-end-of-word'"),s(` + `),n("span",{class:"token string-property property"},"'ctrl-backspace'"),n("span",{class:"token operator"},":"),s(),n("span",{class:"token string"},"'editor:delete-to-beginning-of-word'"),s(` + `),n("span",{class:"token string-property property"},"'ctrl-delete'"),n("span",{class:"token operator"},":"),s(),n("span",{class:"token string"},"'editor:delete-to-end-of-word'"),s(` + +`),n("span",{class:"token string-property property"},"'atom-text-editor:not([mini])'"),n("span",{class:"token operator"},":"),s(` + `),n("span",{class:"token string-property property"},"'ctrl-alt-['"),n("span",{class:"token operator"},":"),s(),n("span",{class:"token string"},"'editor:fold-current-row'"),s(` + `),n("span",{class:"token string-property property"},"'ctrl-alt-]'"),n("span",{class:"token operator"},":"),s(),n("span",{class:"token string"},"'editor:unfold-current-row'"),s(` +`)])]),n("div",{class:"line-numbers","aria-hidden":"true"},[n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"})])],-1),A=t(`

    Beneath the first selector are several keybindings, mapping specific key combinations to commands. When an element with the atom-text-editor class is focused and LNX/WIN: Ctrl+Backspace - MAC: Alt+Backspace is pressed, a custom DOM event called editor:delete-to-beginning-of-word is emitted on the atom-text-editor element.

    The second selector group also targets editors, but only if they don't have the mini attribute. In this example, the commands for code folding don't really make sense on mini-editors, so the selector restricts them to regular editors.

    Key Combinations

    Key combinations express one or more keys combined with optional modifier keys. For example: ctrl-w v, or cmd-shift-up. A key combination is composed of the following symbols, separated by a -. A key sequence can be expressed as key combinations separated by spaces.

    TypeExamples
    Character literalsa 4 $
    Modifier keyscmd ctrl alt shift
    Special keysenter escape backspace delete tab home end pageup pagedown left right up down space

    Commands

    Commands are custom DOM events that are triggered when a key combination or sequence matches a binding. This allows user interface code to listen for named commands without specifying the specific keybinding that triggers it. For example, the following code creates a command to insert the current date in an editor:

    atom.commands.add("atom-text-editor", {
    +	"user:insert-date": function (event) {
    +		const editor = this.getModel();
    +		return editor.insertText(new Date().toLocaleString());
    +	},
    +});
    +

    atom.commands refers to the global CommandRegistry instance where all commands are set and consequently picked up by the command palette.

    When you are looking to bind new keys, it is often useful to use the Command Palette (LNX/WIN: Ctrl+Shift+P - MAC: Cmd+Shift+P) to discover what commands are being listened for in a given focus context. Commands are "humanized" following a simple algorithm, so a command like editor:fold-current-row would appear as "Editor: Fold Current Row".

    "Composed" Commands

    A common question is, "How do I make a single keybinding execute two or more commands?" There isn't any direct support for this in Pulsar, but it can be achieved by creating a custom command that performs the multiple actions you desire and then creating a keybinding for that command. For example, let's say I want to create a "composed" command that performs a Select Line followed by Cut. You could add the following to your init.js:

    atom.commands.add("atom-text-editor", "custom:cut-line", function () {
    +	const editor = this.getModel();
    +	editor.selectLinesContainingCursors();
    +	editor.cutSelectedText();
    +});
    +

    Then let's say we want to map this custom command to alt-ctrl-z, you could add the following to your keymap:

    'atom-text-editor':
    +  'alt-ctrl-z': 'custom:cut-line'
    +

    Specificity and Cascade Order

    As is the case with CSS applying styles, when multiple bindings match for a single element, the conflict is resolved by choosing the most specific selector. If two matching selectors have the same specificity, the binding for the selector appearing later in the cascade takes precedence.

    Currently, there's no way to specify selector ordering within a single keymap, because JSON objects do not preserve order. We handle cases where selector ordering is critical by breaking the keymap into separate files, such as snippets-1.cson and snippets-2.cson.

    Selectors and Custom Packages

    If a keybinding should only apply to a specific grammar, you can limit bindings to that grammar using the data-grammar attribute on the atom-text-editor element:

    "atom-text-editor[data-grammar='source example']":
    +  'ctrl-.': 'custom:custom-command'
    +

    While selectors can be applied to the entire editor by what grammar is associated with it, they cannot be applied to scopes defined within the grammar or to sub-elements of atom-text-editor.

    Removing Bindings

    When the keymap system encounters a binding with the unset! directive as its command, it will treat the current element as if it had no key bindings matching the current keystroke sequence and continue searching from its parent. For example, the following code removes the keybinding for a in the Tree View, which is normally used to trigger the tree-view:add-file command:

    '.tree-view':
    +  'a': 'unset!'
    +

    Keybinding Resolver

    But if some element above the Tree View had a keybinding for a, that keybinding would still execute even when the focus is inside the Tree View.

    When the keymap system encounters a binding with the abort! directive as its command, it will stop searching for a keybinding. For example, the following code removes the keybinding for LNX/WIN: Ctrl+O - MAC: Cmd+O when the selection is inside an editor pane:

    ',28),I=n("div",{class:"language-coffee ext-coffee line-numbers-mode"},[n("pre",{class:"language-coffee"},[n("code",null,[n("span",{class:"token string-property property"},"'atom-text-editor'"),n("span",{class:"token operator"},":"),s(` + `),n("span",{class:"token string-property property"},"'ctrl-o'"),n("span",{class:"token operator"},":"),s(),n("span",{class:"token string"},"'abort!'"),s(` +`)])]),n("div",{class:"line-numbers","aria-hidden":"true"},[n("div",{class:"line-number"}),n("div",{class:"line-number"})])],-1),E=n("div",{class:"language-coffee ext-coffee line-numbers-mode"},[n("pre",{class:"language-coffee"},[n("code",null,[n("span",{class:"token string-property property"},"'atom-text-editor'"),n("span",{class:"token operator"},":"),s(` + `),n("span",{class:"token string-property property"},"'cmd-o'"),n("span",{class:"token operator"},":"),s(),n("span",{class:"token string"},"'abort!'"),s(` +`)])]),n("div",{class:"line-numbers","aria-hidden":"true"},[n("div",{class:"line-number"}),n("div",{class:"line-number"})])],-1),D=n("div",{class:"language-coffee ext-coffee line-numbers-mode"},[n("pre",{class:"language-coffee"},[n("code",null,[n("span",{class:"token string-property property"},"'atom-text-editor'"),n("span",{class:"token operator"},":"),s(` + `),n("span",{class:"token string-property property"},"'ctrl-o'"),n("span",{class:"token operator"},":"),s(),n("span",{class:"token string"},"'abort!'"),s(` +`)])]),n("div",{class:"line-numbers","aria-hidden":"true"},[n("div",{class:"line-number"}),n("div",{class:"line-number"})])],-1),N=t(`

    But if you click inside the Tree View and press LNX/WIN: Ctrl+O - MAC: Cmd+O , it will work.

    Forcing Chromium's Native Keystroke Handling

    If you want to force the native browser behavior for a given keystroke, use the native! directive as the command of a binding. This can be useful to enable the correct behavior in native input elements. If you apply the .native-key-bindings class to an element, all the keystrokes typically handled by the browser will be assigned the native! directive.

    Tips

    Tip: Components and input elements may not correctly handle backspace and arrow keys without forcing this behavior. If your backspace isn't working correctly inside of a component, add either the directive or the .native-key-bindings class.

    Overloading Key Bindings

    Occasionally, it makes sense to layer multiple actions on top of the same key binding. An example of this is the snippets package. Snippets are inserted by typing a snippet prefix such as for and then pressing Tab. Every time Tab is pressed, we want to execute code attempting to expand a snippet if one exists for the text preceding the cursor. If a snippet doesn't exist, we want Tab to actually insert whitespace.

    To achieve this, the snippets package makes use of the .abortKeyBinding() method on the event object representing the snippets:expand command.

    // pseudo-code
    +editor.command("snippets:expand", (e) => {
    +	if (this.cursorFollowsValidPrefix()) {
    +		this.expandSnippet();
    +	} else {
    +		e.abortKeyBinding();
    +	}
    +});
    +

    When the event handler observes that the cursor does not follow a valid prefix, it calls e.abortKeyBinding(), telling the keymap system to continue searching for another matching binding.

    Step-by-Step: How Keydown Events are Mapped to Commands

    • A keydown event occurs on a focused element.
    • Starting at the focused element, the keymap walks upward towards the root of the document, searching for the most specific CSS selector that matches the current DOM element and also contains a keystroke pattern matching the keydown event.
    • When a matching keystroke pattern is found, the search is terminated and the pattern's corresponding command is triggered on the current element.
    • If .abortKeyBinding() is called on the triggered event object, the search is resumed, triggering a binding on the next-most-specific CSS selector for the same element or continuing upward to parent elements.
    • If no bindings are found, the event is handled by Chromium normally.

    Overriding Pulsar's Keyboard Layout Recognition

    `,12),M={href:"https://web.archive.org/web/20220729003828/https://blog.atom.io/2016/10/17/the-wonderful-world-of-keyboards.html",target:"_blank",rel:"noopener noreferrer"},L=t(`

    You can add the following to your init.js to send Ctrl+@ when you press Ctrl+Alt+G:

    atom.keymaps.addKeystrokeResolver(({ event }) => {
    +	if (
    +		event.code === "KeyG" &&
    +		event.altKey &&
    +		event.ctrlKey &&
    +		event.type !== "keyup"
    +	) {
    +		return "ctrl-@";
    +	}
    +});
    +

    Or if you are still using the init.coffee file:

    atom.keymaps.addKeystrokeResolver ({event}) ->
    +  if event.code is 'KeyG' and event.altKey and event.ctrlKey and event.type isnt 'keyup'
    +    return 'ctrl-@'
    +
    `,4),R=n("code",null,"event",-1),$={href:"https://flight-manual.atom.io/hacking-atom/sections/debugging/#check-for-errors-in-the-developer-tools",target:"_blank",rel:"noopener noreferrer"},W=t(`
    document.addEventListener("keydown", (e) => console.log(e), true);
    +

    This will print every keypress event in Pulsar to the console so you can inspect KeyboardEvent.key and KeyboardEvent.code.

    Scoped Settings, Scopes and Scope Descriptors

    Pulsar supports language-specific settings. You can soft wrap only Markdown files, or set the tab length to 4 in Python files.

    Language-specific settings are a subset of something more general we call "scoped settings". Scoped settings allow targeting down to a specific syntax token type. For example, you could conceivably set a setting to target only Ruby comments, only code inside Markdown files, or even only JavaScript function names.

    Scope Names in Syntax Tokens

    Each token in the editor has a collection of scope names. For example, the aforementioned JavaScript function name might have the scope names function and name. An open paren might have the scope names punctuation, parameters, begin.

    Scope names work just like CSS classes. In fact, in the editor, scope names are attached to a token's DOM node as CSS classes.

    Take this piece of JavaScript:

    function functionName() {
    +	console.log("Log it out");
    +}
    +

    In the dev tools, the first line's markup looks like this.

    Markup

    All the class names on the spans are scope names. Any scope name can be used to target a setting's value.

    Scope Selectors

    Scope selectors allow you to target specific tokens just like a CSS selector targets specific nodes in the DOM. Some examples:

    '.source.js' # selects all javascript tokens
    +'.source.js .function.name' # selects all javascript function names
    +'.function.name' # selects all function names in any language
    +
    `,16),V={href:"https://atom.io/docs/api/latest/Config#instance-set",target:"_blank",rel:"noopener noreferrer"},U=n("code",null,"Config::set",-1),Y=n("code",null,"scopeSelector",-1),H=n("code",null,"scopeSelector",-1),K=t(`
    atom.config.set("my-package.my-setting", "special value", {
    +	scopeSelector: ".source.js .function.name",
    +});
    +

    Scope Descriptors

    `,2),F={href:"https://atom.io/docs/api/latest/ScopeDescriptor",target:"_blank",rel:"noopener noreferrer"},B=n("code",null,"Array",-1),J=n("code",null,"String",-1),G=n("em",null,"all",-1),X=t(`

    In our JavaScript example above, a scope descriptor for the function name token would be:

    ["source.js", "meta.function.js", "entity.name.function.js"];
    +
    `,2),Q={href:"https://atom.io/docs/api/latest/Config#instance-get",target:"_blank",rel:"noopener noreferrer"},Z=n("code",null,"Config::get",-1),nn=n("code",null,"scopeDescriptor",-1),sn=t(`
    const scopeDescriptor = [
    +	"source.js",
    +	"meta.function.js",
    +	"entity.name.function.js",
    +];
    +const value = atom.config.get("my-package.my-setting", {
    +	scope: scopeDescriptor,
    +});
    +

    But, you do not need to generate scope descriptors by hand. There are a couple methods available to get the scope descriptor from the editor:

    `,2),en={href:"https://atom.io/docs/api/latest/TextEditor#instance-getRootScopeDescriptor",target:"_blank",rel:"noopener noreferrer"},an=n("code",null,"Editor::getRootScopeDescriptor",-1),tn=n("code",null,'[".source.js"]',-1),on={href:"https://atom.io/docs/api/latest/TextEditor#instance-scopeDescriptorForBufferPosition",target:"_blank",rel:"noopener noreferrer"},pn=n("code",null,"Editor::scopeDescriptorForBufferPosition",-1),cn={href:"https://atom.io/docs/api/latest/Cursor#instance-getScopeDescriptor",target:"_blank",rel:"noopener noreferrer"},ln=n("code",null,"Cursor::getScopeDescriptor",-1),rn=n("code",null,'["source.js", "meta.function.js", "entity.name.function.js"]',-1),un=t(`

    Let's revisit our example using these methods:

    const editor = atom.workspace.getActiveTextEditor();
    +const cursor = editor.getLastCursor();
    +const valueAtCursor = atom.config.get("my-package.my-setting", {
    +	scope: cursor.getScopeDescriptor(),
    +});
    +const valueForLanguage = atom.config.get("my-package.my-setting", {
    +	scope: editor.getRootScopeDescriptor(),
    +});
    +

    Serialization in Pulsar

    When a window is refreshed or restored from a previous session, the view and its associated objects are deserialized from a JSON representation that was stored during the window's previous shutdown. For your own views and objects to be compatible with refreshing, you'll need to make them play nicely with the serializing and deserializing.

    Package Serialization Hook

    Your package's main module can optionally include a serialize method, which will be called before your package is deactivated. You should return a JSON-serializable object, which will be handed back to you as an object argument to activate next time it is called. In the following example, the package keeps an instance of MyObject in the same state across refreshes.

    module.exports = {
    +	activate(state) {
    +		this.myObject = state
    +			? atom.deserializers.deserialize(state)
    +			: new MyObject("Hello");
    +	},
    +
    +	serialize() {
    +		return this.myObject.serialize();
    +	},
    +};
    +

    Serialization Methods

    class MyObject {
    +	constructor(data) {
    +		this.data = data;
    +	}
    +
    +	serialize() {
    +		return {
    +			deserializer: "MyObject",
    +			data: this.data,
    +		};
    +	}
    +}
    +

    serialize()

    Objects that you want to serialize should implement .serialize(). This method should return a serializable object, and it must contain a key named deserializer whose value is the name of a registered deserializer that can convert the rest of the data to an object. It's usually just the name of the class itself.

    Registering Deserializers

    The other side of the coin is deserializers, whose job is to convert a state object returned from a previous call to serialize back into a genuine object.

    deserializers in package.json

    The preferred way to register deserializers is via your package's package.json file:

    {
    +  "name": "wordcount",
    +  ...
    +  "deserializers": {
    +    "MyObject": "deserializeMyObject"
    +  }
    +}
    +

    Here, the key ("MyObject") is the name of the deserializer\u2014the same string used by the deserializer field in the object returned by your serialize() method. The value ("deserializeMyObject") is the name of a function in your main module that'll be passed the serialized data and will return a genuine object. For example, your main module might look like this:

    module.exports = {
    +	deserializeMyObject({ data }) {
    +		return new MyObject(data);
    +	},
    +};
    +

    Now you can call the global deserialize method with state returned from serialize, and your class's deserialize method will be selected automatically.

    atom.deserializers.add(klass)

    An alternative is to use the atom.deserializers.add method with your class in order to make it available to the deserialization system. Usually this is used in conjunction with a class-level deserialize method:

    class MyObject {
    +	static initClass() {
    +		atom.deserializers.add(this);
    +	}
    +
    +	static deserialize({ data }) {
    +		return new MyObject(data);
    +	}
    +
    +	constructor(data) {
    +		this.data = data;
    +	}
    +
    +	serialize() {
    +		return {
    +			deserializer: "MyObject",
    +			data: this.data,
    +		};
    +	}
    +}
    +
    +MyObject.initClass();
    +

    While this used to be the standard method of registering a deserializer, the package.json method is now preferred since it allows Pulsar to defer loading and executing your code until it's actually needed.

    Versioning

    class MyObject {
    +	static initClass() {
    +		atom.deserializers.add(this);
    +
    +		this.version = 2;
    +	}
    +
    +	static deserialize(state) {
    +		// ...
    +	}
    +
    +	serialize() {
    +		return {
    +			version: this.constructor.version,
    +			// ...
    +		};
    +	}
    +}
    +
    +MyObject.initClass();
    +

    Your serializable class can optionally have a class-level @version property and include a version key in its serialized state. When deserializing, Pulsar will only attempt to call deserialize if the two versions match, and otherwise return undefined.

    Developing Node Modules

    Pulsar contains a number of packages that are Node modules instead of Pulsar packages. If you want to make changes to the Node modules, for instance atom-keymap, you have to link them into the development environment differently than you would a normal Pulsar package.

    Linking a Node Module Into Your Pulsar Dev Environment

    Here are the steps to run a local version of a Node module within Pulsar. We're using atom-keymap as an example:

    `,30),dn=n("div",{class:"language-bash ext-sh line-numbers-mode"},[n("pre",{class:"language-bash"},[n("code",null,[s("$ "),n("span",{class:"token function"},"git"),s(` clone https://github.com/pulsar-edit/atom-keymap.git +$ `),n("span",{class:"token builtin class-name"},"cd"),s(` atom-keymap +$ `),n("span",{class:"token function"},"npm"),s(),n("span",{class:"token function"},"install"),s(` +$ `),n("span",{class:"token function"},"npm"),s(),n("span",{class:"token function"},"link"),s(` +$ `),n("span",{class:"token builtin class-name"},"cd"),s(),n("span",{class:"token operator"},"<"),s("WHERE YOU CLONED PULSAR"),n("span",{class:"token operator"},">"),s(` +$ `),n("span",{class:"token function"},"npm"),s(),n("span",{class:"token function"},"link"),s(` atom-keymap + +`),n("span",{class:"token comment"},"# This is the special step, it makes the Node module work with Pulsar's version of Node"),s(` +$ pulsar `),n("span",{class:"token parameter variable"},"-p"),s(` rebuild + +`),n("span",{class:"token comment"},"# If you have cloned Pulsar in a different location than ~/github/pulsar"),s(` +`),n("span",{class:"token comment"},"# you need to set the following environment variable"),s(` +$ `),n("span",{class:"token builtin class-name"},"export"),s(),n("span",{class:"token assign-left variable"},"ATOM_DEV_RESOURCE_PATH"),n("span",{class:"token operator"},"="),n("span",{class:"token operator"},"<"),s("WHERE YOU CLONED PULSAR"),n("span",{class:"token operator"},">"),s(` + +`),n("span",{class:"token comment"},"# Should work!"),s(` +$ pulsar `),n("span",{class:"token parameter variable"},"--dev"),s(),n("span",{class:"token builtin class-name"},"."),s(` +`)])]),n("div",{class:"line-numbers","aria-hidden":"true"},[n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"})])],-1),kn=n("div",{class:"language-bash ext-sh line-numbers-mode"},[n("pre",{class:"language-bash"},[n("code",null,[s("$ "),n("span",{class:"token function"},"git"),s(` clone https://github.com/pulsar-edit/atom-keymap.git +$ `),n("span",{class:"token builtin class-name"},"cd"),s(` atom-keymap +$ `),n("span",{class:"token function"},"npm"),s(),n("span",{class:"token function"},"install"),s(` +$ `),n("span",{class:"token function"},"npm"),s(),n("span",{class:"token function"},"link"),s(` +$ `),n("span",{class:"token builtin class-name"},"cd"),s(),n("span",{class:"token operator"},"<"),s("WHERE YOU CLONED PULSAR"),n("span",{class:"token operator"},">"),s(` +$ `),n("span",{class:"token function"},"npm"),s(),n("span",{class:"token function"},"link"),s(` atom-keymap + +`),n("span",{class:"token comment"},"# This is the special step, it makes the Node module work with Pulsar's version of Node"),s(` +$ pulsar `),n("span",{class:"token parameter variable"},"-p"),s(` rebuild + +`),n("span",{class:"token comment"},"# If you have cloned Pulsar in a different location than ~/github/pulsar"),s(` +`),n("span",{class:"token comment"},"# you need to set the following environment variable"),s(` +$ `),n("span",{class:"token builtin class-name"},"export"),s(),n("span",{class:"token assign-left variable"},"ATOM_DEV_RESOURCE_PATH"),n("span",{class:"token operator"},"="),n("span",{class:"token operator"},"<"),s("WHERE YOU CLONED PULSAR"),n("span",{class:"token operator"},">"),s(` + +`),n("span",{class:"token comment"},"# Should work!"),s(` +$ pulsar `),n("span",{class:"token parameter variable"},"--dev"),s(),n("span",{class:"token builtin class-name"},"."),s(` +`)])]),n("div",{class:"line-numbers","aria-hidden":"true"},[n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"})])],-1),mn=n("div",{class:"language-bash ext-sh line-numbers-mode"},[n("pre",{class:"language-bash"},[n("code",null,[s("$ "),n("span",{class:"token function"},"git"),s(` clone https://github.com/pulsar-edit/atom-keymap.git +$ `),n("span",{class:"token builtin class-name"},"cd"),s(` atom-keymap +$ `),n("span",{class:"token function"},"npm"),s(),n("span",{class:"token function"},"install"),s(` +$ `),n("span",{class:"token function"},"npm"),s(),n("span",{class:"token function"},"link"),s(` +$ `),n("span",{class:"token builtin class-name"},"cd"),s(),n("span",{class:"token operator"},"<"),s("WHERE YOU CLONED PULSAR"),n("span",{class:"token operator"},">"),s(` +$ `),n("span",{class:"token function"},"npm"),s(),n("span",{class:"token function"},"link"),s(` atom-keymap + +`),n("span",{class:"token comment"},"# This is the special step, it makes the Node module work with Pulsar's version of Node"),s(` +$ pulsar `),n("span",{class:"token parameter variable"},"-p"),s(` rebuild + +`),n("span",{class:"token comment"},"# If you have cloned Pulsar in a different location than %USERPROFILE%\\github\\pulsar"),s(` +`),n("span",{class:"token comment"},"# you need to set the following environment variable"),s(` +$ setx `),n("span",{class:"token assign-left variable"},"ATOM_DEV_RESOURCE_PATH"),n("span",{class:"token operator"},"="),n("span",{class:"token operator"},"<"),s("WHERE YOU CLONED PULSAR"),n("span",{class:"token operator"},">"),s(` + +`),n("span",{class:"token comment"},"# Should work!"),s(` +$ pulsar `),n("span",{class:"token parameter variable"},"--dev"),s(),n("span",{class:"token builtin class-name"},"."),s(` +`)])]),n("div",{class:"line-numbers","aria-hidden":"true"},[n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"})])],-1),hn=t(`

    After you get the Node module linked and working, every time you make a change to the Node module's code, you will have to exit Pulsar and do the following:

    $ cd <WHERE YOU CLONED THE NODE MODULE>
    +$ npm install
    +$ cd <WHERE YOU CLONED PULSAR>
    +$ pulsar -p rebuild
    +$ pulsar --dev .
    +

    Interacting With Other Packages Via Services

    Pulsar packages can interact with each other through versioned APIs called services. To provide a service, in your package.json, specify one or more version numbers, each paired with the name of a method on your package's main module:

    {
    +	"providedServices": {
    +		"my-service": {
    +			"description": "Does a useful thing",
    +			"versions": {
    +				"1.2.3": "provideMyServiceV1",
    +				"2.3.4": "provideMyServiceV2"
    +			}
    +		}
    +	}
    +}
    +

    In your package's main module, implement the methods named above. These methods will be called any time a package is activated that consumes their corresponding service. They should return a value that implements the service's API.

    module.exports = {
    +	activate() {
    +		// ...
    +	},
    +
    +	provideMyServiceV1() {
    +		return adaptToLegacyAPI(myService);
    +	},
    +
    +	provideMyServiceV2() {
    +		return myService;
    +	},
    +};
    +
    `,7),vn={href:"https://docs.npmjs.com/cli/v6/using-npm/semver#ranges",target:"_blank",rel:"noopener noreferrer"},gn=n("em",null,"ranges",-1),bn=t(`
    {
    +	"consumedServices": {
    +		"another-service": {
    +			"versions": {
    +				"^1.2.3": "consumeAnotherServiceV1",
    +				">=2.3.4 <2.5": "consumeAnotherServiceV2"
    +			}
    +		}
    +	}
    +}
    +

    These methods will be called any time a package is activated that provides their corresponding service. They will receive the service object as an argument. You will usually need to perform some kind of cleanup in the event that the package providing the service is deactivated. To do this, return a Disposable from your service-consuming method:

    const { Disposable } = require("atom");
    +
    +module.exports = {
    +	activate() {
    +		// ...
    +	},
    +
    +	consumeAnotherServiceV1(service) {
    +		useService(adaptServiceFromLegacyAPI(service));
    +		return new Disposable(() => stopUsingService(service));
    +	},
    +
    +	consumeAnotherServiceV2(service) {
    +		useService(service);
    +		return new Disposable(() => stopUsingService(service));
    +	},
    +};
    +

    Maintaining Your Packages

    Pre-release information

    This section is about a feature in pre-release. The information below documents the intended functionality but there is still ongoing work to support these features with stability.

    While publishing is, by far, the most common action you will perform when working with the packages you provide, there are other things you may need to do.

    Publishing a Package Manually

    STOP

    Publishing a package manually is not a recommended practice and is only for the advanced user who has published packages before. If you perform the steps wrong, you may be unable to publish the new version of your package and may have to completely unpublish your package in order to correct the faulty state. You have been warned.

    Some people prefer to control every aspect of the package publishing process. Normally, the ppm tool manages certain details during publishing to keep things consistent and make everything work smoothly. If you're one of those people that prefers to do things manually, there are certain steps you'll have to take in order to make things work just as smoothly as if ppm has taken care of things for you.

    `,9),fn={class:"custom-container note"},yn=n("p",{class:"custom-container-title"},"Note",-1),wn=n("strong",null,"Note:",-1),_n={href:"https://web.pulsar-edit.dev",target:"_blank",rel:"noopener noreferrer"},xn={href:"https://github.com",target:"_blank",rel:"noopener noreferrer"},jn=t('

    When you have completed the changes that you want to publish and are ready to start the publishing process, you must perform the following steps on the master branch:

    1. Update the version number in your package's package.json. The version number must match the regular expression: ^\\d+\\.\\d+\\.\\d+
    2. Commit the version number change
    3. Create a Git tag referencing the above commit. The tag must match the regular expression ^v\\d+\\.\\d+\\.\\d+ and the part after the v must match the full text of the version number in the package.json
    4. Execute git push --follow-tags
    5. Execute pulsar -p publish --tag tagname where tagname must match the name of the tag created in the above step

    Adding a Collaborator

    ',3),qn={href:"https://help.github.com/articles/adding-collaborators-to-a-personal-repository/",target:"_blank",rel:"noopener noreferrer"},Sn=n("em",null,"Note:",-1),On={href:"https://help.github.com/articles/creating-a-new-organization-account/",target:"_blank",rel:"noopener noreferrer"},Tn={href:"https://help.github.com/articles/permission-levels-for-an-organization/",target:"_blank",rel:"noopener noreferrer"},Pn=n("h3",{id:"transferring-ownership",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#transferring-ownership","aria-hidden":"true"},"#"),s(" Transferring Ownership")],-1),zn=n("div",{class:"custom-container danger"},[n("p",{class:"custom-container-title"},"STOP"),n("p",null,"\u{1F6A8} This is a permanent change. There is no going back! \u{1F6A8}")],-1),Cn={href:"https://help.github.com/articles/transferring-a-repository/",target:"_blank",rel:"noopener noreferrer"},An=n("code",null,"package.json",-1),In=n("h3",{id:"unpublish-your-package",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#unpublish-your-package","aria-hidden":"true"},"#"),s(" Unpublish Your Package")],-1),En={href:"https://web.pulsar-edit.dev",target:"_blank",rel:"noopener noreferrer"},Dn=n("code",null,"package-name",-1),Nn=t(`
    $ pulsar -p unpublish <package-name>
    +
    `,1),Mn={href:"https://web.pulsar-edit.dev",target:"_blank",rel:"noopener noreferrer"},Ln=t(`

    Unpublish a Specific Version

    If you mistakenly published a version of your package or perhaps you find a glaring bug or security hole, you may want to unpublish just that version of your package. For example, if your package is named package-name and the bad version of your package is v1.2.3 then the command you would execute is:

    $ pulsar -p unpublish <package-name@1.2.3>
    +
    `,3),Rn={href:"https://web.pulsar-edit.dev",target:"_blank",rel:"noopener noreferrer"},$n=t(`

    Rename Your Package

    If you need to rename your package for any reason, you can do so with one simple command \u2013 pulsar -p publish --rename changes the name field in your package's package.json, pushes a new commit and tag, and publishes your renamed package. Requests made to the previous name will be forwarded to the new name.

    $ pulsar -p publish --rename <new-package-name>
    +

    Tips

    Tip: Once a package name has been used, it cannot be re-used by another package even if the original package is unpublished.

    Summary

    You should now have a better understanding of some of the core Pulsar APIs and systems.

    `,6);function Wn(Vn,Un){const a=u("ExternalLinkIcon"),r=u("Tabs");return h(),v("div",null,[b,n("p",null,[s("Subscription methods return "),n("a",f,[y,e(a)]),o("TODO: There is no Pulsar API documented yet so keeping link to Atom until we have this"),s(" objects that can be used to unsubscribe. Note in the example above how we save the subscription to the "),w,s(" instance variable and dispose of it when the view is detached. To group multiple subscriptions together, you can add them all to a "),n("a",_,[x,e(a)]),s(" that you dispose when the view is detached.")]),j,n("p",null,[s("If you're exposing package configuration via specific key paths, you'll want to associate them with a schema in your package's main module. Read more about schemas in the "),n("a",q,[s("Config API documentation"),e(a)]),s(". "),o("TODO: There is no Pulsar API documented yet so keeping link to Atom until we have this")]),S,O,T,e(r,{id:"53",data:[{title:"Linux"},{title:"Mac"},{title:"Windows"}],"tab-id":"behind-pulsar"},{tab0:i(({title:p,value:c,isActive:l})=>[P]),tab1:i(({title:p,value:c,isActive:l})=>[z]),tab2:i(({title:p,value:c,isActive:l})=>[C]),_:1}),A,e(r,{id:"173",data:[{title:"Linux"},{title:"Mac"},{title:"Windows"}],"tab-id":"behind-pulsar"},{tab0:i(({title:p,value:c,isActive:l})=>[I]),tab1:i(({title:p,value:c,isActive:l})=>[E]),tab2:i(({title:p,value:c,isActive:l})=>[D]),_:1}),N,n("p",null,[s("Sometimes the problem isn't mapping the command to a key combination, the problem is that Pulsar doesn't recognize properly what keys you're pressing. This is due to "),n("a",M,[s("some limitations in how Chromium reports keyboard events"),e(a)]),s(". But even this can be customized now.")]),L,n("p",null,[s("If you want to know the "),R,s(" for the keystroke you pressed you can paste the following script to your "),n("a",$,[s("developer tools console"),e(a)]),o("TODO: Convert to Pulsar docs when created")]),W,n("p",null,[n("a",V,[U,e(a)]),s(" accepts a "),o("TODO: There is currently no Pulsar API doc so this is being left for the time being"),Y,s(". If you'd like to set a setting for JavaScript function names, you can give it the JavaScript function name "),H,s(":")]),K,n("p",null,[s("A scope descriptor is an "),n("a",F,[s("Object"),e(a)]),o("TODO: There is currently no Pulsar API doc so this is being left for the time being"),s(" that wraps an "),B,s(" of "),J,s("s. The Array describes a path from the root of the syntax tree to a token including "),G,s(" scope names for the entire path.")]),X,n("p",null,[n("a",Q,[Z,e(a)]),s(" accepts a "),o("TODO: There is currently no Pulsar API doc so this is being left for the time being"),nn,s(". You can get the value for your setting scoped to JavaScript function names via:")]),sn,n("ul",null,[n("li",null,[n("a",en,[an,e(a)]),o("TODO: There is currently no Pulsar API doc so this is being left for the time being"),s(" to get the language's descriptor. For example: "),tn]),n("li",null,[n("a",on,[pn,e(a)]),o("TODO: There is currently no Pulsar API doc so this is being left for the time being"),s(" to get the descriptor at a specific position in the buffer.")]),n("li",null,[n("a",cn,[ln,e(a)]),o("TODO: There is currently no Pulsar API doc so this is being left for the time being"),s(" to get a cursor's descriptor based on position. eg. if the cursor were in the name of the method in our example it would return "),rn])]),un,e(r,{id:"420",data:[{title:"Linux"},{title:"Mac"},{title:"Windows"}],"tab-id":"behind-pulsar"},{tab0:i(({title:p,value:c,isActive:l})=>[dn]),tab1:i(({title:p,value:c,isActive:l})=>[kn]),tab2:i(({title:p,value:c,isActive:l})=>[mn]),_:1}),hn,n("p",null,[s("Similarly, to consume a service, specify one or more "),n("a",vn,[s("version "),gn,e(a)]),s(", each paired with the name of a method on the package's main module:")]),bn,n("div",fn,[yn,n("p",null,[wn,s(" The ppm tool will only publish and "),n("a",_n,[s("https://pulsar-edit.dev"),e(a)]),s(" will only list packages that are hosted on "),n("a",xn,[s("GitHub"),e(a)]),s(", regardless of what process is used to publish them.")])]),jn,n("p",null,[s("Some packages get too big for one person. Sometimes priorities change and someone else wants to help out. You can let others help or create co-owners by "),n("a",qn,[s("adding them as a collaborator"),e(a)]),s(" on the GitHub repository for your package. "),Sn,s(" Anyone that has push access to your repository will have the ability to publish new versions of the package that belongs to that repository.")]),n("p",null,[s("You can also have packages that are owned by a "),n("a",On,[s("GitHub organization"),e(a)]),s(". Anyone who is a member of an organization's "),n("a",Tn,[s("team"),e(a)]),s(" which has push access to the package's repository will be able to publish new versions of the package.")]),Pn,zn,n("p",null,[s("If you want to hand off support of your package to someone else, you can do that by "),n("a",Cn,[s("transferring the package's repository"),e(a)]),s(" to the new owner. Once you do that, they can publish a new version with the updated repository information in the "),An,s(".")]),In,n("p",null,[s("If you no longer want to support your package and cannot find anyone to take it over, you can unpublish your package from "),n("a",En,[s("https://pulsar-edit.dev"),e(a)]),s(". For example, if your package is named "),Dn,s(" then the command you would execute is:")]),Nn,n("p",null,[s("This will remove your package from the "),n("a",Mn,[s("https://pulsar-edit.dev"),e(a)]),s(" package registry. Anyone who has already downloaded a copy of your package will still have it and be able to use it, but it will no longer be available for installation by others.")]),Ln,n("p",null,[s("This will remove just this particular version from the "),n("a",Rn,[s("https://pulsar-edit.dev"),e(a)]),s(" package registry.")]),$n])}const Fn=m(g,[["render",Wn],["__file","index.html.vue"]]);export{Fn as default}; diff --git a/assets/index.html.0618f05b.js b/assets/index.html.0618f05b.js new file mode 100644 index 0000000000..d324dd4e31 --- /dev/null +++ b/assets/index.html.0618f05b.js @@ -0,0 +1 @@ +import{_ as r,o as n,c as i,a,b as e,d as o,r as s}from"./app.0e1565ce.js";const l={},c=a("h1",{id:"ide-java",tabindex:"-1"},[a("a",{class:"header-anchor",href:"#ide-java","aria-hidden":"true"},"#"),e(" IDE-Java")],-1),d=a("p",null,[e("Welcome to the "),a("code",null,"ide-java"),e(" wiki!")],-1),h={href:"https://github.com/atom/ide-java/wiki",target:"_blank",rel:"noopener noreferrer"},_={href:"https://github.com/pulsar-edit/ide-java",target:"_blank",rel:"noopener noreferrer"};function u(f,p){const t=s("ExternalLinkIcon");return n(),i("div",null,[c,d,a("p",null,[e("This wiki contains a single file from the original "),a("a",h,[e("upstream Atom Wiki"),o(t)]),e(". That may mean parts of it are out of date, but otherwise hopefully it is a helpful resource relating to our "),a("a",_,[e("IDE-Java Repo"),o(t)]),e("!")])])}const k=r(l,[["render",u],["__file","index.html.vue"]]);export{k as default}; diff --git a/assets/index.html.06c71ebc.js b/assets/index.html.06c71ebc.js new file mode 100644 index 0000000000..374a861a3c --- /dev/null +++ b/assets/index.html.06c71ebc.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-49f587fe","path":"/docs/packages/core/ide-java/","title":"IDE-Java","lang":"en-us","frontmatter":{"lang":"en-us","title":"IDE-Java"},"excerpt":"","headers":[],"git":{"updatedTime":1664399092000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":2}]},"readingTime":{"minutes":0.21,"words":63},"filePathRelative":"docs/packages/core/ide-java/index.md"}');export{e as data}; diff --git a/assets/index.html.07668541.js b/assets/index.html.07668541.js new file mode 100644 index 0000000000..265386b026 --- /dev/null +++ b/assets/index.html.07668541.js @@ -0,0 +1 @@ +import{_ as r,o as l,c,a as e,b as a,d as t,w as h,f as i,r as n}from"./app.0e1565ce.js";const d={},p=i('

    STOP

    This is being kept for archival purposes only from the original Atom documentation. As this may no longer be relevant to Pulsar, use this at your own risk. Current Pulsar documentation is found at documentation home.

    Resources

    Collection of general resources for the rest of the manual.

    Glossary

    Below is a list of some useful terms we use with regard to Atom.

    Buffer

    A buffer is the text content of a file in Atom. It's basically the same as a file for most descriptions, but it's the version Atom has in memory. For instance, you can change the text of a buffer and it isn't written to its associated file until you save it.

    Command

    A command is a bit of functionality in Atom that can be triggered by the user either through a keybinding or a menu item.

    Dock

    Docks are collapsible pane containers that attach to the left, right, and bottom sides of the Atom window.

    Examples:

    • Tree View
    • Git
    • GitHub

    Key Combination

    A key combination is some combination or sequence of keys that are pressed to perform a task.

    Examples:

    • A
    • Ctrl+Enter
    • Ctrl+K Right

    Key Sequence

    A key sequence is a special case of a key combination. It is a key combination that consists of keys that must be pressed and released in sequence. Ctrl+K Down is a key sequence. Alt+S is not a key sequence because it is two keys that are pressed and released together rather than in succession.

    Keybinding

    A keybinding is the mapping of a key combination, such as Ctrl+Enter to an Atom command.

    Keymap

    A keymap is a collection of keybindings. It can also refer to a file or files containing keybindings for an Atom package or Atom itself.

    Package

    ',24),m=i('

    Pane

    A pane is a visual section of the editor space. Each pane can hold multiple pane items. There is always at least one pane in each Atom window.

    Pane Container

    A section of the Atom UI that can contain multiple panes.

    Pane Item

    Some item, often an editor, that is displayed within a pane. In the default configuration of Atom, pane items are represented by tabs at the top of each pane.

    ',6),u={class:"custom-container note"},f=e("p",{class:"custom-container-title"},"Note",-1),b=e("strong",null,"Note:",-1),k={href:"https://github.com/atom/tabs",target:"_blank",rel:"noopener noreferrer"},y=e("h4",{id:"panel",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#panel","aria-hidden":"true"},"#"),a(" Panel")],-1),g=e("p",null,"A piece of the Atom UI that is outside the editor space.",-1),_=e("p",null,"Examples:",-1),x=e("ul",null,[e("li",null,"Find and Replace"),e("li",null,"Keybinding Resolver")],-1);function A(w,v){const o=n("RouterLink"),s=n("ExternalLinkIcon");return l(),c("div",null,[p,e("p",null,[a("An Atom plugin. There is a bunch more information in the section on "),t(o,{to:"/using-atom/sections/atom-packages/"},{default:h(()=>[a("Atom Packages")]),_:1}),a(".")]),m,e("div",u,[f,e("p",null,[b,a(` The reason why we don't call them "tabs" is because you can disable the `),e("a",k,[a("tabs package"),t(s)]),a(" and then there aren't any tabs. For a similar reason, we don't call them files because some things can be shown in a pane that aren't files, like the Settings View.")])]),y,g,_,x])}const E=r(d,[["render",A],["__file","index.html.vue"]]);export{E as default}; diff --git a/assets/index.html.076d2cf9.js b/assets/index.html.076d2cf9.js new file mode 100644 index 0000000000..a968463e84 --- /dev/null +++ b/assets/index.html.076d2cf9.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-7412c3f9","path":"/docs/packages/core/atom-languageclient/","title":"Atom-LanguageClient","lang":"en-us","frontmatter":{"lang":"en-us","title":"Atom-LanguageClient","prev":"/docs/packages/core/","next":"/docs/packages/core/list.html"},"excerpt":"","headers":[],"git":{"updatedTime":1664399092000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":4}]},"readingTime":{"minutes":0.24,"words":72},"filePathRelative":"docs/packages/core/atom-languageclient/index.md"}');export{e as data}; diff --git a/assets/index.html.077e976b.js b/assets/index.html.077e976b.js new file mode 100644 index 0000000000..fe016d4581 --- /dev/null +++ b/assets/index.html.077e976b.js @@ -0,0 +1 @@ +import{_ as e,o as c,c as t}from"./app.0e1565ce.js";const n={};function _(o,r){return c(),t("div")}const a=e(n,[["render",_],["__file","index.html.vue"]]);export{a as default}; diff --git a/assets/index.html.08b5d061.js b/assets/index.html.08b5d061.js new file mode 100644 index 0000000000..73c204826b --- /dev/null +++ b/assets/index.html.08b5d061.js @@ -0,0 +1,836 @@ +import{_ as m,a as k,b as g,c as v,d as b,e as f}from"./spec-suite.8f5e854d.js";import{_ as h}from"./dev-tools.1b7b2813.js";import{_ as y,a as w,b as _}from"./theme-side-by-side.33cf8d5d.js";import{_ as x}from"./iconography.bf3cea92.js";import{_ as q,a as P,b as T,c as C,d as I,e as S,f as j,g as A,h as N,i as R}from"./cpu-profile-done.c24435bc.js";import{_ as M,o as L,c as W,a as e,b as n,d as a,w as o,e as i,f as t,r as d}from"./app.0e1565ce.js";const O={},U=e("div",{class:"custom-container warning"},[e("p",{class:"custom-container-title"},"Under Construction"),e("p",null,[n("This document is under construction, please check back soon for updates. Please see "),e("a",{href:"/docs/community"},"our socials"),n(" and feel free to ask for assistance or inquire as to the status of this document.")])],-1),E={href:"https://github.com/pulsar-edit/tree-view",target:"_blank",rel:"noopener noreferrer"},F={href:"https://github.com/pulsar-edit/command-palette",target:"_blank",rel:"noopener noreferrer"},D={href:"https://github.com/pulsar-edit/find-and-replace",target:"_blank",rel:"noopener noreferrer"},Y=t('

    In this section, we're going to learn how to extend the functionality of Pulsar through writing packages. This will be everything from new user interfaces to new language grammars to new themes. We'll learn this by writing a series of increasingly complex packages together, introducing you to new APIs and tools and techniques as we need them.

    First though we will look at how to build the editor itself from source.

    If you're looking for an example or specific section using a specific API or feature, you can check below for an index to this section and skip right to it.

    Sections

    Building Pulsar

    If you want to investigate a bug, implement a new feature in Pulsar's core or just want to tinker then you will need to build and run Pulsar from source.

    ',6),z={href:"https://github.com/pulsar-edit/pulsar",target:"_blank",rel:"noopener noreferrer"},V=e("h3",{id:"requirements-and-dependencies",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#requirements-and-dependencies","aria-hidden":"true"},"#"),n(" Requirements and dependencies")],-1),H=e("p",null,"To build Pulsar you will need to meet some basic requirements:",-1),G={href:"https://github.com/pulsar-edit/pulsar/blob/master/.nvmrc",target:"_blank",rel:"noopener noreferrer"},$={href:"https://github.com/nvm-sh/nvm",target:"_blank",rel:"noopener noreferrer"},B=e("li",null,[n("yarn (enable with "),e("code",null,"corepack enable"),n(")")],-1),X=e("li",null,"Git",-1),J=e("li",null,"Python",-1),K=e("li",null,"C++ toolchain",-1),Z={href:"https://wiki.gnome.org/Projects/Libsecret",target:"_blank",rel:"noopener noreferrer"},Q=e("p",null,"For OS or distribution specific instructions see below:",-1),ee=e("h4",{id:"ubuntu-debian",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#ubuntu-debian","aria-hidden":"true"},"#"),n(" Ubuntu/Debian")],-1),ne=e("div",{class:"language-bash ext-sh line-numbers-mode"},[e("pre",{class:"language-bash"},[e("code",null,[e("span",{class:"token comment"},"# Install development packages"),n(` +`),e("span",{class:"token function"},"apt"),n(),e("span",{class:"token function"},"install"),n(` build-essential libxkbfile-dev libsecret-1-dev libx11-dev +`)])]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),ae=e("h4",{id:"fedora-rhel",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#fedora-rhel","aria-hidden":"true"},"#"),n(" Fedora/RHEL")],-1),se=e("div",{class:"language-bash ext-sh line-numbers-mode"},[e("pre",{class:"language-bash"},[e("code",null,[e("span",{class:"token comment"},"# Install development packages"),n(` +dnf `),e("span",{class:"token parameter variable"},"--assumeyes"),n(),e("span",{class:"token function"},"install"),n(),e("span",{class:"token function"},"make"),n(` gcc gcc-c++ glibc-devel git-core libsecret-devel rpmdevtools libX11-devel libxkbfile-devel nss atk gdk-pixbuf2 gtk3 mesa-dri-drivers +`)])]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),te=e("h4",{id:"arch",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#arch","aria-hidden":"true"},"#"),n(" Arch")],-1),oe=e("div",{class:"language-bash ext-sh line-numbers-mode"},[e("pre",{class:"language-bash"},[e("code",null,[e("span",{class:"token comment"},"# Install the development packges"),n(` +pacman `),e("span",{class:"token parameter variable"},"-S"),n(` base-devel libxkbfile libsecret libx11 libxcrypt-compat +`)])]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),ie=e("h4",{id:"opensuse",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#opensuse","aria-hidden":"true"},"#"),n(" OpenSUSE")],-1),le=e("div",{class:"language-bash ext-sh line-numbers-mode"},[e("pre",{class:"language-bash"},[e("code",null,[e("span",{class:"token comment"},"# Install development packages"),n(` +`),e("span",{class:"token function"},"zypper"),n(),e("span",{class:"token keyword"},"in"),n(),e("span",{class:"token parameter variable"},"-t"),n(` pattern devel_basis +`),e("span",{class:"token function"},"zypper"),n(),e("span",{class:"token keyword"},"in"),n(` libX11-devel libxkbfile-devel libsecret-devel +`)])]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),re=e("div",{class:"language-bash ext-sh line-numbers-mode"},[e("pre",{class:"language-bash"},[e("code",null,`TODO +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"})])],-1),pe={href:"https://visualstudio.microsoft.com/downloads/",target:"_blank",rel:"noopener noreferrer"},ce=t(`

    Building and running the application

    To build the application so you can start hacking on the core you will need to download the source code to your local machine and cd to the pulsar directory:

    git clone https://github.com/pulsar-edit/pulsar.git && cd pulsar
    +
    `,3),ue=e("code",null,"nvm",-1),de=e("code",null,"yarn",-1),he={href:"https://github.com/pulsar-edit/pulsar/blob/master/.nvmrc",target:"_blank",rel:"noopener noreferrer"},me=t(`
    nvm install
    +corepack enable
    +

    If Node.js is already installed, run the following to make sure the correct version of Node.js is being used (see requirements):

    nvm use
    +node -v
    +

    Run the following to initialize and update the submodules:

    git submodule init && git submodule update
    +

    Now install and build Pulsar & ppm:

    yarn install
    +yarn build
    +yarn build:apm
    +

    Start Pulsar!

    yarn start
    +

    These instructions will also build ppm (Pulsar Package Manager) but it will require some additional configuration for use.

    Building binaries

    The following will allow you to build Pulsar as a stand alone binary or installer. After running you will find your your built application in pulsar/binaries.

    The build script will automatically build for your system's CPU architecture, for example building on an x86_64 CPU will produce binaries for x86_64, building on arm64 will only produce binaries for arm64.

    It is not possible to "cross-build" for different OSs. For Linux binaries you must build from a Linux machine, macOS binaries must be built from macOS etc. Your OS is detected automatically and the script will build the correct binaries for it.

    `,14),ke=e("p",null,[n("By default running "),e("code",null,"yarn dist"),n(" will attempt to create "),e("code",null,"appimage"),n(" (for most Linux distributions), "),e("code",null,"deb"),n(" (for Debian or Ubuntu based distributions) and "),e("code",null,"rpm"),n(" (for Red Hat or Fedora based distributions) binaries but you can select the actual target you want to build by appending the above targets to the command. e.g.:")],-1),ge=e("ul",null,[e("li",null,[e("code",null,"yarn dist appimage")]),e("li",null,[e("code",null,"yarn dist deb")]),e("li",null,[e("code",null,"yarn dist rpm")]),e("li",null,[e("code",null,"yarn dist targz")])],-1),ve=e("p",null,[e("code",null,"yarn dist"),n(" will create a "),e("code",null,"dmg"),n(" installer, there are currently no additional targets for macOS.")],-1),be=e("p",null,"As noted above this builds for your current CPU architecture. i.e. on an Intel Mac this will create Intel binaries, on Apple silicon (M1, M2 etc.) this will create Apple silicon binaries.",-1),fe=e("p",null,[n("By default running "),e("code",null,"yarn dist"),n(" will attempt to create an "),e("code",null,"NSIS"),n(" installer as well as a "),e("code",null,"Portable"),n(" executable which does not require installation. If you only wish to build one then you can specify it by appending the above targets to the command e.g.:")],-1),ye=e("ul",null,[e("li",null,[e("code",null,"yarn dist nsis")]),e("li",null,[e("code",null,"yarn dist portable")])],-1),we=e("h2",{id:"using-ppm-pulsar-package-manager",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#using-ppm-pulsar-package-manager","aria-hidden":"true"},"#"),n(" Using ppm (Pulsar Package Manager)")],-1),_e=e("p",null,[e("code",null,"ppm"),n(" is used for installing and managing Pulsar's packages in much the same way that "),e("code",null,"apm"),n(" did on Atom. However at this point in the project there are a few hoops you have to jump through to get it to work correctly.")],-1),xe=e("p",null,[n("After following the build instructions you will find the "),e("code",null,"ppm"),n(" binary at "),e("code",null,"pulsar/ppm/bin/apm"),n(" but by default Pulsar will be looking in the wrong place. There will also be issues relating to the Electron version which will prevent install from the package backend. To solve this a couple of environmental variables need to be exported.")],-1),qe=e("div",{class:"language-bash ext-sh line-numbers-mode"},[e("pre",{class:"language-bash"},[e("code",null,[e("span",{class:"token builtin class-name"},"export"),n(),e("span",{class:"token assign-left variable"},"ATOM_HOME"),e("span",{class:"token operator"},"="),n("/home/"),e("span",{class:"token operator"},"<"),n("user"),e("span",{class:"token operator"},">"),n(`/.pulsar +`),e("span",{class:"token builtin class-name"},"export"),n(),e("span",{class:"token assign-left variable"},"APM_PATH"),e("span",{class:"token operator"},"="),n(`/ppm/bin/apm +`),e("span",{class:"token builtin class-name"},"export"),n(),e("span",{class:"token assign-left variable"},"ATOM_ELECTRON_VERSION"),e("span",{class:"token operator"},"="),e("span",{class:"token number"},"12.2"),n(`.3 +`)])]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),Pe=e("div",{class:"language-bash ext-sh line-numbers-mode"},[e("pre",{class:"language-bash"},[e("code",null,`TODO +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"})])],-1),Te=e("div",{class:"language-bash ext-sh line-numbers-mode"},[e("pre",{class:"language-bash"},[e("code",null,[e("span",{class:"token builtin class-name"},"set"),n(),e("span",{class:"token assign-left variable"},"ATOM_HOME"),e("span",{class:"token operator"},"="),n("C:"),e("span",{class:"token punctuation"},"\\"),n("Users"),e("span",{class:"token punctuation"},"\\"),e("span",{class:"token operator"},"<"),n("user"),e("span",{class:"token operator"},">"),e("span",{class:"token punctuation"},"\\"),n(`.pulsar +`),e("span",{class:"token builtin class-name"},"set"),n(),e("span",{class:"token assign-left variable"},"APM_PATH"),e("span",{class:"token operator"},"="),e("span",{class:"token punctuation"},"\\"),n("ppm"),e("span",{class:"token punctuation"},"\\"),n("bin"),e("span",{class:"token punctuation"},"\\"),n(`apm +`)])]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),Ce=t(`

    You can now use the binary to link or install packages.

    For example to install the ide-java package from source:

    # clone the repository and cd into it
    +git clone https://github.com/pulsar-edit/ide-java
    +cd ide-java
    +
    +# from the directory where you are running pulsar source code
    +<pulsar source>/ppm/bin/apm link
    +

    Hacking on the Core

    You will first want to build and run Pulsar from source.

    Running in Development Mode

    Once you have a local copy of Pulsar cloned and built, you can then run Pulsar in Development Mode. But first, if you cloned Pulsar to somewhere other than LNX/MAC: ~/github/pulsar - WIN: %USERPROFILE%\\github\\pulsar you will need to set the ATOM_DEV_RESOURCE_PATH environment variable to point to the folder in which you cloned Pulsar. To run Pulsar in Dev Mode, use the --dev parameter from the terminal:

    $ pulsar --dev <path-to-open>
    +

    There are a couple benefits of running Pulsar in Dev Mode:

    `,9),Ie=t("
  • When the ATOM_DEV_RESOURCE_PATH environment variable is set correctly, Pulsar is run using the source code from your local pulsar-edit/pulsar repository. This means you don't have to rebuild after every change, just restart Pulsar \u{1F44D}
  • Packages that exist in LNX/MAC: ~/.pulsar/dev/packages - WIN: %USERPROFILE%\\.pulsar\\dev\\packages are loaded instead of packages of the same name normally loaded from other locations. This means that you can have development versions of packages you use loaded but easily go back to the stable versions by launching without Dev Mode.
  • ",2),Se={href:"https://github.com/pulsar-edit/pulsar/tree/master/packages/dev-live-reload",target:"_blank",rel:"noopener noreferrer"},je=e("code",null,"window:reload",-1),Ae=t(`

    Running Pulsar Core Tests Locally

    In order to run Pulsar Core tests from the terminal, first be certain to set the ATOM_DEV_RESOURCE_PATH environment variable as mentioned above and then:

    $ cd <path-to-your-local-pulsar-repo>
    +$ pulsar --test spec
    +

    Tools of the Trade

    To begin, there are a few things we'll assume you know, at least to some degree. Since all of Pulsar is implemented using web technologies, we have to assume you know web technologies such as JavaScript and CSS. Specifically, we'll be using Less, which is a preprocessor for CSS.

    `,5),Ne={href:"https://github.com/pulsar-edit/.github/blob/main/project-birth/CONTRIBUTING-DURING-START.md#decaffeination",target:"_blank",rel:"noopener noreferrer"},Re=t(`
    MyPackageView = require './my-package-view'
    +
    +module.exports =
    +  myPackageView: null
    +
    +  activate: (state) ->
    +    @myPackageView = new MyPackageView(state.myPackageViewState)
    +
    +  deactivate: ->
    +    @myPackageView.destroy()
    +
    +  serialize: ->
    +    myPackageViewState: @myPackageView.serialize()
    +
    `,1),Me={href:"http://coffeescript.org",target:"_blank",rel:"noopener noreferrer"},Le={href:"http://lesscss.org/",target:"_blank",rel:"noopener noreferrer"},We=e("h2",{id:"the-init-file",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#the-init-file","aria-hidden":"true"},"#"),n(" The Init File")],-1),Oe={class:"custom-container info"},Ue=e("p",{class:"custom-container-title"},"Info",-1),Ee=e("code",null,"init.coffee",-1),Fe=e("code",null,"init.js",-1),De=e("strong",null,[e("em",null,"LNX/MAC")],-1),Ye=e("code",null,"~/.pulsar",-1),ze=e("strong",null,[e("em",null,"WIN")],-1),Ve=e("code",null,"%USERPROFILE%\\.pulsar",-1),He={href:"https://atom.io/docs/api/latest",target:"_blank",rel:"noopener noreferrer"},Ge=e("a",{href:"#package-word-count"},"Package: Word Count",-1),$e=t(`

    You can open the init.js file in an editor from the LNX: Atom > Init Script - MAC: File > Init Script - WIN: Edit > Init Script menu.

    For example, if you have the Audio Beep configuration setting enabled, you could add the following code to your init.js file to have Pulsar greet you with an audio beep every time it loads:

    atom.beep();
    +
    `,3),Be=e("code",null,"init.js",-1),Xe={href:"https://atom.io/docs/api/latest/Selection",target:"_blank",rel:"noopener noreferrer"},Je={href:"https://atom.io/docs/api/latest/Clipboard",target:"_blank",rel:"noopener noreferrer"},Ke=t(`
    atom.commands.add("atom-text-editor", "markdown:paste-as-link", () => {
    +	let clipboardText, editor, selection;
    +	if (!(editor = atom.workspace.getActiveTextEditor())) {
    +		return;
    +	}
    +	selection = editor.getLastSelection();
    +	clipboardText = atom.clipboard.read();
    +	return selection.insertText(
    +		"[" + selection.getText() + "](" + clipboardText + ")"
    +	);
    +});
    +

    Now, reload Pulsar and use the Command Palette to execute the new command, Markdown: Paste As Link, by name. And if you'd like to trigger the command via a keyboard shortcut, you can define a keybinding for the command.

    Package: Word Count

    Let's get started by writing a very simple package and looking at some of the tools needed to develop one effectively. We'll start by writing a package that tells you how many words are in the current buffer and display it in a small modal window.

    Package Generator

    `,5),Ze={href:"https://github.com/pulsar-edit/package-generator",target:"_blank",rel:"noopener noreferrer"},Qe=e("p",null,[n('You can run the generator by invoking the command palette and searching for "Generate Package". A dialog will appear asking you to name your new project. Name it '),e("code",null,"your-name-word-count"),n(". Pulsar will then create that directory and fill it out with a skeleton project and link it into your "),e("strong",null,[e("em",null,"LNX/MAC")]),n(": "),e("code",null,"~/.pulsar/packages"),n(" - "),e("strong",null,[e("em",null,"WIN")]),n(": "),e("code",null,"%USERPROFILE%\\.pulsar\\packages"),n(" directory so it's loaded when you launch your editor next time.")],-1),en={class:"custom-container note"},nn=e("p",{class:"custom-container-title"},"Note",-1),an={href:"https://web.pulsar-edit.dev/packages",target:"_blank",rel:"noopener noreferrer"},sn=e("code",null,"your-name-word-count",-1),tn=e("em",null,"should",-1),on=t('

    Basic generated Pulsar package

    You can see that Pulsar has created about a dozen files that make up the package. Let's take a look at each of them to get an idea of how a package is structured, then we can modify them to get our word count functionality.

    The basic package layout is as follows:

    my-package/
    +\u251C\u2500 grammars/
    +\u251C\u2500 keymaps/
    +\u251C\u2500 lib/
    +\u251C\u2500 menus/
    +\u251C\u2500 spec/
    +\u251C\u2500 snippets/
    +\u251C\u2500 styles/
    +\u251C\u2500 index.js
    +\u2514\u2500 package.json
    +

    Not every package will have (or need) all of these directories and the package generator doesn't create snippets or grammars. Let's see what some of these are so we can start messing with them.

    package.json

    `,6),ln={href:"https://en.wikipedia.org/wiki/Npm_(software)",target:"_blank",rel:"noopener noreferrer"},rn=e("code",null,"package.json",-1),pn={href:"https://docs.npmjs.com/files/package.json",target:"_blank",rel:"noopener noreferrer"},cn=e("code",null,"package.json",-1),un=e("code",null,"package.json",-1),dn=t(`
    • main: the path to the JavaScript file that's the entry point to your package. If this is missing, Pulsar will default to looking for an index.js or index.coffee.
    • styles: an Array of Strings identifying the order of the style sheets your package needs to load. If not specified, style sheets in the styles directory are added alphabetically.
    • keymaps: an Array of Strings identifying the order of the key mappings your package needs to load. If not specified, mappings in the keymaps directory are added alphabetically.
    • menus: an Array of Strings identifying the order of the menu mappings your package needs to load. If not specified, mappings in the menus directory are added alphabetically.
    • snippets: an Array of Strings identifying the order of the snippets your package needs to load. If not specified, snippets in the snippets directory are added alphabetically.
    • activationCommands: an Object identifying commands that trigger your package's activation. The keys are CSS selectors, the values are Arrays of Strings identifying the command. The loading of your package is delayed until one of these events is triggered within the associated scope defined by the CSS selector. If not specified, the activate() method of your main export will be called when your package is loaded.
    • activationHooks: an Array of Strings identifying hooks that trigger your package's activation. The loading of your package is delayed until one of these hooks are triggered. Currently, there are three activation hooks:
      • core:loaded-shell-environment for when Pulsar has finished loading the shell environment variables
      • scope.name:root-scope-used for when a file is opened from the specified language (e.g. source.ruby:root-scope-used)
      • language-package-name:grammar-used for when a specific language package is used (e.g., my-special-language-javascript:grammar-used)
    • workspaceOpeners: An Array of Strings identifying URIs that trigger your package's activation. For example, say your package registers a custom opener for atom://my-custom-panel. By including that string in workspaceOpeners, your package will defer its activation until that URI is opened.

    The package.json in the package we've just generated looks like this currently:

    {
    +	"name": "your-name-word-count",
    +	"main": "./lib/your-name-word-count",
    +	"version": "0.0.0",
    +	"description": "A short description of your package",
    +	"activationCommands": {
    +		"atom-workspace": "your-name-word-count:toggle"
    +	},
    +	"repository": "https://github.com/pulsar-edit/your-name-word-count",
    +	"license": "MIT",
    +	"engines": {
    +		"atom": ">=1.0.0 <2.0.0"
    +	},
    +	"dependencies": {}
    +}
    +

    If you wanted to use activationHooks, you might have:

    {
    +	"name": "your-name-word-count",
    +	"main": "./lib/your-name-word-count",
    +	"version": "0.0.0",
    +	"description": "A short description of your package",
    +	"activationHooks": [
    +		"language-javascript:grammar-used",
    +		"language-coffee-script:grammar-used"
    +	],
    +	"repository": "https://github.com/pulsar-edit/your-name-word-count",
    +	"license": "MIT",
    +	"engines": {
    +		"atom": ">=1.0.0 <2.0.0"
    +	},
    +	"dependencies": {}
    +}
    +

    One of the first things you should do is ensure that this information is filled out. The name, description, repository URL the project will be at, and the license can all be filled out immediately. The other information we'll get into more detail on as we go.

    WARNING

    Do not forget to update the repository URL. The one generated for you is invalid by design and will prevent you from publishing your package until updated.

    Source Code

    If you want to extend Pulsar's behavior, your package should contain a single top-level module, which you export from whichever file is indicated by the main key in your package.json file. In the package we just generated, the main package file is lib/your-name-word-count.js. The remainder of your code should be placed in the lib directory, and required from your top-level file. If the main key is not in your package.json file, it will look for index.js or index.coffee as the main entry point.

    Your package's top-level module is a singleton object that manages the lifecycle of your extensions to Pulsar. Even if your package creates ten different views and appends them to different parts of the DOM, it's all managed from your top-level object.

    Your package's top-level module can implement the following basic methods:

    • activate(state): This optional method is called when your package is activated. It is passed the state data from the last time the window was serialized if your module implements the serialize() method. Use this to do initialization work when your package is started (like setting up DOM elements or binding events). If this method returns a promise the package will be considered loading until the promise resolves (or rejects).
    • initialize(state): This optional method is similar to activate() but is called earlier. Whereas activation occurs after the workspace has been deserialized (and can therefore happen after your package's deserializers have been called), initialize() is guaranteed to be called before everything. Use activate() if you want to be sure that the workspace is ready; use initialize() if you need to do some setup prior to your deserializers or view providers being invoked.
    • serialize(): This optional method is called when the window is shutting down, allowing you to return JSON to represent the state of your component. When the window is later restored, the data you returned is passed to your module's activate method so you can restore your view to where the user left off.
    • deactivate(): This optional method is called when the window is shutting down and when the package is disabled. If your package is watching any files or holding external resources in any other way, release them here. You should also dispose of all subscriptions you're holding on to.

    Style Sheets

    `,13),hn=e("code",null,"styles",-1),mn={href:"http://lesscss.org",target:"_blank",rel:"noopener noreferrer"},kn=t("

    Ideally, you won't need much in the way of styling. Pulsar provides a standard set of components which define both the colors and UI elements for any package that fits into Pulsar seamlessly. You can view all of Pulsar's UI components by opening the styleguide: open the command palette LNX/WIN: Ctrl+Shift+P - MAC: Cmd+Shift+P and search for styleguide, or type LNX/WIN: Ctrl+Shift+G - MAC: Cmd+Ctrl+Shift+G

    ",1),gn=e("em",null,"do",-1),vn=e("em",null,"must",-1),bn={href:"https://github.com/pulsar-edit/atom-dark-ui/blob/master/styles/ui-variables.less",target:"_blank",rel:"noopener noreferrer"},fn=t(`

    An optional styleSheets array in your package.json can list the style sheets by name to specify a loading order; otherwise, style sheets are loaded alphabetically.

    Keymaps

    You can provide key bindings for commonly used actions for your extension, especially if you're also adding a new command. In our new package, we have a keymap filled in for us already in the keymaps/your-name-word-count.json file:

    {
    +  "atom-workspace": {
    +    "ctrl-alt-o": "your-name-word-count:toggle"
    +  }
    +}
    +

    This means that if you press Alt+Ctrl+O, our package will run the your-name-word-count:toggle command. We'll look at that code next, but if you want to change the default key mapping, you can do that in this file.

    Keymaps are placed in the keymaps subdirectory. By default, all keymaps are loaded in alphabetical order. An optional keymaps array in your package.json can specify which keymaps to load and in what order.

    Keybindings are executed by determining which element the keypress occurred on. In the example above, the your-name-word-count:toggle command is executed when pressing Alt+Ctrl+O on the atom-workspace element. Because the atom-workspace element is the parent of the entire Pulsar UI, this means the key combination will work anywhere in the application.

    We'll cover more advanced keybinding stuff a bit later in Keymaps in Depth.

    Menus are placed in the menus subdirectory. This defines menu elements like what pops up when you right click a context-menu or would go in the application menu to trigger functionality in your package.

    By default, all menus are loaded in alphabetical order. An optional menus array in your package.json can specify which menus to load and in what order.

    Application Menu

    It's recommended that you create an application menu item under the Packages menu for common actions with your package that aren't tied to a specific element. If we look in the menus/your-name-word-count.json file that was generated for us, we'll see a section that looks like this:

    
    +"menu": [
    +  {
    +    "label": "Packages",
    +    "submenu": [
    +      {
    +        "label": "Word Count",
    +        "submenu": [
    +          {
    +            "label": "Toggle",
    +            "command": "your-name-word-count:toggle"
    +          }
    +        ]
    +      }
    +    ]
    +  }
    +]
    +
    +

    This section puts a "Toggle" menu item under a menu group named "Your Name Word Count" in the "Packages" menu.

    Application Menu Item

    When you select that menu item, it will run the your-name-word-count:toggle command, which we'll look at in a bit.

    The menu templates you specify are merged with all other templates provided by other packages in the order which they were loaded.

    Context Menu

    It's recommended to specify a context menu item for commands that are linked to specific parts of the interface. In our menus/your-name-word-count.json file, we can see an auto-generated section that looks like this:

    "context-menu": {
    +    "atom-text-editor": [
    +      {
    +        "label": "Toggle your-name-word-count",
    +        "command": "your-name-word-count:toggle"
    +      }
    +    ]
    +  }
    +

    This adds a "Toggle Word Count" menu option to the menu that pops up when you right-click in an Pulsar text editor pane.

    Context Menu Entry

    When you click that it will again run the your-name-word-count:toggle method in your code.

    Context menus are created by determining which element was selected and then adding all of the menu items whose selectors match that element (in the order which they were loaded). The process is then repeated for the elements until reaching the top of the DOM tree.

    You can also add separators and submenus to your context menus. To add a submenu, provide a submenu key instead of a command. To add a separator, add an item with a single type: 'separator' key/value pair. For instance, you could do something like this:

    {
    +  "context-menu": {
    +    "atom-workspace": [
    +      {
    +        "label": "Text",
    +        "submenu": [
    +          {
    +            "label": "Inspect Element",
    +            "command": "core:inspect"
    +          },
    +          {
    +            "type": "separator"
    +          },
    +          {
    +            "label": "Selector All",
    +            "command": "core:select-all"
    +          },
    +          {
    +            "type": "separator"
    +          },
    +          {
    +            "label": "Deleted Selected Text",
    +            "command": "core:delete"
    +          }
    +        ]
    +      }
    +    ]
    +  }
    +}
    +

    Developing Our Package

    Currently with the generated package we have, if we run that your-name-word-count:toggle command through the menu or the command palette, we'll get a dialog that says "The YourNameWordCount package is Alive! It's ALIVE!".

    Wordcount Package is Alive Dialog

    Understanding the Generated Code

    Let's take a look at the code in our lib directory and see what is happening.

    There are two files in our lib directory. One is the main file (lib/your-name-word-count.js), which is pointed to in the package.json file as the main file to execute for this package. This file handles the logic of the whole package.

    The second file is a View class, lib/your-name-word-count-view.js, which handles the UI elements of the package. Let's look at this file first, since it's pretty simple.

    export default class YourNameWordCountView {
    +	constructor(serializedState) {
    +		// Create root element
    +		this.element = document.createElement("div");
    +		this.element.classList.add("your-name-word-count");
    +
    +		// Create message element
    +		const message = document.createElement("div");
    +		message.textContent = "The YourNameWordCount package is Alive! It's ALIVE!";
    +		message.classList.add("message");
    +		this.element.appendChild(message);
    +	}
    +
    +	// Returns an object that can be retrieved when package is activated
    +	serialize() {}
    +
    +	// Tear down any state and detach
    +	destroy() {
    +		this.element.remove();
    +	}
    +
    +	getElement() {
    +		return this.element;
    +	}
    +}
    +

    Basically the only thing happening here is that when the View class is created, it creates a simple div element and adds the your-name-word-count class to it (so we can find or style it later) and then adds the "Your Name Word Count package is Alive!" text to it. There is also a getElement method which returns that div. The serialize and destroy methods don't do anything and we won't have to worry about that until another example.

    Notice that we're simply using the basic browser DOM methods: createElement() and appendChild().

    The second file we have is the main entry point to the package. Again, because it's referenced in the package.json file. Let's take a look at that file.

    import YourNameWordCountView from "./your-name-word-count-view";
    +import { CompositeDisposable } from "atom";
    +
    +export default {
    +	yourNameWordCountView: null,
    +	modalPanel: null,
    +	subscriptions: null,
    +
    +	activate(state) {
    +		this.yourNameWordCountView = new YourNameWordCountView(
    +			state.yourNameWordCountViewState
    +		);
    +		this.modalPanel = atom.workspace.addModalPanel({
    +			item: this.yourNameWordCountView.getElement(),
    +			visible: false,
    +		});
    +
    +		// Events subscribed to in Pulsar's system can be easily cleaned up with a CompositeDisposable
    +		this.subscriptions = new CompositeDisposable();
    +
    +		// Register command that toggles this view
    +		this.subscriptions.add(
    +			atom.commands.add("atom-workspace", {
    +				"your-name-word-count:toggle": () => this.toggle(),
    +			})
    +		);
    +	},
    +
    +	deactivate() {
    +		this.modalPanel.destroy();
    +		this.subscriptions.dispose();
    +		this.yourNameWordCountView.destroy();
    +	},
    +
    +	serialize() {
    +		return {
    +			yourNameWordCountViewState: this.yourNameWordCountView.serialize(),
    +		};
    +	},
    +
    +	toggle() {
    +		console.log("YourNameWordCount was toggled!");
    +		return this.modalPanel.isVisible()
    +			? this.modalPanel.hide()
    +			: this.modalPanel.show();
    +	},
    +};
    +

    There is a bit more going on here. First of all we can see that we are defining four methods. The only required one is activate. The deactivate and serialize methods are expected by Pulsar but optional. The toggle method is one Pulsar is not looking for, so we'll have to invoke it somewhere for it to be called, which you may recall we do both in the activationCommands section of the package.json file and in the action we have in the menu file.

    The deactivate method simply destroys the various class instances we've created and the serialize method simply passes on the serialization to the View class. Nothing too exciting here.

    The activate command does a number of things. For one, it is not called automatically when Pulsar starts up, it is first called when one of the activationCommands as defined in the package.json file are called. In this case, activate is only called the first time the toggle command is called. If nobody ever invokes the menu item or hotkey, this code is never called.

    This method does two things. The first is that it creates an instance of the View class we have and adds the element that it creates to a hidden modal panel in the Pulsar workspace.

    this.yourNameWordCountView = new YourNameWordCountView(
    +	state.yourNameWordCountViewState
    +);
    +this.modalPanel = atom.workspace.addModalPanel({
    +	item: this.yourNameWordCountView.getElement(),
    +	visible: false,
    +});
    +

    We'll ignore the state stuff for now, since it's not important for this simple package. The rest should be fairly straightforward.

    The next thing this method does is create an instance of the CompositeDisposable class so it can register all the commands that can be called from the package so other packages could subscribe to these events.

    // Events subscribed to in Pulsar's system can be easily cleaned up with a CompositeDisposable
    +this.subscriptions = new CompositeDisposable();
    +
    +// Register command that toggles this view
    +this.subscriptions.add(
    +	atom.commands.add("atom-workspace", {
    +		"your-name-word-count:toggle": () => this.toggle(),
    +	})
    +);
    +

    Next we have the toggle method. This method simply toggles the visibility of the modal panel that we created in the activate method.

    toggle() {
    +  console.log('YourNameWordCount was toggled!');
    +  return (
    +    this.modalPanel.isVisible() ?
    +    this.modalPanel.hide() :
    +    this.modalPanel.show()
    +  );
    +}
    +

    This should be fairly simple to understand. We're looking to see if the modal element is visible and hiding or showing it depending on its current state.

    The Flow

    So, let's review the actual flow in this package.

    1. Pulsar starts up
    2. Pulsar starts loading packages
    3. Pulsar reads your package.json
    4. Pulsar loads keymaps, menus, styles and the main module
    5. Pulsar finishes loading packages
    6. At some point, the user executes your package command your-name-word-count:toggle
    7. Pulsar executes the activate method in your main module which sets up the UI by creating the hidden modal view
    8. Pulsar executes the package command your-name-word-count:toggle which reveals the hidden modal view
    9. At some point, the user executes the your-name-word-count:toggle command again
    10. Pulsar executes the command which hides the modal view
    11. Eventually, Pulsar is shut down which can trigger any serializations that your package has defined

    Tip

    Keep in mind that the flow will be slightly different if you choose not to use activationCommands in your package.

    Counting the Words

    So now that we understand what is happening, let's modify the code so that our little modal box shows us the current word count instead of static text.

    We'll do this in a very simple way. When the dialog is toggled, we'll count the words right before displaying the modal. So let's do this in the toggle command. If we add some code to count the words and ask the view to update itself, we'll have something like this:

    toggle() {
    +  if (this.modalPanel.isVisible()) {
    +    this.modalPanel.hide();
    +  } else {
    +    const editor = atom.workspace.getActiveTextEditor();
    +    const words = editor.getText().split(/\\s+/).length;
    +    this.yourNameWordCountView.setCount(words);
    +    this.modalPanel.show();
    +  }
    +}
    +
    `,58),yn={href:"https://atom.io/docs/api/latest/Workspace#instance-getActiveTextEditor",target:"_blank",rel:"noopener noreferrer"},wn=e("code",null,"atom.workspace.getActiveTextEditor()",-1),_n={href:"https://atom.io/docs/api/latest/TextEditor#instance-getText",target:"_blank",rel:"noopener noreferrer"},xn=e("code",null,"getText()",-1),qn=t(`

    Finally, we tell our view to update the word count it displays by calling the setCount() method on our view and then showing the modal again. Since that method doesn't yet exist, let's create it now.

    We can add this code to the end of our your-name-word-count-view.js file:

    setCount(count) {
    +  const displayText = \`There are \${count} words.\`;
    +  this.element.children[0].textContent = displayText;
    +}
    +

    Pretty simple! We take the count number that was passed in and place it into a string that we then stick into the element that our view is controlling.

    Note

    To see your changes, you'll need to reload the code. You can do this by reloading the window (The window:reload command in the Command Palette). A common practice is to have two Pulsar windows, one for developing your package, and one for testing and reloading.

    Word Count Working

    Basic Debugging

    You'll notice a few console.log statements in the code. One of the cool things about Pulsar being built on Chromium is that you can use some of the same debugging tools available to you that you have when doing web development.

    To open up the Developer Console, press LNX/WIN: Ctrl+Shift+I - MAC: Alt+Cmd+I or choose the menu option View > Developer > Toggle Developer Tools.

    Developer Tools Debugging

    From here you can inspect objects, run code and view console output just as though you were debugging a web site.

    Testing

    Your package should have tests, and if they're placed in the spec directory, they can be run by Pulsar.

    ',13),Pn={href:"https://jasmine.github.io/archives/1.3/introduction",target:"_blank",rel:"noopener noreferrer"},Tn=t('

    Running Tests

    Once you've got your test suite written, you can run it by pressing LNX/WIN: Alt+Ctrl+P - MAC: Alt+Cmd+Ctrl+P or via the View > Developer > Run Package Specs menu. Our generated package comes with an example test suite, so you can run this right now to see what happens.

    Spec Suite Results

    You can also use the pulsar --test spec command to run them from the command line. It prints the test output and results to the console and returns the proper status code depending on whether the tests passed or failed.

    Summary

    We've now generated, customized and tested our first package for Pulsar. Congratulations! Now let's go ahead and publish it so it's available to the world.

    Package: Modifying Text

    ',7),Cn={href:"https://en.wikipedia.org/wiki/ASCII_art",target:"_blank",rel:"noopener noreferrer"},In=e("pre",null,` o888 + ooooooo ooooooo ooooooo 888 + 888 888 888 888 888 888 888 + 888 888 888 888 888 888 + 88ooo888 88ooo88 88ooo88 o888o + +`,-1),Sn=e("p",null,"This should demonstrate how to do basic text manipulation in the current text buffer and how to deal with selections.",-1),jn=e("p",null,"The final package can be viewed at https://github.com/pulsar-edit/ascii-art.",-1),An=e("h3",{id:"basic-text-insertion",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#basic-text-insertion","aria-hidden":"true"},"#"),n(" Basic Text Insertion")],-1),Nn=e("strong",null,[e("em",null,"LNX/WIN")],-1),Rn=e("kbd",null,"Ctrl+Shift+P",-1),Mn=e("strong",null,[e("em",null,"MAC")],-1),Ln=e("kbd",null,"Cmd+Shift+P",-1),Wn={href:"https://github.com/pulsar-edit/command-palette",target:"_blank",rel:"noopener noreferrer"},On=e("a",{href:"#package-generator"},"the section on package generation",-1),Un=e("code",null,"ascii-art",-1),En=t(`

    Now let's edit the package files to make our ASCII Art package do something interesting. Since this package doesn't need any UI, we can remove all view-related code so go ahead and delete lib/ascii-art-view.js, spec/ascii-art-view-spec.js, and styles/.

    Next, open up lib/ascii-art.js and remove all view code, so it looks like this:

    const { CompositeDisposable } = require("atom");
    +
    +module.exports = {
    +	subscriptions: null,
    +
    +	activate() {
    +		this.subscriptions = new CompositeDisposable();
    +		this.subscriptions.add(
    +			atom.commands.add("atom-workspace", {
    +				"ascii-art:convert": () => this.convert(),
    +			})
    +		);
    +	},
    +
    +	deactivate() {
    +		this.subscriptions.dispose();
    +	},
    +
    +	convert() {
    +		console.log("Convert text!");
    +	},
    +};
    +

    Create a Command

    Now let's add a command. You should namespace your commands with the package name followed by a : and then the name of the command. As you can see in the code, we called our command ascii-art:convert and we will define it to call the convert() method when it's executed.

    So far, that will simply log to the console. Let's start by making it insert something into the text buffer.

    convert() {
    +  const editor = atom.workspace.getActiveTextEditor()
    +  if (editor) {
    +    editor.insertText('Hello, World!')
    +  }
    +}
    +

    As in Counting Words, we're using atom.workspace.getActiveTextEditor() to get the object that represents the active text editor. If this convert() method is called when not focused on a text editor, nothing will happen.

    `,8),Fn={href:"https://atom.io/docs/api/latest/TextEditor#instance-insertText",target:"_blank",rel:"noopener noreferrer"},Dn=e("code",null,"insertText()",-1),Yn=t(`

    Reload the Package

    Before we can trigger ascii-art:convert, we need to load the latest code for our package by reloading the window. Run the command "Window: Reload" from the Command Palette or by pressing LNX/WIN: Ctrl+Shift+F5 - MAC: Alt+Cmd+Ctrl+L

    Trigger the Command

    Now open the Command Palette and search for the Ascii Art: Convert command. But it's not there! To fix this, open package.json and find the property called activationCommands. Activation commands make Pulsar launch faster by allowing Pulsar to delay a package's activation until it's needed. So remove the existing command and use ascii-art:convert in activationCommands:

    "activationCommands": {
    +  "atom-workspace": "ascii-art:convert"
    +}
    +

    First, reload the window by running the command "Window: Reload" from the command palette. Now when you run the Ascii Art: Convert command it will insert "Hello, World!" into the active editor, if any.

    Add a Key Binding

    Now let's add a key binding to trigger the ascii-art:convert command. Open keymaps/ascii-art.json and add a key binding linking Alt+Ctrl+A to the ascii-art:convert command. You can delete the pre-existing key binding since you won't need it anymore.

    When finished, the file should look like this:

    {
    +  "atom-text-editor": {
    +    "ctrl-alt-a": "ascii-art:convert"
    +  }
    +}
    +
    +

    Now reload the window and verify that the key binding works.

    WARNING

    The Pulsar keymap system is case-sensitive. This means that there is a distinction between a and A when creating keybindings. a means that you want to trigger the keybinding when you press A. But A means that you want to trigger the keybinding when you press Shift+A. You can also write shift-a when you want to trigger the keybinding when you press Shift+A.

    We strongly recommend always using lowercase and explicitly spelling out when you want to include Shift in your keybindings.

    Add the ASCII Art

    `,13),zn={href:"https://npmjs.org/package/figlet",target:"_blank",rel:"noopener noreferrer"},Vn={href:"https://npmjs.org/",target:"_blank",rel:"noopener noreferrer"},Hn=e("code",null,"package.json",-1),Gn=t(`
    "dependencies": {
    +  "figlet": "1.0.8"
    +}
    +

    After saving the file, run the command Update Package Dependencies: Update from the Command Palette. This will install the package's node module dependencies, only figlet in this case. You will need to run Update Package Dependencies: Update whenever you update the dependencies field in your package.json file.

    If for some reason this doesn't work, you'll see a message saying "Failed to update package dependencies" and you will find a new npm-debug.log file in your directory. That file should give you some idea as to what went wrong.

    Now require the figlet node module in lib/ascii-art.js and instead of inserting "Hello, World!", convert the selected text to ASCII art.

    convert () {
    +  const editor = atom.workspace.getActiveTextEditor()
    +  if (editor) {
    +    const selection = editor.getSelectedText()
    +
    +    const figlet = require('figlet')
    +    const font = 'o8'
    +    figlet(selection, {font}, function (error, art) {
    +      if (error) {
    +        console.error(error)
    +      } else {
    +        editor.insertText(\`\\n\${art}\\n\`)
    +      }
    +    })
    +  }
    +}
    +

    Now reload the editor, select some text in an editor window and press Alt+Ctrl+A. It should be replaced with a ridiculous ASCII art version instead.

    `,6),$n={href:"https://atom.io/docs/api/latest/TextEditor#instance-getSelectedText",target:"_blank",rel:"noopener noreferrer"},Bn=e("code",null,"editor.getSelectedText()",-1),Xn={href:"https://atom.io/docs/api/latest/TextEditor#instance-insertText",target:"_blank",rel:"noopener noreferrer"},Jn=e("code",null,"editor.insertText()",-1),Kn=t('

    Summary

    In this section, we've made a UI-less package that takes selected text and replaces it with a processed version. This could be helpful in creating linters or checkers for your code.

    Package: Active Editor Info

    We saw in our Word Count package how we could show information in a modal panel. However, panels aren't the only way to extend Pulsar's UI\u2014you can also add items to the workspace. These items can be dragged to new locations (for example, one of the docks on the edges of the window), and Pulsar will restore them the next time you open the project.

    ',4),Zn={href:"https://github.com/pulsar-edit/active-editor-info",target:"_blank",rel:"noopener noreferrer"},Qn=e("h3",{id:"create-the-package",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#create-the-package","aria-hidden":"true"},"#"),n(" Create the Package")],-1),ea=e("strong",null,[e("em",null,"LNX/WIN")],-1),na=e("kbd",null,"Ctrl+Shift+P",-1),aa=e("strong",null,[e("em",null,"MAC")],-1),sa=e("kbd",null,"Cmd+Shift+P",-1),ta={href:"https://github.com/pulsar-edit/command-palette",target:"_blank",rel:"noopener noreferrer"},oa=e("code",null,"Package Generator: Generate Package",-1),ia=e("a",{href:"#package-generator"},"the section on package generation",-1),la=e("code",null,"active-editor-info",-1),ra=t(`

    Add an Opener

    Now let's edit the package files to show our view in a workspace item instead of a modal panel. The way we do this is by registering an opener with Pulsar. Openers are just functions that accept a URI and return a view (if it's a URI that the opener knows about). When you call atom.workspace.open(), Pulsar will go through all of its openers until it finds one that can handle the URI you passed.

    Let's open lib/active-editor-info.js and edit our activate() method to register an opener:

    "use babel";
    +
    +import ActiveEditorInfoView from "./active-editor-info-view";
    +import { CompositeDisposable, Disposable } from "atom";
    +
    +export default {
    +	subscriptions: null,
    +
    +	activate(state) {
    +		this.subscriptions = new CompositeDisposable(
    +			// Add an opener for our view.
    +			atom.workspace.addOpener((uri) => {
    +				if (uri === "atom://active-editor-info") {
    +					return new ActiveEditorInfoView();
    +				}
    +			}),
    +
    +			// Register command that toggles this view
    +			atom.commands.add("atom-workspace", {
    +				"active-editor-info:toggle": () => this.toggle(),
    +			}),
    +
    +			// Destroy any ActiveEditorInfoViews when the package is deactivated.
    +			new Disposable(() => {
    +				atom.workspace.getPaneItems().forEach((item) => {
    +					if (item instanceof ActiveEditorInfoView) {
    +						item.destroy();
    +					}
    +				});
    +			})
    +		);
    +	},
    +
    +	deactivate() {
    +		this.subscriptions.dispose();
    +	},
    +
    +	toggle() {
    +		console.log("Toggle it!");
    +	},
    +};
    +

    You'll notice we also removed the activeEditorInfoView property and the serialize() method. That's because, with workspace items, it's possible to have more than one instance of a given view. Since each instance can have its own state, each should do its own serialization instead of relying on a package-level serialize() method. We'll come back to that later.

    You probably also noticed that our toggle() implementation just logs the text "Toggle it!" to the console. Let's make it actually toggle our view:

      toggle() {
    +    atom.workspace.toggle('atom://active-editor-info');
    +  }
    +

    Updating the View

    Pulsar uses the same view abstractions everywhere, so we can almost use the generated ActiveEditorInfoView class as-is. We just need to add two small methods:

      getTitle() {
    +    // Used by Pulsar for tab text
    +    return 'Active Editor Info';
    +  }
    +
    +  getURI() {
    +    // Used by Pulsar to identify the view when toggling.
    +    return 'atom://active-editor-info';
    +  }
    +

    Now reload the window and run the Active Editor Info: Toggle command from the command palette! Our view will appear in a new tab in the center of the workspace. If you want, you can drag it into one of the docks. Toggling it again will then hide that dock. If you close the tab and run the toggle command again, it will appear in the last place you had it.

    Note

    We've repeated the same URI three times now. That's okay, but it's probably a good idea to define the URL in one place and then import it from that module wherever you need it.

    Constraining Our Item's Locations

    The purpose of our view is to show information about the active text editor, so it doesn't really make sense to show our item in the center of the workspace (where the text editor will be). Let's add some methods to our view class to influence where its opened:

      getDefaultLocation() {
    +    // This location will be used if the user hasn't overridden it by dragging the item elsewhere.
    +    // Valid values are "left", "right", "bottom", and "center" (the default).
    +    return 'right';
    +  }
    +
    +  getAllowedLocations() {
    +    // The locations into which the item can be moved.
    +    return ['left', 'right', 'bottom'];
    +  }
    +

    Now our item will appear in the right dock initially and users will only be able to drag it to one of the other docks.

    Show Active Editor Info

    Now that we have our view all wired up, let's update it to show some information about the active text editor. Add this to the constructor:

    this.subscriptions = atom.workspace
    +	.getCenter()
    +	.observeActivePaneItem((item) => {
    +		if (!atom.workspace.isTextEditor(item)) {
    +			message.innerText = "Open a file to see important information about it.";
    +			return;
    +		}
    +		message.innerHTML = \`
    +    <h2>\${item.getFileName() || "untitled"}</h2>
    +    <ul>
    +      <li><b>Soft Wrap:</b> \${item.softWrapped}</li>
    +      <li><b>Tab Length:</b> \${item.getTabLength()}</li>
    +      <li><b>Encoding:</b> \${item.getEncoding()}</li>
    +      <li><b>Line Count:</b> \${item.getLineCount()}</li>
    +    </ul>
    +  \`;
    +	});
    +

    Now whenever you open a text editor in the center, the view will update with some information about it.

    WARNING

    We use a template string here because it's simple and we have a lot of control over what's going into it, but this could easily result in the insertion of unwanted HTML if you're not careful. Sanitize your input and use the DOM API or a templating system when doing this for real.

    Also, don't forget to clean up the subscription in the destroy() method:

    destroy() {
    +  this.element.remove();
    +  this.subscriptions.dispose();
    +}
    +

    Serialization

    If you were to reload Atom now, you'd see that our item had disappeared. That's because we haven't told Pulsar how to serialize it yet. Let's do that now.

    The first step is to implement a serialize() method on our ActiveEditorInfoView class. Atom will call the serialize() method on every item in the workspace periodically to save its state.

      serialize() {
    +    return {
    +      // This is used to look up the deserializer function. It can be any string, but it needs to be
    +      // unique across all packages!
    +      deserializer: 'active-editor-info/ActiveEditorInfoView'
    +    };
    +  }
    +

    Note

    All of our view's state is derived from the active text editor so we only need the deserializer field. If we had other state that we wanted to preserve across reloads, we would just add things to the object we're returning. Just make sure that they're JSON serializable!

    Next we need to register a deserializer function that Atom can use to recreate the real object when it starts up. The best way to do that is to add a "deserializers" object to our package.json file:

    {
    +  "name": "active-editor-info",
    +  ...
    +  "deserializers": {
    +    "active-editor-info/ActiveEditorInfoView": "deserializeActiveEditorInfoView"
    +  }
    +}
    +

    Notice that the key ("active-editor-info/ActiveEditorInfoView") matches the string we used in our serialize() method above. The value ("deserializeActiveEditorInfoView") refers to a function in our main module, which we still need to add. Go back to active-editor-info.js and do that now:

      deserializeActiveEditorInfoView(serialized) {
    +    return new ActiveEditorInfoView();
    +  }
    +

    The value returned from our serialize() method will be passed to this function. Since our serialized object didn't include any state, we can just return a new ActiveEditorInfoView instance.

    Reload Pulsar and toggle the view with the Active Editor Info: Toggle command. Then reload Pulsar again. Your view should be just where you left it!

    Summary

    In this section, we've made a toggleable workspace item whose placement can be controlled by the user. This could be helpful when creating all sorts of visual tools for working with code!

    Creating a Theme

    `,37),pa={href:"http://lesscss.org/",target:"_blank",rel:"noopener noreferrer"},ca=t('

    Pulsar supports two types of themes: UI and Syntax. UI themes style elements such as the tree view, the tabs, drop-down lists, and the status bar. Syntax themes style the code, gutter and other elements inside the editor view.

    Theme boundary

    Themes can be installed and changed from the Settings View which you can open by selecting the LNX: Edit > Preferences - MAC: Pulsar > Preferences - WIN: File > Preferences menu, and clicking the "Install" or "Themes" tab on the left hand navigation.

    Getting Started

    Themes are pretty straightforward but it's still helpful to be familiar with a few things before starting:

    ',5),ua={href:"https://speakerdeck.com/danmatthews/less-css",target:"_blank",rel:"noopener noreferrer"},da=t('
  • You may also want to review the concept of a package.json (as covered in Pulsar package.json). This file is used to help distribute your theme to Pulsar users.
  • Your theme's package.json must contain a theme key with a value of ui or syntax for Pulsar to recognize and load it as a theme.
  • ',2),ha={href:"https://web.pulsar-edit.dev/packages",target:"_blank",rel:"noopener noreferrer"},ma=t('

    Creating a Syntax Theme

    Let's create your first theme.

    To get started, press LNX/WIN: Ctrl+Shift+P - MAC: Cmd+Shift+P and start typing Generate Syntax Theme to generate a new theme package. Select Generate Syntax Theme, and you'll be asked for the path where your theme will be created. Let's call ours motif-syntax.

    Tip

    Syntax themes should end with -syntax and UI themes should end with -ui.

    Pulsar will display a new window, showing the motif-syntax theme, with a default set of folders and files created for us. If you open the Settings View with LNX/WIN: Ctrl+, - MAC: Cmd+, and click the "Themes" tab on the left, you'll see the "Motif" theme listed in the "Syntax Theme" drop-down. Select it from the menu to activate it, now when you open an editor you should see your new motif-syntax theme in action.

    Open up styles/colors.less to change the various color variables which have already been defined. For example, turn @red into #f4c2c1.

    Then open styles/base.less and modify the various selectors that have already been defined. These selectors style different parts of code in the editor such as comments, strings and the line numbers in the gutter.

    As an example, let's make the .gutter background-color into @red.

    Reload Pulsar by pressing LNX/WIN: Alt+Ctrl+R - MAC: Alt+Cmd+Ctrl+L to see the changes you made reflected in your Pulsar window. Pretty neat!

    Tip

    You can avoid reloading to see changes you make by opening an Pulsar window in Dev Mode. To open a Dev Mode Pulsar window run pulsar --dev . in the terminal, or use the View > Developer > Open in Dev Mode menu. When you edit your theme, changes will instantly be reflected!

    Note

    It's advised to not specify a font-family in your syntax theme because it will override the Font Family field in Pulsar's settings. If you still like to recommend a font that goes well with your theme, we suggest you do so in your README.

    Creating a UI Theme

    To create a UI theme, do the following:

    ',13),ka={href:"https://github.com/pulsar-edit/ui-theme-template",target:"_blank",rel:"noopener noreferrer"},ga=t("
  • Clone the forked repository to the local filesystem
  • Open a terminal in the forked theme's directory
  • Open your new theme in a Dev Mode Pulsar window run pulsar --dev . in the terminal or use the View > Developer > Open in Dev Mode menu
  • Change the name of the theme in the theme's package.json file
  • Name your theme end with a -ui, for example super-white-ui
  • Run pulsar -p link --dev to symlink your repository to LNX/MAC: ~/.pulsar/dev/packages - WIN: %USERPROFILE%\\.pulsar
  • Reload Pulsar using LNX/WIN: Alt+Ctrl+R - MAC: Alt+Cmd+Ctrl+L
  • Enable the theme via the "UI Theme" drop-down in the "Themes" tab of the Settings View
  • Make changes! Since you opened the theme in a Dev Mode window, changes will be instantly reflected in the editor without having to reload.
  • ",9),va=t('

    Tip

    Because we used pulsar -p link --dev in the above instructions, if you break anything you can always close Pulsar and launch Pulsar normally to force Pulsar to the default theme. This allows you to continue working on your theme even if something goes catastrophically wrong.

    Theme Variables

    UI themes must provide a ui-variables.less and Syntax themes a syntax-variables.less file. It contains predefined variables that packages use to make sure the look and feel matches.

    Here the variables with the default values:

    ',4),ba={href:"https://github.com/pulsar-edit/pulsar/blob/master/static/variables/ui-variables.less",target:"_blank",rel:"noopener noreferrer"},fa={href:"https://github.com/pulsar-edit/pulsar/blob/master/static/variables/syntax-variables.less",target:"_blank",rel:"noopener noreferrer"},ya=e("p",null,"These default values will be used as a fallback in case a theme doesn't define its own variables.",-1),wa=e("h4",{id:"use-in-packages",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#use-in-packages","aria-hidden":"true"},"#"),n(" Use in Packages")],-1),_a=e("p",null,[n("In any of your package's "),e("code",null,".less"),n(" files, you can access the theme variables by importing the "),e("code",null,"ui-variables"),n(" or "),e("code",null,"syntax-variables"),n(" file from Pulsar.")],-1),xa={href:"https://github.com/pulsar-edit/styleguide",target:"_blank",rel:"noopener noreferrer"},qa=t(`

    Here's an example .less file that a package can define using theme variables:

    @import "ui-variables";
    +
    +.my-selector {
    +	background-color: @base-background-color;
    +	padding: @component-padding;
    +}
    +
    @import "syntax-variables";
    +
    +.my-selector {
    +	background-color: @syntax-background-color;
    +}
    +

    Development workflow

    There are a few tools to help make theme development faster and easier.

    Live Reload

    `,6),Pa=e("strong",null,[e("em",null,"LNX/WIN")],-1),Ta=e("kbd",null,"Alt+Ctrl+R",-1),Ca=e("strong",null,[e("em",null,"MAC")],-1),Ia=e("kbd",null,"Alt+Cmd+Ctrl+L",-1),Sa={href:"https://github.com/pulsar-edit/pulsar/tree/master/packages/dev-live-reload",target:"_blank",rel:"noopener noreferrer"},ja=t('

    To launch a Dev Mode window:

    • Open your theme directory in a dev window by selecting the View > Developer > Open in Dev Mode menu item
    • Or launch Pulsar from the terminal with pulsar --dev

    If you'd like to reload all the styles at any time, you can use the shortcut LNX/WIN: Alt+Ctrl+R - MAC: Alt+Cmd+Ctrl+L,

    Developer Tools

    Pulsar is based on the Chromium browser and supports its Developer Tools. You can open them by selecting the View > Developer > Toggle Developer Tools menu, or by using the LNX/WIN: Ctrl+Shift+I - MAC: Alt+Cmd+I

    The dev tools allow you to inspect elements and take a look at their CSS properties.

    Developer Tools

    ',7),Aa={href:"https://developer.chrome.com/devtools/docs/dom-and-styles",target:"_blank",rel:"noopener noreferrer"},Na=e("h4",{id:"pulsar-styleguide",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#pulsar-styleguide","aria-hidden":"true"},"#"),n(" Pulsar Styleguide")],-1),Ra={href:"https://github.com/pulsar-edit/styleguide",target:"_blank",rel:"noopener noreferrer"},Ma=t('

    To open the Styleguide, open the command palette with LNX/WIN: Ctrl+Shift+P - MAC: Cmd+Shift+P and search for styleguide, or use the shortcut LNX/WIN: Ctrl+Shift+G - MAC: Cmd+Ctrl+Shift+G.

    Style Guide

    Side by side

    Sometimes when creating a theme (or package) things can go wrong and the editor becomes unusable. E.g. if the text and background have the same color or something gets pushed out of sight. To avoid having to open Pulsar in "normal" mode to fix the issue, it's advised to open two Pulsar windows. One for making changes and one in Dev Mode to see the changes getting applied.

    Side by side screenshot

    Make changes on the left, see the changes getting applied in "Dev Mode" on the right.

    Now if you mess up something, only the window in "Dev Mode" will be affected and you can easily correct the mistake in your "normal" window.

    Publish your theme

    Once you're happy with your theme and would like to share it with other Pulsar users, it's time to publish it. \u{1F389}

    ',9),La=e("h2",{id:"creating-a-grammar",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#creating-a-grammar","aria-hidden":"true"},"#"),n(" Creating a Grammar")],-1),Wa={href:"http://tree-sitter.github.io/tree-sitter",target:"_blank",rel:"noopener noreferrer"},Oa={href:"https://en.wikipedia.org/wiki/Abstract_syntax_tree",target:"_blank",rel:"noopener noreferrer"},Ua=e("em",null,"syntax trees",-1),Ea=t('

    This syntax tree gives Pulsar a comprehensive understanding of the structure of your code, which has several benefits:

    1. Syntax highlighting will not break because of formatting changes.
    2. Code folding will work regardless of how your code is indented.
    3. Editor features can operate on the syntax tree. For instance, the Select Larger Syntax Node and Select Smaller Syntax Node allow you to select conceptually larger and smaller chunks of your code.
    4. Community packages can use the syntax tree to manipulate code intelligently.

    Tree-sitter grammars are relatively new. Many languages in Pulsar are still supported by TextMate grammars, though we intend to phase these out over time.

    If you're adding support for a new language, you're in the right place!

    Getting Started

    There are two components required to use Tree-sitter in Pulsar: a parser and a grammar file.

    The Parser

    ',7),Fa={href:"https://en.wikipedia.org/wiki/Context-free_grammar",target:"_blank",rel:"noopener noreferrer"},Da={href:"http://tree-sitter.github.io/tree-sitter/creating-parsers",target:"_blank",rel:"noopener noreferrer"},Ya={href:"https://github.com/tree-sitter",target:"_blank",rel:"noopener noreferrer"},za={href:"https://npmjs.com",target:"_blank",rel:"noopener noreferrer"},Va=e("code",null,"name",-1),Ha=e("code",null,"version",-1),Ga=e("code",null,"package.json",-1),$a=t(`
    {
    +  "name": "tree-sitter-mylanguage",
    +  "version": "0.0.1",
    +  // ...
    +}
    +

    then run the command npm publish.

    The Package

    Once you have a Tree-sitter parser that is available on npm, you can use it in your Pulsar package. Packages with grammars are, by convention, always named starting with language. You'll need a folder with a package.json, a grammars subdirectory, and a single json or cson file in the grammars directory, which can be named anything.

    language-mylanguage
    +\u251C\u2500\u2500 LICENSE
    +\u251C\u2500\u2500 README.md
    +\u251C\u2500\u2500 grammars
    +\u2502   \u2514\u2500\u2500 mylanguage.cson
    +\u2514\u2500\u2500 package.json
    +

    The Grammar File

    The mylanguage.cson file specifies how Pulsar should use the parser you created.

    Basic Fields

    It starts with some required fields:

    name: 'My Language'
    +scopeName: 'mylanguage'
    +type: 'tree-sitter'
    +parser: 'tree-sitter-mylanguage'
    +
    `,10),Ba=e("li",null,[e("code",null,"scopeName"),n(" - A unique, stable identifier for the language. Pulsar users will use this in configuration files if they want to specify custom configuration based on the language.")],-1),Xa=e("li",null,[e("code",null,"name"),n(" - A human readable name for the language.")],-1),Ja=e("code",null,"parser",-1),Ka={href:"https://nodejs.org/api/modules.html#modules_require",target:"_blank",rel:"noopener noreferrer"},Za=e("code",null,"require()",-1),Qa=e("li",null,[e("code",null,"type"),n(" - This should have the value "),e("code",null,"tree-sitter"),n(" to indicate to Pulsar that this is a Tree-sitter grammar and not a "),e("a",{href:"#creating-a-legacy-textmate-grammar"},"TextMate grammar"),n(".")],-1),es=t(`

    Language Recognition

    Next, the file should contain some fields that indicate to Pulsar when this language should be used. These fields are all optional.

    • fileTypes - An array of filename suffixes. The grammar will be used for files whose names end with one of these suffixes. Note that the suffix may be an entire filename.
    • firstLineRegex - A regex pattern that will be tested against the first line of the file. The grammar will be used if this regex matches.
    • contentRegex - A regex pattern that will be tested against the contents of the file in order to break ties in cases where multiple grammars matched the file using the above two criteria. If the contentRegex matches, this grammar will be preferred over another grammar with no contentRegex. If the contentRegex does not match, a grammar with no contentRegex will be preferred over this one.

    Syntax Highlighting

    The HTML classes that Pulsar uses for syntax highlighting do not correspond directly to nodes in the syntax tree. Instead, Tree-sitter grammar files specify scope mappings that specify which classes should be applied to which syntax nodes. The scopes object controls these scope mappings. Its keys are CSS selectors that select nodes in the syntax tree. Its values can be of several different types.

    Here is a simple example:

    scopes:
    +  'call_expression > identifier': 'entity.name.function'
    +

    This entry means that, in the syntax tree, any identifier node whose parent is a call_expression should be highlighted using three classes: syntax--entity, syntax--name, and syntax--function.

    `,8),ns={href:"https://developer.mozilla.org/en-US/docs/Web/CSS/Child_selectors",target:"_blank",rel:"noopener noreferrer"},as=e("code",null,">",-1),ss=e("code",null,"'call_expression identifier'",-1),ts=e("code",null,"identifier",-1),os=e("code",null,"call_expression",-1),is=t(`

    Advanced Selectors

    The keys of the scopes object can also contain multiple CSS selectors, separated by commas, similar to CSS files. The triple-quote syntax in CSON makes it convenient to write keys like this on multiple lines:

    scopes:
    +  '''
    +  function_declaration > identifier,
    +  call_expression > identifier,
    +  call_expression > field_expression > field_identifier
    +  ''': 'entity.name.function'
    +
    `,3),ls={href:"https://developer.mozilla.org/en-US/docs/Web/CSS/:nth-child",target:"_blank",rel:"noopener noreferrer"},rs=e("code",null,":nth-child",-1),ps=e("code",null,"identifier",-1),cs=e("code",null,"singleton_method",-1),us=t(`
    scopes:
    +  'singleton_method > identifier:nth-child(3)': 'entity.name.function'
    +
    `,1),ds=e("em",null,"anonymous",-1),hs=e("code",null,"(",-1),ms=e("code",null,":",-1),ks={href:"http://tree-sitter.github.io/tree-sitter/using-parsers#named-vs-anonymous-nodes",target:"_blank",rel:"noopener noreferrer"},gs=t('
    scopes:\n  '''\n    "*",\n    "/",\n    "+",\n    "-"\n  ''': 'keyword.operator'\n

    Text-based Mappings

    You can also apply different classes to a syntax node based on its text. Here are some examples:

    scopes:\n\n  # Apply the classes `syntax--builtin` and `syntax--variable` to all\n  # `identifier` nodes whose text is `require`.\n  'identifier': {exact: 'require', scopes: 'builtin.variable'},\n\n  # Apply the classes `syntax--type` and `syntax--integer` to all\n  # `primitive_type` nodes whose text starts with `int` or `uint`.\n  'primitive_type': {match: /^u?int/, scopes: 'type.integer'},\n\n  # Apply the classes `syntax--builtin`, `syntax--class`, and\n  # `syntax--name` to `constant` nodes with the text `Array`,\n  # `Hash` and `String`. For all other `constant` nodes, just\n  # apply the classes `syntax--class` and `syntax--name`.\n  'constant': [\n    {match: '^(Array|Hash|String)$', scopes: 'builtin.class.name'},\n    'class.name'\n  ]\n

    In total there are four types of values that can be associated with selectors in scopes:

    • Strings - Each class name in the dot-separated string will be prefixed with syntax-- and applied to the selected node.
    • Objects with the keys exact and scopes - If the node's text equals the exact string, the scopes string will be used as described above.
    • Objects with the keys match and scopes - If the node's text matches the match regex pattern, the scopes string will be used as described above.
    • Arrays - The elements of the array will be processed from beginning to end. The first element that matches the selected node will be used as describe above.

    Specificity

    ',7),vs=e("code",null,"scopes",-1),bs={href:"https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity",target:"_blank",rel:"noopener noreferrer"},fs=e("code",null,"exact",-1),ys=e("code",null,"match",-1),ws=e("em",null,"not",-1),_s=e("code",null,"exact",-1),xs=e("code",null,"match",-1),qs=t(`
    scopes:
    +  'call_expression > identifier': 'entity.name.function'
    +
    +  # If we did not include the second selector here, then this rule
    +  # would not apply to identifiers inside of call_expressions,
    +  # because the selector \`call_expression > identifier\` is more
    +  # specific than the selector \`identifier\`.
    +  'identifier, call_expression > identifier': [
    +    {exact: 'require', scopes: 'builtin.variable'},
    +    {match: '^[A-Z]', scopes: 'constructor'},
    +  ]
    +

    Language Injection

    Sometimes, a source file can contain code written in several different languages. Tree-sitter grammars support this situation using a two-part process called language injection. First, an 'outer' language must define an injection point - a set of syntax nodes whose text can be parsed using a different language, along with some logic for guessing the name of the other language that should be used. Second, an 'inner' language must define an injectionRegex - a regex pattern that will be tested against the language name provided by the injection point.

    `,3),Ps={href:"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#Tagged_templates",target:"_blank",rel:"noopener noreferrer"},Ts=t(`
    // HTML in a template literal
    +const htmlContent = html\`<div>Hello \${name}</div>\`;
    +

    The tree-sitter-javascript parser parses this tagged template literal as a call_expression with two children: an identifier and a template_literal:

    (call_expression
    +  (identifier)
    +  (template_literal
    +    (interpolation
    +      (identifier))))
    +

    Here is an injection point that would allow syntax highlighting inside of template literals:

    atom.grammars.addInjectionPoint("source.js", {
    +	type: "call_expression",
    +
    +	language(callExpression) {
    +		const { firstChild } = callExpression;
    +		if (firstChild.type === "identifier") {
    +			return firstChild.text;
    +		}
    +	},
    +
    +	content(callExpression) {
    +		const { lastChild } = callExpression;
    +		if (lastChild.type === "template_string") {
    +			return lastChild;
    +		}
    +	},
    +});
    +

    The language callback would then be called with every call_expression node in the syntax tree. In the example above, it would retrieve the first child of the call_expression, which is an identifier with the name "html". The callback would then return the string "html".

    The content callback would then be called with the same call_expression node and return the template_string node within the call_expression node.

    In order to parse the HTML within the template string, the HTML grammar file would need to specify an injectionRegex:

    injectionRegex: 'html|HTML'
    +

    Code Folding

    The next field in the grammar file, folds, controls code folding. Its value is an array of fold pattern objects. Fold patterns are used to decide whether or not a syntax node can be folded, and if so, where the fold should start and end. Here are some example fold patterns:

    folds: [
    +
    +  # All \`comment\` nodes are foldable. By default, the fold starts at
    +  # the end of the node's first line, and ends at the beginning
    +  # of the node's last line.
    +  {
    +    type: 'comment'
    +  }
    +
    +  # \`if_statement\` nodes are foldable if they contain an anonymous
    +  # "then" token and either an \`elif_clause\` or \`else_clause\` node.
    +  # The fold starts at the end of the "then" token and ends at the
    +  # \`elif_clause\` or \`else_clause\`.
    +  {
    +    type: 'if_statement',
    +    start: {type: '"then"'}
    +    end: {type: ['elif_clause', 'else_clause']}
    +  }
    +
    +  # Any node that starts with an anonymous "(" token and ends with
    +  # an anonymous ")" token is foldable. The fold starts after the
    +  # "(" and ends before the ")".
    +  {
    +    start: {type: '"("', index: 0},
    +    end: {type: '")"', index: -1}
    +  }
    +]
    +

    Fold patterns can have one or more of the following fields:

    • type - A string or array of strings. In order to be foldable according to this pattern, a syntax node's type must match one of these strings.
    • start - An object that is used to identify a child node after which the fold should start. The object can have one or both of the following fields:
      • type - A string or array of strings. To start a fold, a child node's type must match one of these strings.
      • index - a number that's used to select a specific child according to its index. Negative values are interpreted as indices relative the last child, so that -1 means the last child.
    • end - An object that is used to identify a child node before which the fold should end. It has the same structure as the start object.

    Comments

    The last field in the grammar file, comments, controls the behavior of Pulsar's Editor: Toggle Line Comments command. Its value is an object with a start field and an optional end field. The start field is a string that should be prepended to or removed from lines in order to comment or uncomment them.

    In JavaScript, it looks like this:

    comments:
    +  start: '// '
    +

    The end field should be used for languages that only support block comments, not line comments. If present, it will be appended to or removed from the end of the last selected line in order to comment or un-comment the selection.

    In CSS, it would look like this:

    comments:
    +  start: '/* '
    +  end: ' */'
    +

    Example Packages

    More examples of all of these features can be found in the Tree-sitter grammars bundled with Pulsar:

    `,23),Cs={href:"https://github.com/pulsar-edit/pulsar/tree/master/packages/language-shellscript",target:"_blank",rel:"noopener noreferrer"},Is={href:"https://github.com/pulsar-edit/pulsar/tree/master/packages/language-c",target:"_blank",rel:"noopener noreferrer"},Ss={href:"https://github.com/pulsar-edit/pulsar/tree/master/packages/language-go",target:"_blank",rel:"noopener noreferrer"},js={href:"https://github.com/pulsar-edit/pulsar/tree/master/packages/language-html",target:"_blank",rel:"noopener noreferrer"},As={href:"https://github.com/pulsar-edit/pulsar/tree/master/packages/language-javascript",target:"_blank",rel:"noopener noreferrer"},Ns={href:"https://github.com/pulsar-edit/pulsar/tree/master/packages/language-python",target:"_blank",rel:"noopener noreferrer"},Rs={href:"https://github.com/pulsar-edit/pulsar/tree/master/packages/language-ruby",target:"_blank",rel:"noopener noreferrer"},Ms={href:"https://github.com/pulsar-edit/pulsar/tree/master/packages/language-typescript",target:"_blank",rel:"noopener noreferrer"},Ls=t('

    Creating a Legacy TextMate Grammar

    Pulsar's syntax highlighting can be powered by two types of grammars. If you're adding support for a new language, the preferred way is to create a Tree-sitter grammar. Tree-sitter grammars have better performance and provide support for more editor features, such as the Select Larger Syntax Node command.

    This section describes the Pulsar's legacy support for TextMate grammars.

    TextMate grammars are supported by several popular text editors. They provide a set of regex (regular expression) patterns which are assigned scopes. These scopes are then turned into the CSS classes that you can target in syntax themes.

    Getting Started

    TextMate Grammars depend heavily on regexes, and you should be comfortable with interpreting and writing regexes before continuing. Note that Pulsar uses the Oniguruma engine, which is very similar to the PCRE or Perl regex engines. Here are some resources to help you out:

    • https://www.regular-expressions.info/tutorial.html provides a comprehensive regex tutorial
    • https://www.rexegg.com/regex-quickstart.html contains a cheat sheet for various regex expressions
    • https://regex101.com/ or https://regexr.com/ allows live prototyping
    • https://github.com/kkos/oniguruma/blob/master/doc/RE the docs for the Oniguruma regex engine
    ',7),Ws={href:"https://github.com/bevry/cson#what-is-cson",target:"_blank",rel:"noopener noreferrer"},Os={href:"https://www.json.org/",target:"_blank",rel:"noopener noreferrer"},Us=t('

    Create the Package

    To get started, press LNX/WIN: Ctrl+Shift+P - MAC: Cmd+Shift+P and start typing "Generate Package" to generate a new grammar package. Select "Package Generator: Generate Package," and you'll be asked for the path where your package will be created. Let's call ours language-flight-manual.

    Tip

    Grammar packages should start with language-.

    ',3),Es=e("code",null,"keymaps",-1),Fs=e("code",null,"lib",-1),Ds=e("code",null,"menus",-1),Ys=e("code",null,"styles",-1),zs=e("code",null,"package.json",-1),Vs=e("code",null,"activationCommands",-1),Hs=e("code",null,"grammars",-1),Gs=e("code",null,"flight-manual.cson",-1),$s={href:"https://gist.github.com/DamnedScholar/622926bcd222eb1ddc483d12103fd315",target:"_blank",rel:"noopener noreferrer"},Bs=t(`

    scopeName is the root scope of your package. This should generally describe what language your grammar package is highlighting; for example, language-javascript's scopeName is source.js and language-html's is text.html.basic. Name it source.flight-manual for now.

    name is the user-friendly name that is displayed in places like the status bar or the grammar selector. Again, this name should describe what the grammar package is highlighting. Rename it to Flight Manual.

    fileTypes is an array of filetypes that language-flight-manual should highlight. We're interested in highlighting the Flight Manual's Markdown files, so add the md extension to the list and remove the others.

    patterns contains the array of regex patterns that will determine how the file is tokenized.

    Adding Patterns

    To start, let's add a basic pattern to tokenize the words Flight Manual whenever they show up. Your regex should look like \\bFlight Manual\\b. Here's what your patterns block should look like:

    'patterns': [
    +  {
    +    'match': '\\\\bFlight Manual\\\\b'
    +    'name': 'entity.other.flight-manual'
    +  }
    +]
    +
    `,7),Xs=e("code",null,"match",-1),Js=e("code",null,"name",-1),Ks={href:"https://manual.macromates.com/en/language_grammars",target:"_blank",rel:"noopener noreferrer"},Zs=t(`

    Tip

    All scopes should end with the portion of the root scopeName after the leading source or text. In our case, all scopes should end with flight-manual.

    Note

    Astute readers may have noticed that the \\b was changed to \\\\b with two backslashes and not one. This is because CSON processes the regex string before handing it to Oniguruma, so all backslashes need to be escaped twice.

    But what if we wanted to apply different scopes to Flight and Manual? This is possible by adding capture groups to the regex and then referencing those capture groups in a new capture property. For example:

    'match': '\\\\b(Flight) (Manual)\\\\b'
    +'name': 'entity.other.flight-manual'
    +'captures':
    +  '1':
    +    'name': 'keyword.other.flight.flight-manual'
    +  '2':
    +    'name': 'keyword.other.manual.flight-manual'
    +

    This will assign the scope keyword.other.flight.flight-manual to Flight, keyword.other.manual.flight-manual to Manual, and entity.other.flight-manual to the overarching Flight Manual.

    Begin/End Patterns

    Now let's say we want to tokenize the {{#note}} blocks that occur in Flight Manual files. Our previous two examples used match, but one limit of match is that it can only match single lines. {{#note}} blocks, on the other hand, can span multiple lines. For these cases, you can use the begin/end keys. Once the regex in the begin key is matched, tokenization will continue until the end pattern is reached.

    'begin': '({{)(#note)(}})'
    +'beginCaptures':
    +  '0': # The 0 capture contains the entire match
    +    'name': 'meta.block.start.flight-manual'
    +  '1':
    +    'name': 'punctuation.definition.block.flight-manual'
    +  '2':
    +    'name': 'keyword.note.flight-manual'
    +  '3':
    +    'name': 'punctuation.definition.block.flight-manual'
    +'end': '({{)(/note)(}})'
    +'endCaptures':
    +  '0':
    +    'name': 'meta.block.end.flight-manual'
    +  '1':
    +    'name': 'punctuation.definition.block.flight-manual'
    +  '2':
    +    'name': 'keyword.note.flight-manual'
    +  '3':
    +    'name': 'punctuation.definition.block.flight-manual'
    +'name': 'meta.block.note.flight-manual'
    +

    Tip

    Get into the habit of providing punctuation scopes early on. It's much less effort than having to go back and rewriting all your patterns to support punctuation scopes when your grammar starts to get a bit longer!

    Awesome, we have our first multiline pattern! However, if you've been following along and playing around in your own .md file, you may have noticed that Flight Manual doesn't receive any scopes inside a note block. A begin/end block is essentially a subgrammar of its own: once it starts matching, it will only match its own subpatterns until the end pattern is reached. Since we haven't defined any subpatterns, then clearly nothing will be matched inside of a note block. Let's fix that!

    'begin': '({{)(#note)(}})'
    +'beginCaptures':
    +  '0': # The 0 capture contains the entire match
    +    'name': 'meta.block.start.flight-manual'
    +  '1':
    +    'name': 'punctuation.definition.block.flight-manual'
    +  '2':
    +    'name': 'keyword.note.flight-manual'
    +  '3':
    +    'name': 'punctuation.definition.block.flight-manual'
    +'end': '({{)(/note)(}})'
    +'endCaptures':
    +  '0':
    +    'name': 'meta.block.end.flight-manual'
    +  '1':
    +    'name': 'punctuation.definition.block.flight-manual'
    +  '2':
    +    'name': 'keyword.note.flight-manual'
    +  '3':
    +    'name': 'punctuation.definition.block.flight-manual'
    +'name': 'meta.block.note.flight-manual'
    +'patterns': [
    +  {
    +    'match': '\\\\b(Flight) (Manual)\\\\b'
    +    'name': 'entity.other.flight-manual'
    +    'captures':
    +      '1':
    +        'name': 'keyword.other.flight.flight-manual'
    +      '2':
    +        'name': 'keyword.other.manual.flight-manual'
    +  }
    +]
    +

    There. With the patterns block, Flight Manual should now receive the proper scopes.

    Repositories and the Include keyword, or how to avoid duplication

    At this point, note blocks are looking pretty nice, as is the Flight Manual keyword, but the rest of the file is noticeably lacking any form of Markdown syntax highlighting. Is there a way to include the GitHub-Flavored Markdown grammar without copying and pasting everything over? This is where the include keyword comes in. include allows you to include other patterns, even from other grammars! language-gfm's scopeName is source.gfm, so let's include that. Our patterns block should now look like the following:

    'patterns': [
    +  {
    +    'include': 'source.gfm'
    +  }
    +  {
    +    # Flight Manual pattern
    +  }
    +  {
    +    # Note begin/end pattern
    +  }
    +]
    +

    However, including source.gfm has led to another problem: note blocks still don't have any Markdown highlighting! The quick fix would be to add the include pattern to the note's pattern block as well, but now we're duplicating two patterns. You can imagine that as this grammar grows it'll quickly become inefficient to keep copying each new global pattern over to the note pattern as well. Therefore, include helpfully recognizes the special $self scope. $self automatically includes all the top-level patterns of the current grammar. The note block can then be simplified to the following:

    'begin': '({{)(#note)(}})'
    +# beginCaptures
    +'end': '({{)(/note)(}})'
    +# endCaptures
    +'name': 'meta.block.note.flight-manual'
    +'patterns': [
    +  {
    +    'include': '$self'
    +  }
    +]
    +

    Where to Go from Here

    There are several good resources out there that help when writing a grammar. The following is a list of some particularly useful ones (some have been linked to in the sections above as well).

    `,19),Qs={href:"https://gist.github.com/DamnedScholar/622926bcd222eb1ddc483d12103fd315",target:"_blank",rel:"noopener noreferrer"},et={href:"https://gist.github.com/Aerijo/b8c82d647db783187804e86fa0a604a1",target:"_blank",rel:"noopener noreferrer"},nt={href:"http://www.apeth.com/nonblog/stories/textmatebundle.html",target:"_blank",rel:"noopener noreferrer"},at={href:"https://github.com/kkos/oniguruma/blob/master/doc/RE",target:"_blank",rel:"noopener noreferrer"},st={href:"http://manual.macromates.com/en/language_grammars.html",target:"_blank",rel:"noopener noreferrer"},tt={href:"https://github.com/pulsar-edit/first-mate",target:"_blank",rel:"noopener noreferrer"},ot=e("code",null,"first-mate",-1),it={href:"https://github.com/pulsar-edit/pulsar/tree/master/packages/language-python",target:"_blank",rel:"noopener noreferrer"},lt={href:"https://github.com/pulsar-edit/pulsar/tree/master/packages/language-javascript",target:"_blank",rel:"noopener noreferrer"},rt={href:"https://github.com/pulsar-edit/pulsar/tree/master/packages/language-html",target:"_blank",rel:"noopener noreferrer"},pt={href:"https://github.com/pulsar-edit/pulsar/tree/master/packages",target:"_blank",rel:"noopener noreferrer"},ct=e("h2",{id:"converting-from-textmate",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#converting-from-textmate","aria-hidden":"true"},"#"),n(" Converting from TextMate")],-1),ut={href:"https://macromates.com",target:"_blank",rel:"noopener noreferrer"},dt=e("h3",{id:"converting-a-textmate-grammar-bundle",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#converting-a-textmate-grammar-bundle","aria-hidden":"true"},"#"),n(" Converting a TextMate Grammar Bundle")],-1),ht=e("p",null,"Converting a TextMate bundle will allow you to use its editor preferences, snippets, and colorization inside Pulsar.",-1),mt={href:"https://en.wikipedia.org/wiki/R_(programming_language)",target:"_blank",rel:"noopener noreferrer"},kt={href:"https://github.com/textmate",target:"_blank",rel:"noopener noreferrer"},gt=t(`

    You can convert the R bundle with the following command:

    $ pulsar -p init --package language-r --convert https://github.com/textmate/r.tmbundle
    +

    You can now change directory into language-r to see the converted bundle. Once you link your package with the pulsar -p link command, your new package is ready to use. Launch Pulsar and open a .r file in the editor to see it in action!

    Converting a TextMate Syntax Theme

    `,4),vt={href:"https://macromates.com",target:"_blank",rel:"noopener noreferrer"},bt=e("h4",{id:"differences",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#differences","aria-hidden":"true"},"#"),n(" Differences")],-1),ft={href:"https://en.wikipedia.org/wiki/Property_list",target:"_blank",rel:"noopener noreferrer"},yt={href:"https://en.wikipedia.org/wiki/Cascading_Style_Sheets",target:"_blank",rel:"noopener noreferrer"},wt={href:"http://lesscss.org",target:"_blank",rel:"noopener noreferrer"},_t=t(`

    The utility that converts the theme first parses the theme's plist file and then creates comparable CSS rules and properties that will style Pulsar similarly.

    Convert the Theme

    Download the theme you wish to convert.

    Now, let's say you've downloaded the theme to ~/Downloads/MyTheme.tmTheme, you can convert the theme with the following command:

    $ pulsar -p init --theme my-theme --convert ~/Downloads/MyTheme.tmTheme
    +

    You can then change directory to my-theme to see the converted theme.

    Activate the Theme

    Once your theme is installed you can enable it by launching Pulsar and opening the Settings View with the LNX: Edit > Preferences - MAC: Pulsar > Preferences - WIN: File > Preferences menu item. Then select the "Themes" tab on the left side navigation. Finally, choose "My Theme" from the "Syntax Theme" dropdown menu to enable your new theme.

    Your theme is now enabled, open an editor to see it in action!

    Publishing

    `,10),xt=e("code",null,"ppm",-1),qt=e("code",null,"pulsar",-1),Pt=e("code",null,"-p",-1),Tt=e("code",null,"--package",-1),Ct=e("code",null,"pulsar -p",-1),It=e("p",null,[n("See more in "),e("a",{href:"#using-ppm"},"Using PPM"),n(".")],-1),St=e("h3",{id:"prepare-your-package",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#prepare-your-package","aria-hidden":"true"},"#"),n(" Prepare Your Package")],-1),jt=e("p",null,"There are a few things you should double check before publishing:",-1),At=t("
  • Your package.json file has name, description, and repository fields.
  • Your package.json name is URL Safe, as in it's not an emoji or special character.
  • Your package.json file has a version field with a value of "0.0.0".
  • ",3),Nt=e("code",null,"package.json",-1),Rt=e("code",null,"version",-1),Mt={href:"https://semver.org/spec/v2.0.0.html",target:"_blank",rel:"noopener noreferrer"},Lt=t("
  • Your package.json file has an engines field that contains an entry for atom such as: "engines": {"atom": ">=1.0.0 <2.0.0"}.
  • Your package has a README.md file at the root.
  • Your repository URL in the package.json file is the same as the URL of your repository.
  • ",3),Wt={href:"https://github.com",target:"_blank",rel:"noopener noreferrer"},Ot={href:"https://help.github.com/articles/importing-a-git-repository-using-the-command-line/",target:"_blank",rel:"noopener noreferrer"},Ut=e("h3",{id:"publish-your-package",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#publish-your-package","aria-hidden":"true"},"#"),n(" Publish Your Package")],-1),Et={href:"https://web.pulsar-edit.dev/packages",target:"_blank",rel:"noopener noreferrer"},Ft=e("code",null,"https://web.pulsar-edit.dev/packages/your-package-name",-1),Dt=e("p",null,[n("Now let's review what the "),e("code",null,"pulsar -p publish"),n(" command does:")],-1),Yt=e("li",null,"Registers the package name on Pulsar Package Repository if it is being published for the first time.",-1),zt=e("li",null,[n("Updates the "),e("code",null,"version"),n(" field in the "),e("code",null,"package.json"),n(" file and commits it.")],-1),Vt={href:"https://git-scm.com/book/en/Git-Basics-Tagging",target:"_blank",rel:"noopener noreferrer"},Ht=e("li",null,"Pushes the tag and current branch up to GitHub.",-1),Gt=e("li",null,"Updates Pulsar Package Repository with the new version being published.",-1),$t=t(`

    Now run the following commands to publish your package:

    $ cd path-to-your-package
    +$ pulsar -p publish minor
    +
    `,2),Bt=e("code",null,"pulsar -p publish",-1),Xt={href:"https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/",target:"_blank",rel:"noopener noreferrer"},Jt={href:"https://en.wikipedia.org/wiki/Keychain_(software)",target:"_blank",rel:"noopener noreferrer"},Kt=t(`

    Your package is now published and available on Pulsar Package Repository. Head on over to https://web.pulsar-edit.dev/packages/your-package-name to see your package's page.

    With pulsar -p publish, you can bump the version and publish by using

    $ pulsar -p publish <version-type>
    +

    where version-type can be major, minor and patch.

    • MAJOR version when you make incompatible API changes
    • MINOR version when you add functionality in a backwards compatible manner
    • PATCH version when you make backwards compatible bug fixes

    i.e. to bump a package from v1.0.0 to v1.1.0:

    $ pulsar -p publish minor
    +
    `,7),Zt={href:"https://semver.org/",target:"_blank",rel:"noopener noreferrer"},Qt=e("p",null,[n("You can also run "),e("code",null,"pulsar -p help publish"),n(" to see all the available options and "),e("code",null,"pulsar -p help"),n(" to see all the other available commands.")],-1),eo=e("h2",{id:"iconography",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#iconography","aria-hidden":"true"},"#"),n(" Iconography")],-1),no={href:"https://github.com/github/octicons/tree/v4.4.0",target:"_blank",rel:"noopener noreferrer"},ao=t('

    NOTE: Some older icons from version 2.1.2 are still kept for backwards compatibility.

    Overview

    In the Styleguide under the "Icons" section you'll find all the Octicons that are available.

    Octicons in the Styleguide

    Usage

    Octicons can be added with simple CSS classes in your markup. Prefix the icon names with icon icon-.

    As an example, to add a monitor icon (device-desktop), use the icon icon-device-desktop classes:

    <span class="icon icon-device-desktop"></span>
    +

    Size

    Octicons look best with a font-size of 16px. It's already used as the default, so you don't need to worry about it. In case you prefer a different icon size, try to use multiples of 16 (32px, 48px etc.) for the sharpest result. Sizes in between are ok too, but might look a bit blurry for icons with straight lines.

    Usability

    `,11),so={href:"https://atom.io/docs/api/latest/TooltipManager",target:"_blank",rel:"noopener noreferrer"},to=e("code",null,'title="label"',-1),oo=e("h2",{id:"debugging",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#debugging","aria-hidden":"true"},"#"),n(" Debugging")],-1),io={href:"https://github.com/pulsar-edit/.github/blob/main/CONTRIBUTING.md#reporting-bugs",target:"_blank",rel:"noopener noreferrer"},lo=t(`

    Update to the Latest Version

    You might be running into an issue which was already fixed in a more recent version of Pulsar than the one you're using.

    If you're using a released version, check which version of Pulsar you're using:

    $ pulsar --version
    +> Pulsar  : 1.63.0-dev
    +> Electron: 12.2.3
    +> Chrome  : 89.0.4389.128
    +> Node    : 14.16.0
    +
    `,4),ro={href:"https://pulsar-edit.dev/download.html",target:"_blank",rel:"noopener noreferrer"},po=t(`

    If you're building Pulsar from source, pull down the latest version of master and re-build. Make sure that if logging an issue you include the latest commit hash you built from.

    Using Safe Mode

    A large part of Pulsar's functionality comes from packages you can install. Pulsar will also execute the code in your init script on startup. In some cases, these packages and the code in the init script might be causing unexpected behavior, problems, or performance issues.

    To determine if that is happening, start Pulsar from the terminal in safe mode:

    $ pulsar --safe
    +

    This starts Pulsar, but does not load packages from LNX/MAC: ~/.pulsar/packages or ~/.pulsar/dev/packages - WIN: %USERPROFILE%\\.pulsar\\packages or %USERPROFILE%\\.pulsar\\dev\\packages. and disables loading of your init script. If you can no longer reproduce the problem in safe mode, it's likely it was caused by one of the packages or the init script.

    If removing or commenting out all content from the init script and starting Pulsar normally still produces the error, then try figuring out which package is causing trouble. Start Pulsar normally again and open the Settings View with LNX/WIN: Ctrl+, - MAC: Cmd+,. Since the Settings View allows you to disable each installed package, you can disable packages one by one until you can no longer reproduce the issue. Restart Pulsar or reload Pulsar with LNX/WIN: Ctrl+Shift+F5 - MAC: Alt+Cmd+Ctrl+L. after you disable each package to make sure it's completely gone.

    When you find the problematic package, you can disable or uninstall the package. We strongly recommend creating an issue on the package's GitHub repository.

    Clearing Saved State

    Pulsar saves a number of things about your environment when you exit in order to restore Pulsar to the same configuration when you next launch the program. In some cases the state that gets saved can be something undesirable that prevents Pulsar from working properly. In these cases, you may want to clear the state that Pulsar has saved.

    DANGER

    Clearing the saved state permanently destroys any state that Pulsar has saved across all projects. This includes unsaved changes to files you may have been editing in all projects. This is a destructive action.

    Clearing the saved state can be done by opening a terminal and executing:

    $ pulsar --clear-window-state
    +

    Reset to Factory Defaults

    In some cases, you may want to reset Pulsar to "factory defaults", in other words clear all of your configuration and remove all packages. This can easily be done by opening a terminal and executing:

    `,15),co=e("div",{class:"language-bash ext-sh line-numbers-mode"},[e("pre",{class:"language-bash"},[e("code",null,[n("$ "),e("span",{class:"token function"},"mv"),n(` ~/.pulsar ~/.pulsar-backup +`)])]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"})])],-1),uo=e("div",{class:"language-bash ext-sh line-numbers-mode"},[e("pre",{class:"language-bash"},[e("code",null,[n("$ "),e("span",{class:"token function"},"mv"),n(` ~/.pulsar ~/.pulsar-backup +`)])]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"})])],-1),ho=e("div",{class:"language-bash ext-sh line-numbers-mode"},[e("pre",{class:"language-bash"},[e("code",null,[n("$ "),e("span",{class:"token function"},"rename"),n(" %USERPROFILE%"),e("span",{class:"token punctuation"},"\\"),n(`.pulsar .pulsar-backup +`)])]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"})])],-1),mo=t(`

    Once that is complete, you can launch Pulsar as normal. Everything will be just as if you first installed Pulsar.

    Tip

    The command given above doesn't delete the old configuration, just puts it somewhere that Pulsar can't find it. If there are pieces of the old configuration you want to retrieve, you can find them in the LNX/MAC: ~/.pulsar-backup - WIN: %USERPROFILE%\\.pulsar-backup directory.

    Check for Linked Packages

    If you develop or contribute to Pulsar packages, there may be left-over packages linked to your LNX/MAC: ~/.pulsar/packages or ~/.pulsar/dev/packages - WIN: %USERPROFILE%\\.pulsar\\packages or %USERPROFILE%\\.pulsar\\dev\\packages. directories. You can use the pulsar -p links command to list all linked packages:

    $ pulsar -p links
    +> /Users/pulsy/.pulsar/dev/packages (0)
    +> \u2514\u2500\u2500 (no links)
    +> /Users/pulsy/.pulsar/packages (1)
    +> \u2514\u2500\u2500 color-picker -> /Users/pulsy/github/color-picker
    +

    You can remove links using the pulsar -p unlink command:

    $ pulsar -p unlink color-picker
    +> Unlinking /Users/pulsy/.pulsar/packages/color-picker \u2713
    +

    See pulsar -p links --help and pulsar -p unlink --help for more information on these commands.

    Tip

    You can also use pulsar -p unlink --all to easily unlink all packages and themes.

    Check for Incompatible Packages

    `,10),ko={href:"https://github.com/pulsar-edit/pulsar/tree/master/packages/incompatible-packages",target:"_blank",rel:"noopener noreferrer"},go=e("p",null,[e("img",{src:q,alt:"Incompatible Packages Status Bar Indicator",title:"Incompatible Packages Status Bar Indicator"})],-1),vo=e("p",null,"If you see this indicator, click it and follow the instructions.",-1),bo=e("h3",{id:"check-pulsar-and-package-settings",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#check-pulsar-and-package-settings","aria-hidden":"true"},"#"),n(" Check Pulsar and Package Settings")],-1),fo=e("p",null,"In some cases, unexpected behavior might be caused by settings in Pulsar or in one of the packages.",-1),yo={href:"https://github.com/pulsar-edit/settings-view",target:"_blank",rel:"noopener noreferrer"},wo=e("strong",null,[e("em",null,"LNX/WIN")],-1),_o=e("kbd",null,"Ctrl+,",-1),xo=e("strong",null,[e("em",null,"MAC")],-1),qo=e("kbd",null,"Cmd+,",-1),Po=e("strong",null,[e("em",null,"LNX")],-1),To=e("em",null,"Edit > Preferences",-1),Co=e("strong",null,[e("em",null,"MAC")],-1),Io=e("em",null,"Pulsar > Preferences",-1),So=e("strong",null,[e("em",null,"WIN")],-1),jo=e("em",null,"File > Preferences",-1),Ao=e("code",null,"Settings View: Open",-1),No={href:"https://github.com/pulsar-edit/command-palette",target:"_blank",rel:"noopener noreferrer"},Ro=e("p",null,[e("img",{src:P,alt:"Settings View"})],-1),Mo=e("p",null,"Some of these options are also available on a per-language basis which means that they may be different for specific languages, for example JavaScript or Python. To check the per-language settings, open the settings for the language package under the Packages tab in the Settings View, for example the language-javascript or language-python package.",-1),Lo={href:"https://github.com/pulsar-edit/wrap-guide",target:"_blank",rel:"noopener noreferrer"},Wo={href:"https://github.com/pulsar-edit/whitespace",target:"_blank",rel:"noopener noreferrer"},Oo=e("p",null,[e("img",{src:T,alt:"Package Settings"})],-1),Uo=e("h3",{id:"check-your-configuration",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#check-your-configuration","aria-hidden":"true"},"#"),n(" Check Your Configuration")],-1),Eo=e("h3",{id:"check-your-keybindings",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#check-your-keybindings","aria-hidden":"true"},"#"),n(" Check Your Keybindings")],-1),Fo={href:"https://github.com/pulsar-edit/keybinding-resolver",target:"_blank",rel:"noopener noreferrer"},Do=t('

    Show the keybinding resolver with LNX/WIN: Ctrl+. - MAC: Cmd+., or with Keybinding Resolver: Show from the Command palette. With the Keybinding Resolver shown, press a key combination:

    Keybinding Resolver

    The Keybinding Resolver shows you a list of keybindings that exist for the key combination, where each item in the list has the following:

    • the command for the keybinding
    • the CSS selector used to define the context in which the keybinding is valid
    • the file in which the keybinding is defined

    The keybindings are listed in two colors. All the keybindings that are matched but not executed are shown in gray. The one that is executed, if any, is shown in green. If the command you wanted to trigger isn't listed, then a keybinding for that command hasn't been loaded.

    ',5),Yo=e("ul",null,[e("li",null,[e("p",null,"The key combination was not used in the context defined by the keybinding's selector"),e("p",null,[n("For example, you can't trigger the keybinding for the "),e("code",null,"tree-view:add-file"),n(" command if the Tree View is not focused.")])]),e("li",null,[e("p",null,"There is another keybinding that took precedence"),e("p",null,"This often happens when you install a package which defines keybindings that conflict with existing keybindings. If the package's keybindings have selectors with higher specificity or were loaded later, they'll have priority over existing ones.")])],-1),zo=e("code",null,"keymap.cson",-1),Vo={href:"https://github.com/orgs/pulsar-edit/discussions",target:"_blank",rel:"noopener noreferrer"},Ho=e("h3",{id:"check-font-rendering-issues",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#check-font-rendering-issues","aria-hidden":"true"},"#"),n(" Check Font Rendering Issues")],-1),Go=e("strong",null,[e("em",null,"LNX/WIN")],-1),$o=e("kbd",null,"Ctrl+Shift+I",-1),Bo=e("strong",null,[e("em",null,"MAC")],-1),Xo=e("kbd",null,"Alt+Cmd+I",-1),Jo={href:"https://developers.google.com/web/tools/chrome-devtools/inspect-styles/",target:"_blank",rel:"noopener noreferrer"},Ko=t('

    Fonts In Use

    Check for Errors in the Developer Tools

    When an unexpected error occurs in Pulsar, you will normally see a red notification which provides details about the error and allows you to create an issue on the right repository:

    Exception Notification

    Not all errors are logged with a notification so if you suspect you're experiencing an error but there's no notification, you can also look for errors in the developer tools Console tab. To access the Console tab, press LNX/WIN: Ctrl+Shift+I - MAC: Alt+Cmd+I to open developer tools and then click the Console tab:

    DevTools Error

    If there are multiple errors, you can scroll down to the bottom of the panel to see the most recent error. Or while reproducing an error, you can right click in the Console tab panel, select Clear console to remove all Console output, and then reproduce the error to see what errors are logged to the Console tab.

    Note

    When running in Dev Mode, the developer tools are automatically shown with the error logged in the Console tab.

    Find Crash Logs

    ',9),Zo=e("p",null,"When Pulsar crashes, it should write a core dump if system settings permit. In order to find whether the core dump is written and to where, consult the documentation for your distribution of Linux. Once you have the core dump, you can save it to send in later if it is needed for debugging.",-1),Qo=e("p",null,[n("When Pulsar crashes, you will find a crash dump in Console.app. You can launch Console.app using Spotlight or you can find it in "),e("code",null,"/Applications/Utilities/Console.app"),n(". Once you have launched the program, you can find the latest crash dump by following these instructions:")],-1),ei=e("ol",null,[e("li",null,'Click "User Reports" in the left-most column'),e("li",null,[n("Find the latest entry in the middle column that starts with "),e("code",null,"Pulsar"),n(" and ends with "),e("code",null,".crash")])],-1),ni=e("p",null,"Once you have the crash dump, you can save it to send in later if it is needed for debugging.",-1),ai=e("p",null,[n("When Pulsar crashes, you will find a crash dump inside your "),e("code",null,"%TEMP%\\Pulsar Crashes"),n(" directory. It will be the newest file with the "),e("code",null,".dmp"),n(" extension. Once you have the crash dump, you can save it to send in later if it is needed for debugging.")],-1),si=e("h3",{id:"diagnose-startup-performance",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#diagnose-startup-performance","aria-hidden":"true"},"#"),n(" Diagnose Startup Performance")],-1),ti={href:"https://github.com/pulsar-edit/timecop",target:"_blank",rel:"noopener noreferrer"},oi=e("p",null,[e("img",{src:C,alt:"Timecop"})],-1),ii=e("p",null,"Timecop displays the following information:",-1),li=e("ul",null,[e("li",null,"Pulsar startup times"),e("li",null,"File compilation times"),e("li",null,"Package loading and activation times"),e("li",null,"Theme loading and activation times")],-1),ri=e("p",null,"If a specific package has high load or activation times, you might consider reporting an issue to the maintainers. You can also disable the package to potentially improve future startup times.",-1),pi=e("h3",{id:"diagnose-runtime-performance",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#diagnose-runtime-performance","aria-hidden":"true"},"#"),n(" Diagnose Runtime Performance")],-1),ci={href:"https://github.com/pulsar-edit/.github/blob/main/CONTRIBUTING.md#reporting-bugs",target:"_blank",rel:"noopener noreferrer"},ui=t('

    To run a profile, open the Developer Tools with LNX/WIN: Ctrl+Shift+I - MAC: Alt+Cmd+I. From there:

    1. Click the Profiles tab
    2. Select "Collect JavaScript CPU Profile"
    3. Click "Start"

    DevTools Profiler

    Once that is done, then perform the slow action to capture a recording. When finished, click "Stop". Switch to the "Chart" view, and a graph of the recorded actions will appear. You can save and post the profile data by clicking "Save" next to the profile's name in the left panel.

    DevTools Profiler

    ',5),di={href:"https://developer.chrome.com/devtools/docs/cpu-profiling",target:"_blank",rel:"noopener noreferrer"},hi=t(`

    Profiling Startup Performance

    If the time for loading the window looks high, you can create a CPU profile for that period using the --profile-startup command line flag when starting Pulsar:

    $ pulsar --profile-startup .
    +

    This will automatically capture a CPU profile as Pulsar is loading and open the Developer Tools once Pulsar loads. From there:

    1. Click the Profiles tab in the Developer Tools
    2. Select the "startup" profile
    3. Click the "Save" link for the startup profile

    You can then include the startup profile in any issue you report.

    Check Your Build Tools

    If you are having issues installing a package using pulsar -p install, this could be because the package has dependencies on libraries that contain native code. This means you will need to have a C++ compiler and Python installed to be able to install it. You can run pulsar -p install --check to see if the Pulsar package manager can build native code on your machine.

    Check out the pre-requisites in the build instructions for your platform for more details.

    Check if your GPU is causing the problem

    If you encounter flickering or other rendering issues, you can stop Pulsar from using your Graphics Processing Unit (GPU) with the --disable-gpu Chromium flag to see if the fault lies with your GPU:

    $ pulsar --disable-gpu
    +

    Chromium (and thus Pulsar) normally uses the GPU to accelerate drawing parts of the interface. --disable-gpu tells Pulsar to not even attempt to do this, and just use the CPU for rendering everything. This means that the parts of the interface that would normally be accelerated using the GPU will instead take slightly longer and render on the CPU. This likely won't make a noticeable difference, but does slightly increase the battery usage on portable devices as the CPU has to work harder to do the things the GPU is optimized for.

    Two other Chromium flags that are useful for debugging are --enable-gpu-rasterization and --force-gpu-rasterization:

    $ pulsar --enable-gpu-rasterization --force-gpu-rasterization
    +

    --enable-gpu-rasterization allows other commands to determine how a layer tile (graphics) should be drawn and --force-gpu-rasterization determines that the Skia GPU backend should be used for drawing layer tiles (only valid with GPU accelerated compositing).

    Be sure to use Chromium flags at the end of the terminal call if you want to use other Pulsar flags as they will not be executed after the Chromium flags e.g.:

    $ pulsar --safe --enable-gpu-rasterization --force-gpu-rasterization
    +

    Writing Specs

    We've looked at and written a few specs through the examples already. Now it's time to take a closer look at the spec framework itself. How exactly do you write tests in Pulsar?

    `,20),mi={href:"https://jasmine.github.io/archives/1.3/introduction",target:"_blank",rel:"noopener noreferrer"},ki=e("h3",{id:"create-a-new-spec",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#create-a-new-spec","aria-hidden":"true"},"#"),n(" Create a New Spec")],-1),gi={href:"https://github.com/pulsar-edit/pulsar/tree/master/spec",target:"_blank",rel:"noopener noreferrer"},vi={href:"https://github.com/pulsar-edit/markdown-preview/tree/master/spec",target:"_blank",rel:"noopener noreferrer"},bi=e("code",null,"spec",-1),fi=t(`

    Create a Spec File

    Spec files must end with -spec so add sample-spec.js to the spec directory.

    Add One or More describe Methods

    The describe method takes two arguments, a description and a function. If the description explains a behavior it typically begins with when; if it is more like a unit test it begins with the method name.

    describe("when a test is written", function () {
    +	// contents
    +});
    +

    or

    describe("Editor::moveUp", function () {
    +	// contents
    +});
    +

    Add One or More it Methods

    The it method also takes two arguments, a description and a function. Try and make the description flow with the it method. For example, a description of "this should work" doesn't read well as "it this should work". But a description of "should work" sounds great as "it should work".

    describe("when a test is written", function () {
    +	it("has some expectations that should pass", function () {
    +		// Expectations
    +	});
    +});
    +

    Add One or More Expectations

    `,11),yi={href:"https://jasmine.github.io/archives/1.3/introduction#section-Expectations",target:"_blank",rel:"noopener noreferrer"},wi=t(`
    describe("when a test is written", function () {
    +	it("has some expectations that should pass", function () {
    +		expect("apples").toEqual("apples");
    +		expect("oranges").not.toEqual("apples");
    +	});
    +});
    +
    Custom Matchers

    In addition to the Jasmine's built-in matchers, Pulsar includes the following:

    `,3),_i={href:"https://github.com/velesin/jasmine-jquery",target:"_blank",rel:"noopener noreferrer"},xi=t("
  • The toBeInstanceOf matcher is for the instanceof operator
  • The toHaveLength matcher compares against the .length property
  • The toExistOnDisk matcher checks if the file exists in the filesystem
  • The toHaveFocus matcher checks if the element currently has focus
  • The toShow matcher tests if the element is visible in the dom
  • ",5),qi={href:"https://github.com/pulsar-edit/pulsar/blob/master/spec/spec-helper.js",target:"_blank",rel:"noopener noreferrer"},Pi=t(`

    Asynchronous Specs

    Writing Asynchronous specs can be tricky at first. Some examples.

    Promises

    Working with promises is rather easy in Pulsar. You can use our waitsForPromise function.

    describe("when we open a file", function () {
    +	it("should be opened in an editor", function () {
    +		waitsForPromise(function () {
    +			atom.workspace
    +				.open("c.coffee")
    +				.then((editor) => expect(editor.getPath()).toContain("c.coffee"));
    +		});
    +	});
    +});
    +

    This method can be used in the describe, it, beforeEach and afterEach functions.

    describe("when we open a file", function () {
    +	beforeEach(function () {
    +		waitsForPromise(() => atom.workspace.open("c.coffee"));
    +	});
    +
    +	it("should be opened in an editor", function () {
    +		expect(atom.workspace.getActiveTextEditor().getPath()).toContain(
    +			"c.coffee"
    +		);
    +	});
    +});
    +

    If you need to wait for multiple promises use a new waitsForPromise function for each promise. (Caution: Without beforeEach this example will fail!)

    describe("waiting for the packages to load", function () {
    +	beforeEach(function () {
    +		waitsForPromise(() => atom.workspace.open("sample.js"));
    +
    +		waitsForPromise(() => atom.packages.activatePackage("tabs"));
    +
    +		waitsForPromise(() => atom.packages.activatePackage("tree-view"));
    +	});
    +
    +	it("should have waited long enough", function () {
    +		expect(atom.packages.isPackageActive("tabs")).toBe(true);
    +		expect(atom.packages.isPackageActive("tree-view")).toBe(true);
    +	});
    +});
    +

    waitsForPromise can take an additional object argument before the function. The object can have the following properties:

    • shouldReject Whether the promise should reject or resolve (default: false)
    • timeout The amount of time (in ms) to wait for the promise to be resolved or rejected (default: process.env.CI ? 60000 : 5000)
    • label The label to display if promise times out (default: 'promise to be resolved or rejected')
    describe("when we open a file", function () {
    +	it("should be opened in an editor", function () {
    +		waitsForPromise(
    +			{
    +				shouldReject: false,
    +				timeout: 5000,
    +				label: "promise to be resolved or rejected",
    +			},
    +			() =>
    +				atom.workspace
    +					.open("c.coffee")
    +					.then((editor) => expect(editor.getPath()).toContain("c.coffee"))
    +		);
    +	});
    +});
    +

    Asynchronous Functions with Callbacks

    Specs for asynchronous functions can be done using the waitsFor and runs functions. A simple example.

    describe("fs.readdir(path, cb)", function () {
    +	it("is async", function () {
    +		const spy = jasmine.createSpy("fs.readdirSpy");
    +		fs.readdir("/tmp/example", spy);
    +
    +		waitsFor(() => spy.callCount > 0);
    +
    +		runs(function () {
    +			const exp = [null, ["example.coffee"]];
    +
    +			expect(spy.mostRecentCall.args).toEqual(exp);
    +			expect(spy).toHaveBeenCalledWith(null, ["example.coffee"]);
    +		});
    +	});
    +});
    +
    `,15),Ti={href:"https://jasmine.github.io/archives/1.3/introduction#section-Asynchronous_Support",target:"_blank",rel:"noopener noreferrer"},Ci=t(`

    Running Specs

    Most of the time you'll want to run specs by triggering the window:run-package-specs command. This command is not only to run package specs, it can also be used to run Pulsar core specs when working on Pulsar itself. This will run all the specs in the current project's spec directory.

    To run a limited subset of specs use the fdescribe or fit methods. You can use those to focus a single spec or several specs. Modified from the example above, focusing an individual spec looks like this:

    describe("when a test is written", function () {
    +	fit("has some expectations that should pass", function () {
    +		expect("apples").toEqual("apples");
    +		expect("oranges").not.toEqual("apples");
    +	});
    +});
    +

    Running on CI

    `,5),Ii={href:"https://blog.atom.io/2014/04/25/ci-for-your-packages.html",target:"_blank",rel:"noopener noreferrer"},Si={href:"http://blog.atom.io/2014/07/28/windows-ci-for-your-packages.html",target:"_blank",rel:"noopener noreferrer"},ji=t(`

    Running via the Command Line

    To run tests on the command line, run Pulsar with the --test flag followed by one or more paths to test files or directories. You can also specify a --timeout option, which will force-terminate your tests after a certain number of seconds have passed.

    pulsar --test --timeout 60 ./test/test-1.js ./test/test-2.js
    +

    Customizing your test runner

    By default, package tests are run with Jasmine 1.3, which is outdated but can't be changed for compatibility reasons. You can specify your own custom test runner by including an atomTestRunner field in your package.json. Pulsar will require whatever module you specify in this field, so you can use a relative path or the name of a module in your package's dependencies.

    Your test runner module must export a single function, which Pulsar will call within a new window to run your package's tests. Your function will be called with the following parameters:

    • testPaths An array of paths to tests to run. Could be paths to files or directories.
    • buildAtomEnvironment A function that can be called to construct an instance of the atom global. No atom global will be explicitly assigned, but you can assign one in your runner if desired. This function should be called with the following parameters:
      • applicationDelegate An object responsible for Pulsar's interaction with the browser process and host OS. Use buildDefaultApplicationDelegate for a default instance. You can override specific methods on this object to prevent or test these interactions.
      • window A window global.
      • document A document global.
      • configDirPath A path to the configuration directory (usually ~/.pulsar).
      • enablePersistence A boolean indicating whether the Pulsar environment should save or load state from the file system. You probably want this to be false.
    • buildDefaultApplicationDelegate A function that builds a default instance of the application delegate, suitable to be passed as the applicationDelegate parameter to buildAtomEnvironment.
    • logFile An optional path to a log file to which test output should be logged.
    • headless A boolean indicating whether or not the tests are being run from the command line via pulsar --test.
    • legacyTestRunner This function can be invoked to run the legacy Jasmine runner, giving your package a chance to transition to a new test runner while maintaining a subset of its tests in the old environment.

    Your function should return a promise that resolves to an exit code when your tests are finished running. This exit code will be returned when running your tests via the command line.

    Handling URIs

    Packages have the ability to handle special URIs triggered from the system; for example, a package named my-package can register itself to handle any URI starting with atom://my-package/.

    WARNING

    Handling URIs triggered from other applications, like a web browser, is a powerful tool, but also one that can be jarring. You should shape your package's user experience to handle this well. In general, you should avoid taking direct action on behalf of a user. For example, a URI handler that immediately installs a package is too invasive, but a URI handler that shows the package's pane in the settings view is useful. A URI handler that begins to clone a repo is overly aggressive, but a URI handler that prompts the user to clone a repo is okay.

    Any package with a URI handler that we feel violates this guideline is subject to removal from the Pulsar package registry at our discretion.

    Modifying your package.json

    The first step to handling URIs from your package is to modify its package.json file. You should add a new key called uriHandler, and its value should be an object.

    The uriHandler object must contain a key called method with a string value that tells Pulsar which method in your package to call when a URI needs to be handled. The object can optionally include a key called deferActivation which can be set to the boolean false to prevent Pulsar from deferring activation of your package \u2014\xA0see more below.

    For example, if we want our package my-package to handle URIs with a method on our package's main module called handleURI, we could add the following to our package.json:

    "uriHandler": {
    +  "method": "handleURI"
    +}
    +

    Modifying your Main Module

    `,17),Ai=e("code",null,"atom://my-package/",-1),Ni=e("code",null,"handleURI",-1),Ri={href:"https://nodejs.org/api/url.html#url_url_parse_urlstring_parsequerystring_slashesdenotehost",target:"_blank",rel:"noopener noreferrer"},Mi=e("code",null,"url.parse(uri, true)",-1),Li=t(`

    Here's a sample package, written in JavaScript, that handles URIs with the package.json configuration we saw above.

    export default {
    +	activate() {
    +		// normal activation code here
    +	},
    +
    +	handleURI(parsedUri) {
    +		console.log(parsedUri);
    +	},
    +};
    +

    When Pulsar handles, for example, the URI atom://my-package/my/test/url?value=42&other=false, the package would log out something like the following:

    {
    +  protocol: 'atom:',
    +  slashes: true,
    +  auth: null,
    +  host: 'my-package',
    +  port: null,
    +  hostname: 'my-package',
    +  hash: null,
    +  search: '?value=true&other=false',
    +  query: { value: '42', other: 'false' },
    +  pathname: '/my/test/url',
    +  path: '/my/test/url?value=true&other=false',
    +  href: 'atom://my-package/my/test/url?value=true&other=false'
    +}
    +

    Notice that the query string arguments are available in the query property, but are strings \u2014\xA0you'll have to convert to other native types yourself.

    Controlling Activation Deferral

    For performance reasons, adding a uriHandler entry to your package's package.json will enable deferred activation. This means that Pulsar will not activate your package until it has a URI for it to handle \u2014\xA0it will then activate your package and then immediately call the URI handler method. If you want to disable the deferred activation, ensuring your package is activated upon startup, you can add "deferActivation": false to the URI handler config. For example,

    "uriHandler": {
    +  "method": "handleURI",
    +  "deferActivation": false
    +}
    +

    Before doing this, make sure your package actually needs to be activated immediately \u2014\xA0disabling deferred activation means Pulsar takes longer to start since it has to activate all packages without deferred activation.

    Linux Support

    Because URI handling is different across operating systems and distributions, there is no built-in URI handler support for Pulsar on Linux. If you want to configure URI handling on your system yourself, then you should configure atom: protocol URI's to trigger Pulsar with the --uri-handler flag; for example, the URI atom://test/uri should launch Atom via atom --uri-handler atom://test/uri.

    Core URIs

    Pulsar provides a core URI to handle opening files with the syntax atom://core/open/file?filename=<filepath>&line=<line>&column=<col>

    Cross-Platform Compatibility

    Pulsar runs on a number of platforms and while Electron and Node take care of many of the details there are still some considerations to ensure your package works on other operating systems.

    File symlinks can be used on Windows by non-Administrators by specifying 'junction' as the type (this argument is ignored on macOS & Linux).

    Also consider:

    • Symlinks committed to Git will not checkout correctly on Windows - dynamically create what you need with fs.symlink instead
    • Symlinked directories are only available to Administrators on Windows - avoid a dependency on them

    Filenames

    • Reserved filenames on Windows are com1-com9, lpt1-lpt9, con, nul, aux and prn (regardless of extension, e.g. prn.txt is disallowed)
    • Reserved characters on Windows are ? \\ / < > ? % | : " so avoid where possible
    • Names with spaces when passed to the command line;
      • Linux and macOS require a backslash before each space e.g. /my\\ test
      • Windows requires you surround the path with double quotes e.g. "c:\\my test"

    File paths

    • Windows uses \\ although some tools and PowerShell allow / too
    • macOS and Linux use /

    You can dynamically find out what your platform uses with path.sep or better yet use the node path library functions such as join and normalize which automatically take care of this.

    Windows supports up to 250 characters for a path - avoid deeply nested directory structures

    Paths are not URLs

    URL parsing routines should not be used on file paths. While they initially look like a relative path it will fail in a number of scenarios on all platforms.

    • Various characters are misinterpreted, e.g. ? as query string, # as a fragment identifier
    • Windows drive specifiers are incorrectly parsed as a protocol

    If you need to use a path for a URL use the file: protocol with an absolute path instead to ensure drive letters and slashes are appropriately addressed, e.g. file:///c|/test/pic.png

    fs.stat on directories

    The fs.stat function does not return the size of the contents of a directory but rather the allocation size of the directory itself. This returns 0 on Windows and 1024 on macOS and so should not be relied upon.

    path.relative can't traverse drives

    • On a Linux or macOS system path.relative can be used to calculate a relative path to traverse between any two given paths.
    • On Windows this is not always possible as it can contain multiple absolute roots, e.g. c:\\ and d:\\

    Rapid file operations

    `,34),Wi={href:"https://www.npmjs.com/package/rimraf",target:"_blank",rel:"noopener noreferrer"},Oi=t(`

    Line endings

    • Linux and macOS use LF
    • Windows uses CRLF
    • Git on Windows often has autocrlf set which automatically converts between the two

    If you are writing specs that use text file fixtures consider that this will interfere with file lengths, hash codes and direct text comparisons. It will also change the Atom selection length by 1 character per line.

    If you have spec fixtures that are text files you may want to tell Git to force LF, CRLF or not convert them by specifying the paths in .gitattributes e.g.

    spec/fixtures/always-crlf.txt eol=crlf
    +spec/fixtures/always-lf.txt eol=lf
    +spec/fixtures/leave-as-is.txt -text
    +

    Contributing to Official Pulsar Packages

    `,6),Ui={href:"https://github.com/pulsar-edit/pulsar",target:"_blank",rel:"noopener noreferrer"},Ei=t(`

    Hacking on Packages

    Cloning

    The first step is creating your own clone. For some packages, you may also need to install the requirements necessary for building Pulsar in order to run pulsar -p install.

    For example, if you want to make changes to the tree-view package, fork the repo on your GitHub account, then clone it:

    $ git clone https://github.com/pulsar-edit/tree-view.git
    +

    Next install all the dependencies:

    $ cd tree-view
    +$ pulsar -p install
    +> Installing modules \u2713
    +

    Now you can link it to development mode so when you run an Pulsar window with pulsar -p --dev, you will use your fork instead of the built in package:

    $ pulsar -p link -d
    +

    Running in Development Mode

    Editing a package in Pulsar is a bit of a circular experience: you're using Pulsar to modify itself. What happens if you temporarily break something? You don't want the version of Pulsar you're using to edit to become useless in the process. For this reason, you'll only want to load packages in development mode while you are working on them. You'll perform your editing in stable mode, only switching to development mode to test your changes.

    To open a development mode window, use the Application: Open Dev command. You can also run dev mode from the command line with pulsar --dev.

    To load your package in development mode, create a symlink to it in LNX/MAC: ~/.pulsar/dev/packages - WIN: %USERPROFILE%\\.pulsar\\dev\\packages. This occurs automatically when you clone the package with pulsar -p develop. You can also run pulsar -p link --dev and pulsar -p unlink --dev from the package directory to create and remove dev-mode symlinks.

    Installing Dependencies

    You'll want to keep dependencies up to date by running pulsar -p update after pulling any upstream changes.

    Creating a Fork of a Core Package

    `,16),Fi={href:"https://github.com/pulsar-edit/pulsar/tree/master/packages",target:"_blank",rel:"noopener noreferrer"},Di=e("code",null,"packages",-1),Yi=t('

    Tip

    In most cases, we recommend generating a brand new package or a brand new theme as the starting point for your creation. The guide below applies only to situations where you want to create a package that closely resembles a core Pulsar package.

    Creating Your New Package

    ',2),zi={href:"https://github.com/pulsar-edit/pulsar/tree/master/packages/one-light-ui",target:"_blank",rel:"noopener noreferrer"},Vi={href:"https://github.com/pulsar-edit/pulsar/archive/master.zip",target:"_blank",rel:"noopener noreferrer"},Hi=t("
  • Unzip the file to a temporary location (for example LNX/MAC: /tmp/pulsar - WIN: C:\\TEMP\\pulsar)

  • Copy the contents of the desired package into a working directory for your fork

  • ",2),Gi=e("div",{class:"language-bash ext-sh line-numbers-mode"},[e("pre",{class:"language-bash"},[e("code",null,[n("$ "),e("span",{class:"token function"},"cp"),n(),e("span",{class:"token parameter variable"},"-R"),n(` /tmp/pulsar/packages/one-light-ui ~/src/one-light-ui-plus +$ `),e("span",{class:"token builtin class-name"},"cd"),n(` ~/src/one-light-ui-plus +`)])]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),$i=e("div",{class:"language-bash ext-sh line-numbers-mode"},[e("pre",{class:"language-bash"},[e("code",null,[n("$ "),e("span",{class:"token function"},"cp"),n(),e("span",{class:"token parameter variable"},"-R"),n(` /tmp/pulsar/packages/one-light-ui ~/src/one-light-ui-plus +$ `),e("span",{class:"token builtin class-name"},"cd"),n(` ~/src/one-light-ui-plus + +`)])]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),Bi=e("div",{class:"language-powershell ext-powershell line-numbers-mode"},[e("pre",{class:"language-powershell"},[e("code",null,[n("$ "),e("span",{class:"token function"},"Copy-Item"),n(),e("span",{class:"token operator"},"-"),n("Path "),e("span",{class:"token string"},'"C:\\TEMP\\pulsar\\packages\\one-light-ui"'),n(),e("span",{class:"token operator"},"-"),n("Destination "),e("span",{class:"token string"},'"C:\\src\\one-light-ui-plus"'),n(),e("span",{class:"token operator"},"-"),n("Recurse "),e("span",{class:"token operator"},"-"),n(`Force +$ cd C:\\src\\one-light-ui-plus +`)])]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),Xi=t(`
    1. Create a local repository and commit the initial contents
    $ git init
    +$ git commit -am "Import core Pulsar package"
    +
    1. Update the name property in package.json to give your package a unique name

    2. Make the other customizations that you have in mind

    3. Commit your changes

    $ git commit -am "Apply initial customizations"
    +
    `,4),Ji={start:"8"},Ki={href:"https://help.github.com/articles/create-a-repo/",target:"_blank",rel:"noopener noreferrer"},Zi=e("li",null,[e("p",null,"Follow the instructions in the github.com UI to push your code to your new online repository")],-1),Qi=e("li",null,[e("p",null,[n("Follow the steps in the "),e("a",{href:"#publishing"},"Publishing guide"),n(" to publish your new package")])],-1),el=t('

    Merging Upstream Changes into Your Package

    The code in the original package will continue to evolve over time, either to fix bugs or to add new enhancements. You may want to incorporate some or all of those updates into your package. To do so, you can follow these steps for merging upstream changes into your package.

    Maintaining a Fork of a Core Package

    ',3),nl={href:"https://github.com/atom/atom/blob/master/docs/rfcs/003-consolidate-core-packages.md",target:"_blank",rel:"noopener noreferrer"},al={href:"https://github.com/atom/atom",target:"_blank",rel:"noopener noreferrer"},sl={href:"https://github.com/atom/one-light-ui",target:"_blank",rel:"noopener noreferrer"},tl={href:"https://github.com/pulsar-edit/one-light-ui",target:"_blank",rel:"noopener noreferrer"},ol=e("code",null,"packages/one-light-ui",-1),il={href:"https://github.com/pulsar-edit/pulsar/blob/master/packages/README.md",target:"_blank",rel:"noopener noreferrer"},ll=e("p",null,"If you forked one of the core packages before it was moved into the atom/atom or pulsar-edit/pulsar repository, and you want to continue merging upstream changes into your fork, please follow the steps below.",-1),rl=e("h3",{id:"step-by-step-guide",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#step-by-step-guide","aria-hidden":"true"},"#"),n(" Step-by-step guide")],-1),pl={href:"https://github.com/pulsar-edit/one-light-ui",target:"_blank",rel:"noopener noreferrer"},cl=e("code",null,"one-light-ui-plus",-1),ul=t(`

    Add pulsar-edit/pulsar as a Remote

    Navigate to your local clone of your fork:

    $ cd path/to/your/fork
    +
    `,3),dl={href:"https://github.com/pulsar-edit/pulsar",target:"_blank",rel:"noopener noreferrer"},hl=t(`
    $ git remote add upstream https://github.com/pulsar-edit/pulsar.git
    +

    Get the Latest Changes for the Core Package

    Tip

    Follow these steps each time you want to merge upstream changes into your fork.

    Fetch the latest changes from the pulsar-edit/pulsar repository:

    $ git fetch upstream
    +

    Identify recent changes to the core package. For example, if you're maintaining a fork of the one-light-ui package, then you'll want to identify recent changes in the packages/one-light-ui directory:

    $ git log --oneline upstream/master -- packages/one-light-ui
    +f884f6de8 [themes] Rename A[a]tom -> P[p]ulsar
    +0db3190f4 Additional rebranding where needed
    +234adb874 Remove deprecated code strings
    +...
    +

    Look through the log and identify the commits that you want to merge into your fork.

    Merge Upstream Changes into Your Fork

    For each commit that you want to bring into your fork, use [git format-patch][https://git-scm.com/docs/git-format-patch] in conjunction with [git am][https://git-scm.com/docs/git-am]. For example, to merge commit f884f6de8 into your fork:

    $ git format-patch -1 --stdout f884f6de8 | git am -p3
    +

    Repeat this step for each commit that you want to merge into your fork.

    Summary

    If you finished this chapter, you should be an Pulsar-hacking master. We've discussed how you should work with JavaScript and CoffeeScript, and how to put it to good use in creating packages. You should also be able to do this in your own created theme now.

    Even when something goes wrong, you should be able to debug this easily. But also fewer things should go wrong, because you are capable of writing great specs for Pulsar.

    In the next chapter, we\u2019ll go into more of a deep dive on individual internal APIs and systems of Pulsar, even looking at some Pulsar source to see how things are really getting done.

    Having trouble?

    If you have any issues then please feel free to ask for help from the Pulsar Team or the wider community via any of our Community areas

    `,18),ml={href:"https://github.com/pulsar-edit/pulsar/issues",target:"_blank",rel:"noopener noreferrer"},kl={href:"https://github.com/pulsar-edit/pulsar/issues/new?assignees=&labels=bug%2Ctriage&projects=&template=bug-report.yml",target:"_blank",rel:"noopener noreferrer"};function gl(vl,bl){const s=d("ExternalLinkIcon"),u=d("Tabs"),c=d("RouterLink");return L(),W("div",null,[U,e("p",null,[n(`Now it's time to come to the "Hackable" part of the Hyper-Hackable Editor. As we've seen throughout the second section, a huge part of Pulsar is made up of bundled packages. If you wish to add some functionality to Pulsar, you have access to the same APIs and tools that the core features of Pulsar has. From the `),e("a",E,[n("tree-view"),a(s)]),n(" to the "),e("a",F,[n("command-palette"),a(s)]),n(" to "),e("a",D,[n("find-and-replace"),a(s)]),n(" functionality, even the most core features of Pulsar are implemented as packages.")]),Y,e("p",null,[n("The Pulsar application code can be found in the "),e("a",z,[n("pulsar-edit/pulsar"),a(s)]),n(" repository.")]),V,H,e("ul",null,[e("li",null,[n("Node.js (version specified in "),e("a",G,[n("pulsar/.nvmrc"),a(s)]),n(", recommended installation is via "),e("a",$,[n("nvm"),a(s)]),n(")")]),B,X,J,K,e("li",null,[e("a",Z,[n("Libsecret"),a(s)]),n(" development headers")])]),Q,a(u,{id:"182",data:[{title:"Linux"},{title:"macOS"},{title:"Windows"}],"tab-id":"core-hacking"},{tab0:o(({title:l,value:r,isActive:p})=>[ee,ne,ae,se,te,oe,ie,le]),tab1:o(({title:l,value:r,isActive:p})=>[re]),tab2:o(({title:l,value:r,isActive:p})=>[e("p",null,[n("Firstly install "),e("a",pe,[n("Visual Studio"),a(s)]),n(" from Microsoft.")])]),_:1}),ce,e("p",null,[n("Install Node.js (using "),ue,n(" - see above) and enable corepack (for "),de,n("). This will install the version of Node.js specified in "),e("a",he,[n("pulsar/.nvmrc"),a(s)]),n(":")]),me,a(u,{id:"252",data:[{title:"Linux"},{title:"macOS"},{title:"Windows"}],"tab-id":"core-hacking"},{tab0:o(({title:l,value:r,isActive:p})=>[ke,ge]),tab1:o(({title:l,value:r,isActive:p})=>[ve,be]),tab2:o(({title:l,value:r,isActive:p})=>[fe,ye]),_:1}),we,_e,xe,a(u,{id:"317",data:[{title:"Linux"},{title:"macOS"},{title:"Windows"}],"tab-id":"core-hacking"},{tab0:o(({title:l,value:r,isActive:p})=>[qe]),tab1:o(({title:l,value:r,isActive:p})=>[Pe]),tab2:o(({title:l,value:r,isActive:p})=>[Te]),_:1}),Ce,e("ol",null,[Ie,e("li",null,[n("Packages that contain stylesheets, such as syntax themes, will have those stylesheets automatically reloaded by the "),e("a",Se,[n("dev-live-reload"),a(s)]),n(" package. This does not live reload JavaScript or CoffeeScript files \u2014 you'll need to reload the window ("),je,n(") to see changes to those.")])]),Ae,i("This whole section needs to be reworked once decaff properly starts on the core"),e("p",null,[n('While much of Pulsar has been converted to JavaScript, a lot of older code is still implemented in CoffeeScript but the process of "decaffeination" is ongoing, to continue this conversion feel free to read '),e("a",Ne,[n("more"),a(s)]),n(". Additionally, Pulsar's default configuration language is CSON, which is based on CoffeeScript. If you don't know CoffeeScript, but you are familiar with JavaScript, you shouldn't have too much trouble. Here is an example of some simple CoffeeScript code:")]),Re,e("p",null,[n("We'll go over examples like this in a bit, but this is what the language looks like. Just about everything you can do with CoffeeScript in Pulsar is also doable in JavaScript. You can brush up on CoffeeScript at "),e("a",Me,[n("coffeescript.org"),a(s)]),n(".")]),e("p",null,[n("Less is an even simpler transition from CSS. It adds a number of useful things like variables and functions to CSS. You can learn about Less at "),e("a",Le,[n("lesscss.org"),a(s)]),n(". Our usage of Less won't get too complex in this book however, so as long as you know basic CSS you should be fine.")]),We,e("div",Oe,[Ue,e("p",null,[n("The default init file for Pulsar has been changed from the previous CoffeeScript "),Ee,n(" file used by Atom to JavaScript. The CoffeeScript file will still work but should you wish to reference the specific version of this document for it then you should look at the "),a(c,{to:"/docs/atom-archive/hacking-atom/#the-init-file"},{default:o(()=>[n("Atom Archive")]),_:1}),n(".")])]),e("p",null,[n("When Pulsar finishes loading, it will evaluate "),Fe,n(" in your "),De,n(": "),Ye,n(" - "),ze,n(": "),Ve,n(" directory, giving you a chance to run JavaScript code to make customizations. Code in this file has full access to "),e("a",He,[n("Pulsar's API"),a(s)]),n("."),i("TODO: Replace link when we have the API documented"),n(" If customizations become extensive, consider creating a package, which we will cover in "),Ge,n(".")]),$e,i("TODO: All API links to be updated when it is documented"),e("p",null,[n("Because "),Be,n(" provides access to Pulsar's API, you can use it to implement useful commands without creating a new package or extending an existing one. Here's a command which uses the "),e("a",Xe,[n("Selection API"),a(s)]),n(" and "),e("a",Je,[n("Clipboard API"),a(s)]),n(" to construct a Markdown link from the selected text and the clipboard contents as the URL:")]),Ke,e("p",null,[n("The simplest way to start a package is to use the built-in package generator that ships with Pulsar. As you might expect by now, this generator is itself a separate package implemented in "),e("a",Ze,[n("package-generator"),a(s)]),n(".")]),Qe,e("div",en,[nn,e("p",null,[n("You may encounter a situation where your package is not loaded. That is because a new package using the same name as an actual package hosted on "),e("a",an,[n("pulsar-edit.dev"),a(s)]),n(' (e.g. "wordcount" and "word-count") is not being loaded as you expected. If you follow our suggestion above of using the '),sn,n(" package name, you "),tn,n(" be safe \u{1F600}")])]),on,e("p",null,[n("Similar to "),e("a",ln,[n("Node modules"),a(s)]),n(", Pulsar packages contain a "),rn,n(' file in their top-level directory. This file contains metadata about the package, such as the path to its "main" module, library dependencies, and manifests specifying the order in which its resources should be loaded.')]),e("p",null,[n("In addition to some of the regular "),e("a",pn,[n("Node "),cn,n(" keys"),a(s)]),n(" available, Pulsar "),un,n(" files have their own additions.")]),dn,e("p",null,[n("Style sheets for your package should be placed in the "),hn,n(" directory. Any style sheets in this directory will be loaded and attached to the DOM when your package is activated. Style sheets can be written as CSS or "),e("a",mn,[n("Less"),a(s)]),n(", but Less is recommended.")]),kn,e("p",null,[n("If you "),gn,n(" need special styling, try to keep only structural styles in the package style sheets. If you "),vn,n(" specify colors and sizing, these should be taken from the active theme's "),e("a",bn,[n("ui-variables.less"),a(s)]),n(".")]),fn,e("p",null,[n("Let's look at the 3 lines we've added. First we get an instance of the current editor object (where our text to count is) by calling "),e("a",yn,[wn,a(s)]),n(". "),i("TODO: Update when API is documented")]),e("p",null,[n("Next we get the number of words by calling "),e("a",_n,[xn,a(s)]),i("TODO: Update when API is documented"),n(" on our new editor object, then splitting that text on whitespace with a regular expression and then getting the length of that array.")]),qn,e("p",null,[n("Under the hood, "),e("a",Pn,[n("Jasmine v1.3"),a(s)]),n(" executes your tests, so you can assume that any DSL available there is also available to your package.")]),Tn,e("p",null,[n("Now that we have our first package written, let's go through examples of other types of packages we can make. This section will guide you though creating a simple command that replaces the selected text with "),e("a",Cn,[n("ascii art"),a(s)]),n('. When you run our new command with the word "cool" selected, it will be replaced with:')]),In,Sn,jn,An,e("p",null,[n("To begin, press "),Nn,n(": "),Rn,n(" - "),Mn,n(": "),Ln,n(" to bring up the "),e("a",Wn,[n("Command Palette"),a(s)]),n('. Type "generate package" and select the "Package Generator: Generate Package" command, just as we did in '),On,n(". Enter "),Un,n(" as the name of the package.")]),En,e("p",null,[n("Next we insert a string into the current text editor with the "),e("a",Fn,[Dn,a(s)]),i("TODO: Replace with API link once documented)"),n(' method. This will insert the text wherever the cursor currently is in the current editor. If there are selections, it will replace all selections with the "Hello, World!" text.')]),Yn,e("p",null,[n("Now we need to convert the selected text to ASCII art. To do this we will use the "),e("a",zn,[n("figlet"),a(s)]),n(" Node module from "),e("a",Vn,[n("npm"),a(s)]),n(". Open "),Hn,n(" and add the latest version of figlet to the dependencies:")]),Gn,e("p",null,[n("There are a couple of new things in this example we should look at quickly. The first is the "),e("a",$n,[Bn,a(s)]),i("TODO: Replace with API link once documented)"),n(" which, as you might guess, returns the text that is currently selected.")]),e("p",null,[n("We then call the Figlet code to convert that into something else and replace the current selection with it with the "),e("a",Xn,[Jn,a(s)]),i("TODO: Replace with API link once documented)"),n(" call.")]),Kn,i("Below was part of this section in the original docs but as Nuclide is retired we either need a new example or remove this entirely"),i(`This system is used by Pulsar's tree view, as well as by third party +packages like [Nuclide](https://nuclide.io) for its console, debugger, outline +view, and diagnostics (linter results).`),e("p",null,[n("For this package, we'll define a workspace item that tells us some information about our active text editor. The final package can be viewed at "),e("a",Zn,[n("https://github.com/pulsar-edit/active-editor-info"),a(s)]),n(".")]),Qn,e("p",null,[n("To begin, press "),ea,n(": "),na,n(" - "),aa,n(": "),sa,n(" to bring up the "),e("a",ta,[n("Command Palette"),a(s)]),n('. Type "generate package" and select the '),oa,n(" command, just as we did in "),ia,n(". Enter "),la,n(" as the name of the package.")]),ra,e("p",null,[n("Pulsar's interface is rendered using HTML, and it's styled via "),e("a",pa,[n("Less"),a(s)]),n(" which is a superset of CSS. Don't worry if you haven't heard of Less before; it's just like CSS, but with a few handy extensions.")]),ca,e("ul",null,[e("li",null,[n("Less is a superset of CSS, but it has some really handy features like variables. If you aren't familiar with its syntax, take a few minutes to "),e("a",ua,[n("familiarize yourself"),a(s)]),n(".")]),da,e("li",null,[n("You can find existing themes to install or fork in "),e("a",ha,[n("Pulsar Package Repository"),a(s)]),n(". "),i("TODO: Update to a themes URL if we get one on the front end site")])]),ma,e("ol",null,[e("li",null,[n("Fork the "),e("a",ka,[n("ui-theme-template"),a(s)])]),ga]),va,e("ul",null,[e("li",null,[e("a",ba,[n("ui-variables.less"),a(s)])]),e("li",null,[e("a",fa,[n("syntax-variables.less"),a(s)])])]),ya,wa,_a,e("p",null,[n("Your package should generally only specify structural styling, and these should come from "),e("a",xa,[n("the style guide"),a(s)]),n(". Your package shouldn't specify colors, padding sizes, or anything in absolute pixels. You should instead use the theme variables. If you follow this guideline, your package will look good out of the box with any theme!")]),qa,e("p",null,[n("Reloading by pressing "),Pa,n(": "),Ta,n(" - "),Ca,n(": "),Ia,n(" after you make changes to your theme is less than ideal. Pulsar supports "),e("a",Sa,[n("live updating"),a(s)]),n(" of styles on Pulsar windows in Dev Mode.")]),ja,e("p",null,[n("Check out Google's "),e("a",Aa,[n("extensive tutorial"),a(s)]),n(" for a short introduction.")]),Na,e("p",null,[n("If you are creating an UI theme, you'll want a way to see how your theme changes affect all the components in the system. The "),e("a",Ra,[n("Styleguide"),a(s)]),n(" is a page that renders every component Pulsar supports.")]),Ma,e("p",null,[n("Follow the steps on the "),a(c,{to:"/docs/launch-manual/sections/core-hacking/#publishing/"},{default:o(()=>[n("Publishing")]),_:1}),n(" page. The example used is for the Word Count package, but publishing a theme works exactly the same.")]),La,i("This may need to be thoroughly reworked if we change the tree-sitter implementation"),e("p",null,[n("Pulsar's syntax highlighting and code folding system is powered by "),e("a",Wa,[n("Tree-sitter"),a(s)]),n(". Tree-sitter parsers create and maintain full "),e("a",Oa,[Ua,a(s)]),n(" representing your code.")]),Ea,e("p",null,[n("Tree-sitter generates parsers based on "),e("a",Fa,[n("context-free grammars"),a(s)]),n(" that are typically written in JavaScript. The generated parsers are C libraries that can be used in other applications as well as Pulsar.")]),e("p",null,[n("They can also be developed and tested at the command line, separately from Pulsar. Tree-sitter has "),e("a",Da,[n("its own documentation page"),a(s)]),n(" on how to create these parsers. The "),e("a",Ya,[n("Tree-sitter GitHub organization"),a(s)]),n(" also contains a lot of example parsers that you can learn from, each in its own repository.")]),e("p",null,[n("Once you have created a parser, you need to publish it to "),e("a",za,[n("the NPM registry"),a(s)]),n(" to use it in Pulsar. To do this, make sure you have a "),Va,n(" and "),Ha,n(" in your parser's "),Ga,n(":")]),$a,e("ul",null,[Ba,Xa,e("li",null,[Ja,n(" - The name of the parser node module that will be used for parsing. This string will be passed directly to "),e("a",Ka,[Za,a(s)]),n(" in order to load the parser.")]),Qa]),es,e("p",null,[n("Note that in this selector, we're using the "),e("a",ns,[n("immediate child combinator"),a(s)]),n(" ("),as,n("). Arbitrary descendant selectors without this combinator (for example "),ss,n(", which would match any "),ts,n(" occurring anywhere within a "),os,n(") are currently not supported.")]),is,e("p",null,[n("You can use the "),e("a",ls,[rs,n(" pseudo-class"),a(s)]),n(" to select nodes based on their order within their parent. For example, this example selects "),ps,n(" nodes which are the fourth (zero-indexed) child of a "),cs,n(" node.")]),us,e("p",null,[n("Finally, you can use double-quoted strings in the selectors to select "),ds,n(" tokens in the syntax tree, like "),hs,n(" and "),ms,n(". See "),e("a",ks,[n("the Tree-sitter documentation"),a(s)]),n(" for more information about named vs anonymous tokens.")]),gs,e("p",null,[n("If multiple selectors in the "),vs,n(" object match a node, the node's classes will be decided based on the "),e("a",bs,[n("most specific"),a(s)]),n(" selector. Note that the "),fs,n(" and "),ys,n(" rules do "),ws,n(" affect specificity, so you may need to supply the same "),_s,n(" or "),xs,n(" rules for multiple selectors to ensure that they take precedence over other selectors. You can use the same selector multiple times in a scope mapping, within different comma-separated keys:")]),qs,e("p",null,[n("For example, in JavaScript, "),e("a",Ps,[n("tagged template literals"),a(s)]),n(" sometimes contain code written in a different language, and the name of the language is often used in the 'tag' function, as shown in this example:")]),Ts,e("ul",null,[e("li",null,[e("a",Cs,[n("Bash"),a(s)])]),e("li",null,[e("a",Is,[n("C"),a(s)])]),e("li",null,[e("a",Ss,[n("Go"),a(s)])]),e("li",null,[e("a",js,[n("HTML"),a(s)])]),e("li",null,[e("a",As,[n("JavaScript"),a(s)])]),e("li",null,[e("a",Ns,[n("Python"),a(s)])]),e("li",null,[e("a",Rs,[n("Ruby"),a(s)])]),e("li",null,[e("a",Ms,[n("TypeScript"),a(s)])])]),Ls,e("p",null,[n("Grammar files are written in the "),e("a",Ws,[n("CSON"),a(s)]),n(" or "),e("a",Os,[n("JSON"),a(s)]),n(" format. Whichever one you decide to use is up to you, but this tutorial will be written in CSON.")]),Us,e("p",null,[n("The default package template creates a lot of folders that aren't needed for grammar packages. Go ahead and delete the "),Es,n(", "),Fs,n(", "),Ds,n(", and "),Ys,n(" folders. Furthermore, in "),zs,n(", remove the "),Vs,n(" section. Now create a new folder called "),Hs,n(", and inside that a file called "),Gs,n(". This is the main file that we will be working with - start by populating it with a "),e("a",$s,[n("boilerplate template"),a(s)]),n(". Now let's go over what each key means.")]),Bs,e("p",null,[Xs,n(" is where your regex is contained, and "),Js,n(" is the scope name that is to be applied to the entirety of the match. More information about scope names can be found in "),e("a",Ks,[n("Section 12.4 of the TextMate Manual"),a(s)]),n(".")]),Zs,e("ul",null,[e("li",null,[e("a",Qs,[n("DamnedScholar's Gist"),a(s)]),n(". Provides a template of most keys, each with a short comment explaining their function.")]),e("li",null,[e("a",et,[n("Aerijo's Gist"),a(s)]),n(". Another guide that attempts to fully explain making a grammar package for users of all levels.")]),e("li",null,[e("a",nt,[n("http://www.apeth.com/nonblog/stories/textmatebundle.html"),a(s)]),n(". A blog of a programmer's experience writing a grammar package for TextMate.")]),e("li",null,[e("a",at,[n("Oniguruma docs"),a(s)]),n(". The documentation for the regex engine Pulsar uses.")]),e("li",null,[e("a",st,[n("TextMate Section 12"),a(s)]),n(". Pulsar uses the same principles as laid out here, including the list of acceptable scopes.")]),e("li",null,[e("a",tt,[ot,a(s)]),n(". Not necessary to write a grammar, but a good technical reference for what Pulsar is doing behind the scenes.")]),e("li",null,[n("Look at any existing packages, such as the ones for "),e("a",it,[n("Python"),a(s)]),n(", "),e("a",lt,[n("JavaScript"),a(s)]),n(", "),e("a",rt,[n("HTML"),a(s)]),n(", "),e("a",pt,[n("and more"),a(s)]),n(".")])]),i(` (This is left from the original Atom flight-manual) +TODO: +* \`repository\` and including from repository patterns +* Wrap-up +* Implement Package Generator functionality to generate a grammar +* Intermediate + advanced grammar tutorials +`),ct,e("p",null,[n("It's possible that you have themes or grammars from "),e("a",ut,[n("TextMate"),a(s)]),n(" that you like and use and would like to convert to Pulsar. If so, you're in luck because there are tools to help with the conversion.")]),dt,ht,e("p",null,[n("Let's convert the TextMate bundle for the "),e("a",mt,[n("R"),a(s)]),n(" programming language. You can find other existing TextMate bundles "),e("a",kt,[n("on GitHub"),a(s)]),n(".")]),gt,e("p",null,[n("This section will go over how to convert a "),e("a",vt,[n("TextMate"),a(s)]),n(" theme to an Pulsar theme.")]),bt,e("p",null,[n("TextMate themes use "),e("a",ft,[n("plist"),a(s)]),n(" files while Pulsar themes use "),e("a",yt,[n("CSS"),a(s)]),n(" or "),e("a",wt,[n("Less"),a(s)]),n(" to style the UI and syntax in the editor.")]),_t,e("p",null,[n("Pulsar bundles a command line utility called "),xt,n(" which we first used back in "),a(c,{to:"/docs/launch-manual/sections/using-pulsar/#command-line"},{default:o(()=>[n("Command Line")]),_:1}),n(" to search for and install packages via the command line. This is invoked by using the "),qt,n(" command with the "),Pt,n(" or "),Tt,n(" option. The "),Ct,n(" command can also be used to publish Pulsar packages to the public registry and update them.")]),It,St,jt,e("ul",null,[At,e("li",null,[n("Your "),Nt,n(),Rt,n(" field is "),e("a",Mt,[n("Semver V2"),a(s)]),n(" compliant.")]),Lt,e("li",null,[n("Your package is in a Git repository that has been pushed to "),e("a",Wt,[n("GitHub"),a(s)]),n(". Follow "),e("a",Ot,[n("this guide"),a(s)]),n(" if your package isn't already on GitHub.")])]),Ut,e("p",null,[n("Before you publish a package it is a good idea to check ahead of time if a package with the same name has already been published to "),e("a",Et,[n("the Pulsar Package Repository"),a(s)]),n(". You can do that by visiting "),Ft,n(" to see if the package already exists. If it does, update your package's name to something that is available before proceeding.")]),Dt,e("ol",null,[Yt,zt,e("li",null,[n("Creates a new "),e("a",Vt,[n("Git tag"),a(s)]),n(" for the version being published.")]),Ht,Gt]),$t,i(" TODO: Rewrite this Section once Authentication Information is Public "),e("p",null,[n("If this is the first package you are publishing, the "),Bt,n(" command may prompt you for your GitHub username and password. If you have two-factor authentication enabled, use a "),e("a",Xt,[n("personal access token"),a(s)]),n(" in lieu of a password. This is required to publish and you only need to enter this information the first time you publish. The credentials are stored securely in your "),e("a",Jt,[n("keychain"),a(s)]),n(" once you login.")]),Kt,e("p",null,[n("Check out "),e("a",Zt,[n("semantic versioning"),a(s)]),n(" to learn more about best practices for versioning your package releases.")]),Qt,eo,e("p",null,[n("Pulsar comes bundled with the "),e("a",no,[n("Octicons 4.4.0"),a(s)]),n(" icon set. Use them to add icons to your packages.")]),ao,e("p",null,[n("Although icons can make your UI visually appealing, when used without a text label, it can be hard to guess its meaning. In cases where space for a text label is insufficient, consider adding a "),e("a",so,[n("tooltip"),a(s)]),i("TODO: Needs updating once we have the API documented"),n(" that appears on hover. Or a more subtle "),to,n(" attribute would help as well.")]),oo,e("p",null,[n("Pulsar provides several tools to help you understand unexpected behavior and debug problems. This guide describes some of those tools and a few approaches to help you debug and provide more helpful information when "),e("a",io,[n("submitting issues"),a(s)]),n(":")]),lo,i("The below does not exist yet, uncomment or update when it is"),i("Then check for the [latest released version](https://github.com/pulsar-edit/pulsar/releases/latest)."),e("p",null,[n("You can find the latest releases on the "),e("a",ro,[n("Pulsar Website"),a(s)]),n(", follow the links for either the latest release or Cirrus CI version. Make sure to mention which version when logging an issue.")]),i(` TODO: This section may need re-instating once/if we have auto-updates working again +::: tabs#Updating + +@tab Linux + +To update to the latest version, you can download it from +[the Pulsar website](https://pulsar-edit.dev/) or [the latest release on GitHub](https://github.com/atom/atom/releases/latest) +and follow the [Installation instructions for Pulsar on Linux](../../getting-started/#installing-pulsar). + +@tab macOS + +If there is a more recent release available, you can update to the most recent +release with the auto-update functionality built in to Pulsar and the +[about package](https://github.com/pulsar-edit/pulsar/tree/master/packages/about). +You can open the About View by using the _Pulsar > About_ menu option to see +whether Pulsar is up-to-date, downloading a new update or click the button to +"Restart and Install Update". + +@tab Windows + +If there is a more recent release available, you can update to the most recent +release with the auto-update functionality built in to Pulsar and the +[about package](https://github.com/pulsar-edit/pulsar/tree/master/packages/about). +You can open the About View by using the _Help > About_ menu option to see +whether Pulsar is up-to-date, downloading a new update or click the button to +"Restart and Install Update". + +::: + +`),po,a(u,{id:"2174",data:[{title:"Linux"},{title:"macOS"},{title:"Windows"}],"tab-id":"debugging"},{tab0:o(({title:l,value:r,isActive:p})=>[co]),tab1:o(({title:l,value:r,isActive:p})=>[uo]),tab2:o(({title:l,value:r,isActive:p})=>[ho]),_:1}),mo,e("p",null,[n("If you have packages installed that use native Node modules, when you upgrade to a new version of Pulsar, they might need to be rebuilt. Pulsar detects this and through the "),e("a",ko,[n("incompatible-packages package"),a(s)]),n(" displays an indicator in the status bar when this happens.")]),go,vo,bo,fo,e("p",null,[n("Open Pulsar's "),e("a",yo,[n("Settings View"),a(s)]),n(" with "),wo,n(": "),_o,n(" - "),xo,n(": "),qo,n(", the "),Po,n(": "),To,n(" - "),Co,n(": "),Io,n(" - "),So,n(": "),jo,n(" menu option, or the "),Ao,n(" command from the "),e("a",No,[n("Command Palette"),a(s)]),n(".")]),Ro,e("p",null,[n("Check Pulsar's settings in the Settings View, there's a description of most configuration options in the "),a(c,{to:"/docs/launch-manual/sections/using-pulsar/#configuration-key-reference"},{default:o(()=>[n("Basic Customization section")]),_:1}),n('. For example, if you want Pulsar to hide the invisible symbols representing whitespace characters, disable the "Show Invisibles" option.')]),Mo,e("p",null,[n("Since Pulsar ships with a set of packages and you can also install additional packages yourself, check the list of packages and their settings. For instance, if you'd like to get rid of the vertical line in the middle of the editor, disable the "),e("a",Lo,[n("Wrap Guide package"),a(s)]),n(". And if you don't like it when Pulsar strips trailing whitespace or ensures that there's a single trailing newline in the file, you can configure that in the "),e("a",Wo,[n("whitespace package's"),a(s)]),n(" settings.")]),Oo,Uo,e("p",null,[n("You might have defined some custom styles, keymaps or snippets in "),a(c,{to:"/docs/launch-manual/sections/using-pulsar/#basic-customization/"},{default:o(()=>[n("one of your configuration files")]),_:1}),n(". In some situations, these personal hacks might be causing the unexpected behavior you're observing so try clearing those files and restarting Pulsar.")]),Eo,e("p",null,[n("If a command is not executing when you press a key combination or the wrong command is executing, there might be an issue with the keybinding for that combination. Pulsar ships with the "),e("a",Fo,[n("Keybinding Resolver"),a(s)]),n(", a neat package which helps you understand what key Pulsar saw you press and the command that was triggered because of it.")]),Do,e("p",null,[n("If multiple keybindings are matched, Pulsar determines which keybinding will be executed based on the "),a(c,{to:"/docs/launch-manual/sections/behind-pulsar/#specificity-and-cascade-order"},{default:o(()=>[n("specificity of the selectors and the order in which they were loaded")]),_:1}),n(". If the command you wanted to trigger is listed in the Keybinding Resolver, but wasn't the one that was executed, this is normally explained by one of two causes:")]),Yo,e("p",null,[n("Pulsar loads core Pulsar keybindings and package keybindings first, and user-defined keybindings last. Since user-defined keybindings are loaded last, you can use your "),zo,n(" file to tweak the keybindings and sort out problems like these. See the "),a(c,{to:"/docs/launch-manual/sections/behind-pulsar/#keymaps-in-depth"},{default:o(()=>[n("Keymaps in Depth section")]),_:1}),n(" for more information.")]),e("p",null,[n("If you notice that a package's keybindings are taking precedence over core Pulsar keybindings, it might be a good idea to report the issue on that package's GitHub repository. You can contact Pulsar maintainers on "),e("a",Vo,[n("Pulsar's github discussions"),a(s)]),n(".")]),Ho,e("p",null,[n("You can determine which fonts are being used to render a specific piece of text by using the Developer Tools. To open the Developer Tools press "),Go,n(": "),$o,n(" - "),Bo,n(": "),Xo,n('. Once the Developer Tools are open, click the "Elements" tab. Use the '),e("a",Jo,[n("standard tools for finding the element"),a(s)]),n(' containing the text you want to check. Once you have selected the element, you can click the "Computed" tab in the styles pane and scroll to the bottom. The list of fonts being used will be shown there:')]),Ko,a(u,{id:"2351",data:[{title:"Linux"},{title:"macOS"},{title:"Windows"}],"tab-id":"debugging"},{tab0:o(({title:l,value:r,isActive:p})=>[Zo]),tab1:o(({title:l,value:r,isActive:p})=>[Qo,ei,ni]),tab2:o(({title:l,value:r,isActive:p})=>[ai]),_:1}),si,e("p",null,[n("If Pulsar is taking a long time to start, you can use the "),e("a",ti,[n("Timecop package"),a(s)]),n(" to get insight into where Pulsar spends time while loading.")]),oi,ii,li,ri,pi,e("p",null,[n("If you're experiencing performance problems in a particular situation, your "),e("a",ci,[n("Issue reports"),a(s)]),n(" will be more valuable if you include a saved profile from Chrome's CPU profiler that gives some insight into what is slow.")]),ui,e("p",null,[n("To learn more, check out the "),e("a",di,[n("Chrome documentation on CPU profiling"),a(s)]),n(".")]),hi,e("p",null,[n("Pulsar uses "),e("a",mi,[n("Jasmine"),a(s)]),n(" as its spec framework. Any new functionality should have specs to guard against regressions.")]),ki,e("p",null,[e("a",gi,[n("Pulsar specs"),a(s)]),n(" and "),e("a",vi,[n("package specs"),a(s)]),n(" are added to their respective "),bi,n(" directory. The example below creates a spec for Pulsar core.")]),fi,e("p",null,[n("The best way to learn about expectations is to read the "),e("a",yi,[n("Jasmine documentation"),a(s)]),n(" about them. Below is a simple example.")]),wi,e("ul",null,[e("li",null,[e("a",_i,[n("jasmine-jquery"),a(s)])]),xi]),e("p",null,[n("These are defined in "),e("a",qi,[n("spec/spec-helper.js"),a(s)]),n(".")]),Pi,e("p",null,[n("For a more detailed documentation on asynchronous tests please visit the "),e("a",Ti,[n("Jasmine documentation"),a(s)]),n(".")]),Ci,e("p",null,[n("It is now easy to run the specs in a CI environment like Travis and AppVeyor. See the "),e("a",Ii,[n("Travis CI For Your Packages"),a(s)]),n(" and "),e("a",Si,[n("AppVeyor CI For Your Packages"),a(s)]),n(" posts for more details.")]),ji,e("p",null,[n("Now that we've told Pulsar that we want our package to handle URIs beginning with "),Ai,n(" via our "),Ni,n(" method, we need to actually write this method. Pulsar passes two arguments to your URI handler method; the first one is the fully-parsed URI plus query string, "),e("a",Ri,[n("parsed with Node's "),Mi,a(s)]),n(". The second argument is the raw, string URI; this is normally not needed since the first argument gives you structured information about the URI.")]),Li,e("p",null,[n("Creation and deletion operations may take a few milliseconds to complete. If you need to remove many files and folders consider "),e("a",Wi,[n("RimRAF"),a(s)]),n(" which has built-in retry logic for this.")]),Oi,e("p",null,[n("If you discover a bug or issue with an official Pulsar package then feel free to open up the issue in that specific repository instead. When in doubt just open the issue on the "),e("a",Ui,[n("pulsar-edit/pulsar"),a(s)]),n(" repository but be aware that it may get transferred to the proper package's repository.")]),Ei,e("p",null,[n("Several of Pulsar's core packages are maintained in the "),e("a",Fi,[Di,n(" directory of the pulsar-edit/pulsar repository"),a(s)]),n(". If you would like to use one of these packages as a starting point for your own package, please follow the steps below.")]),Yi,i("Could this be made better with GH CLI?"),e("p",null,[n("For the sake of this guide, let's assume that you want to start with the current code in the "),e("a",zi,[n("one-light-ui"),a(s)]),n(' package, make some customizations to it, and publish your new package under the name "one-light-ui-plus".')]),e("ol",null,[e("li",null,[e("p",null,[n("Download the "),e("a",Vi,[n("current contents of the pulsar-edit/pulsar repository as a zip file"),a(s)])])]),Hi]),a(u,{id:"3062",data:[{title:"Linux"},{title:"macOS"},{title:"Windows"}],"tab-id":"core-hacking"},{tab0:o(({title:l,value:r,isActive:p})=>[Gi]),tab1:o(({title:l,value:r,isActive:p})=>[$i]),tab2:o(({title:l,value:r,isActive:p})=>[Bi]),_:1}),Xi,e("ol",Ji,[e("li",null,[e("p",null,[e("a",Ki,[n("Create a public repository on github.com"),a(s)]),n(" for your new package")])]),Zi,Qi]),el,e("p",null,[n("Originally, each of Atom's core packages resided in a separate repository. In 2018, in an effort to streamline the development of Atom by reducing overhead, the Atom team "),e("a",nl,[n("consolidated many core Atom packages"),a(s)]),n(" into the "),e("a",al,[n("atom/atom repository"),a(s)]),n(". For example, the one-light-ui package was originally maintained in the "),e("a",sl,[n("atom/one-light-ui"),a(s)]),n(" repository, but was moved to the "),e("a",tl,[ol,n(" directory"),a(s)]),n(" in the main repository.")]),e("p",null,[n("The Pulsar team has continued this trend and has move even more packages into the core, particularly default language packages. A list of these packages moved can be found in "),e("a",il,[n("this document"),a(s)]),n(".")]),ll,rl,e("p",null,[n("For the sake of this guide, let's assume that you forked the "),e("a",pl,[n("pulsar-edit/one-light-ui"),a(s)]),n(" repository, renamed your fork to "),cl,n(", and made some customizations.")]),ul,e("p",null,[n("Add the "),e("a",dl,[n("pulsar-edit/pulsar repository"),a(s)]),n(" as a git remote:")]),hl,e("p",null,[n("If you think you have found a bug then please have a look through our existing "),e("a",ml,[n("issues"),a(s)]),n(" and if you can't find anything then please "),e("a",kl,[n("create a new bug report"),a(s)]),n(".")])])}const Pl=M(O,[["render",gl],["__file","index.html.vue"]]);export{Pl as default}; diff --git a/assets/index.html.09d5e6bd.js b/assets/index.html.09d5e6bd.js new file mode 100644 index 0000000000..569da0b948 --- /dev/null +++ b/assets/index.html.09d5e6bd.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-033b47ca","path":"/docs/launch-manual/","title":"Launch Manual","lang":"en-US","frontmatter":{"title":"Launch Manual"},"excerpt":"","headers":[{"level":2,"title":"Getting Started","slug":"getting-started","link":"#getting-started","children":[]},{"level":2,"title":"Using Pulsar","slug":"using-pulsar","link":"#using-pulsar","children":[]},{"level":2,"title":"Hacking the Core","slug":"hacking-the-core","link":"#hacking-the-core","children":[]},{"level":2,"title":"Behind Pulsar","slug":"behind-pulsar","link":"#behind-pulsar","children":[]},{"level":2,"title":"FAQ","slug":"faq","link":"#faq","children":[]}],"git":{"updatedTime":1668992554000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":4},{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":0.78,"words":233},"filePathRelative":"docs/launch-manual/index.md"}');export{e as data}; diff --git a/assets/index.html.0f592a40.js b/assets/index.html.0f592a40.js new file mode 100644 index 0000000000..7134df0fa9 --- /dev/null +++ b/assets/index.html.0f592a40.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-7ab1f304","path":"/tag/chocolatey/","title":"chocolatey Tag","lang":"en-US","frontmatter":{"title":"chocolatey Tag","blog":{"type":"category","name":"chocolatey","key":"tag"},"layout":"Blog"},"excerpt":"","headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null}');export{e as data}; diff --git a/assets/index.html.100503fe.js b/assets/index.html.100503fe.js new file mode 100644 index 0000000000..6e57745254 --- /dev/null +++ b/assets/index.html.100503fe.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-4a89825a","path":"/tag/windows/","title":"windows Tag","lang":"en-US","frontmatter":{"title":"windows Tag","blog":{"type":"category","name":"windows","key":"tag"},"layout":"Blog"},"excerpt":"","headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null}');export{e as data}; diff --git a/assets/index.html.10d7d674.js b/assets/index.html.10d7d674.js new file mode 100644 index 0000000000..12aaf0fb30 --- /dev/null +++ b/assets/index.html.10d7d674.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-109540fd","path":"/category/survey/","title":"survey Category","lang":"en-US","frontmatter":{"title":"survey Category","blog":{"type":"category","name":"survey","key":"category"},"layout":"Blog"},"excerpt":"","headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null}');export{e as data}; diff --git a/assets/index.html.10df3af1.js b/assets/index.html.10df3af1.js new file mode 100644 index 0000000000..2fbc2a52ae --- /dev/null +++ b/assets/index.html.10df3af1.js @@ -0,0 +1 @@ +const t=JSON.parse('{"key":"v-154dc4c4","path":"/star/","title":"Star","lang":"en-US","frontmatter":{"title":"Star","blog":{"type":"type","key":"star"},"layout":"Blog"},"excerpt":"","headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null}');export{t as data}; diff --git a/assets/index.html.10fdafc2.js b/assets/index.html.10fdafc2.js new file mode 100644 index 0000000000..fe016d4581 --- /dev/null +++ b/assets/index.html.10fdafc2.js @@ -0,0 +1 @@ +import{_ as e,o as c,c as t}from"./app.0e1565ce.js";const n={};function _(o,r){return c(),t("div")}const a=e(n,[["render",_],["__file","index.html.vue"]]);export{a as default}; diff --git a/assets/index.html.11b1f262.js b/assets/index.html.11b1f262.js new file mode 100644 index 0000000000..7ea95be415 --- /dev/null +++ b/assets/index.html.11b1f262.js @@ -0,0 +1,33 @@ +import{_ as m}from"./windows-system-settings.141561e2.js";import{d as f,_ as g,a as b,b as y,c as w,e as v,f as _,g as p}from"./finder.04f876a5.js";import{_ as k,o as x,c as P,a as e,b as t,d as n,w as a,e as h,f as d,r as c}from"./app.0e1565ce.js";const S={},T=e("h1",{id:"getting-started",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#getting-started","aria-hidden":"true"},"#"),t(" Getting Started")],-1),C={class:"custom-container warning"},A=e("p",{class:"custom-container-title"},"Under Construction",-1),F=e("p",null,"This section is all about how to start using Pulsar, such as how to install it and how to use it for basic text editing.",-1),O=e("h2",{id:"why-pulsar",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#why-pulsar","aria-hidden":"true"},"#"),t(" Why Pulsar?")],-1),I=d('

    There are a lot of text editors out there; why should you spend your time learning about and using Pulsar? Editors like Sublime and TextMate offer convenience but only limited extensibility. On the other end of the spectrum, Emacs and Vim offer extreme flexibility, but they aren't very approachable and can only be customized with special-purpose scripting languages.

    We think we can do better. Our goal is a zero-compromise combination of hackability and usability: an editor that will be welcoming to an elementary school student on their first day learning to code, but also a tool they won't outgrow as they develop into seasoned hackers.

    As we've used Pulsar to build Pulsar, what began as an experiment has gradually matured into a tool we can't live without. On the surface, Pulsar is the modern desktop text editor you've come to expect. Pop the hood, however, and you'll discover a system begging to be hacked on.

    The Nucleus of Pulsar

    The web is not without its faults, but two decades of development has forged it into an incredibly malleable and powerful platform. So when we set out to write a text editor that we ourselves would want to extend, web technology was the obvious choice. But first, we had to free it from its chains.

    The Native Web

    Web browsers are great for browsing web pages, but writing code is a specialized activity that warrants dedicated tools. More importantly, the browser severely restricts access to the local system for security reasons, and for us, a text editor that couldn't write files or run local sub-processes was a non-starter.

    For this reason, we didn't build Pulsar as a traditional web application. Instead, Pulsar is a specialized variant of Chromium designed to be a text editor rather than a web browser. Every Pulsar window is essentially a locally-rendered web page.

    All the APIs available to a typical Node.js application are also available to the code running in each window's JavaScript context. This hybrid provides a unique client-side development experience.

    Since everything is local, you don't have to worry about asset pipelines, script concatenation, and asynchronous module definitions. If you want to load some code, just require it at the top of your file. Node's module system makes it easy to break the system down into lots of small, focused packages.

    JavaScript, Meet C++

    Interacting with native code is also really simple. For example, we wrote a wrapper around the Oniguruma regular expression engine for our TextMate grammar support. In a browser, that would have required adventures with NaCl or Esprima. Node integration made it easy.

    In addition to the Node APIs, we also expose APIs for native dialogs, adding application and context menu items, manipulating the window dimensions, etc.

    Web Tech: The Fun Parts

    Another great benefit, that comes with writing code for Pulsar, is the guarantee that it's running on the newest version of Chromium. That means we can ignore issues like browser compatibility and polyfills. We can use all the web's shiny features of tomorrow, today.

    For example, the layout of our workspace and panes is based on flexbox. It's an emerging standard and has gone through a lot of change since we started using it, but none of that mattered as long as it worked.

    With the entire industry pushing web technology forward, we're confident that we're building Pulsar on fertile ground. Native UI technologies come and go, but the web is a standard that becomes more capable and ubiquitous with every passing year. We're excited to dig deeper into its toolbox.

    An Open-Source Text Editor

    We see Pulsar as a great replacement for Atom but we can't do it without your support going forward, since we know that we can't achieve our vision for Pulsar alone. As Emacs and Vim have demonstrated over the past three decades, if you want to build a thriving, long-lasting community around a text editor, it has to be open source.

    ',19),z={href:"https://github.com/pulsar-edit",target:"_blank",rel:"noopener noreferrer"},W=e("h2",{id:"installing-pulsar",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#installing-pulsar","aria-hidden":"true"},"#"),t(" Installing Pulsar")],-1),N=e("p",null,"To get started with Pulsar, we'll need to get it on your system. This section will go over installing Pulsar on your system, as well as the basics of how to build it from source.",-1),Y={href:"https://pulsar-edit.dev/",target:"_blank",rel:"noopener noreferrer"},j=e("p",null,"Simply select your operating system (if not opened automatically) and architecture (where necessary) and choose the type of download you require.",-1),L=e("p",null,"The button or buttons should be specific to your platform, and the download package should be easily installable. However, let's go over them here in a bit of detail.",-1),V=e("h3",{id:"universal-releases",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#universal-releases","aria-hidden":"true"},"#"),t(" Universal releases")],-1),D=e("p",null,[t("Releases are provided in "),e("code",null,".AppImage"),t(" and "),e("code",null,".tar.gz"),t(' "universal" formats that should work on most distributions.')],-1),E=e("h4",{id:"appimage",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#appimage","aria-hidden":"true"},"#"),t(" AppImage")],-1),q=e("p",null,"Simply run the Pulsar AppImage from your file manager or the terminal:",-1),M=e("div",{class:"language-bash ext-sh line-numbers-mode"},[e("pre",{class:"language-bash"},[e("code",null,`$ ./pulsar_1.100.0_amd64.AppImage +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"})])],-1),U={href:"https://github.com/TheAssassin/AppImageLauncher",target:"_blank",rel:"noopener noreferrer"},B=e("h4",{id:"tar-gz",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#tar-gz","aria-hidden":"true"},"#"),t(" tar.gz")],-1),$=e("p",null,[t("Simply extract and run the "),e("code",null,"pulsar"),t(" binary or integrate it into your system manually.")],-1),R=e("h3",{id:"debian-ubuntu-based-distributions",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#debian-ubuntu-based-distributions","aria-hidden":"true"},"#"),t(" Debian/Ubuntu based distributions")],-1),G=e("code",null,".deb",-1),H=e("p",null,"You can install this by opening it in your file manager or via the terminal:",-1),X=e("div",{class:"language-bash ext-sh line-numbers-mode"},[e("pre",{class:"language-bash"},[e("code",null,[t("$ "),e("span",{class:"token function"},"sudo"),t(),e("span",{class:"token function"},"apt"),t(),e("span",{class:"token function"},"install"),t(` ./pulsar_1.100.0_amd64.deb +`)])]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"})])],-1),J=e("h3",{id:"fedora-rhel-based-distributions",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#fedora-rhel-based-distributions","aria-hidden":"true"},"#"),t(" Fedora/RHEL based distributions")],-1),K=e("code",null,".rpm",-1),Q=e("p",null,"You can install this by opening it in your file manager or via the terminal:",-1),Z=e("div",{class:"language-bash ext-sh line-numbers-mode"},[e("pre",{class:"language-bash"},[e("code",null,[e("span",{class:"token comment"},"# On DNF-based distributions"),t(` +$ `),e("span",{class:"token function"},"sudo"),t(" dnf "),e("span",{class:"token function"},"install"),t(),e("span",{class:"token parameter variable"},"-y"),t(` ./pulsar_1.100.0_amd64.rpm + +`),e("span",{class:"token comment"},"# On YUM-based distributions"),t(` +$ `),e("span",{class:"token function"},"sudo"),t(" yum "),e("span",{class:"token function"},"install"),t(),e("span",{class:"token parameter variable"},"-y"),t(` ./pulsar_1.100.0_amd64.rpm +`)])]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),ee=e("code",null,".dmg",-1),te=e("code",null,"Pulsar",-1),ae=e("p",null,[t("If you prefer to install from "),e("code",null,".zip"),t(", this is also provided and requires you to extract the file and drag the "),e("code",null,"Pulsar"),t(' application into your "Applications" folder.')],-1),ne={href:"https://en.wikipedia.org/wiki/Portable_application",target:"_blank",rel:"noopener noreferrer"},oe=e("p",null,"We also have Pulsar available in certain package manager repositories. See the relevant section within the downloads for the installation commands.",-1),se=e("p",null,[e("img",{src:m,alt:"Pulsar on Windows"})],-1),ie=e("p",null,[t("The context menu "),e("code",null,"Open with Pulsar"),t(" in File Explorer, and the option to make Pulsar available for file association using "),e("code",null,"Open with..."),t(", is controlled by the System Settings panel as seen above.")],-1),re=e("p",null,[t("With Pulsar open, click on "),e("code",null,"File > Settings"),t(", and then the "),e("code",null,"System"),t(" tab on the left. Check the boxes next to "),e("code",null,"Show in file context menus"),t(", as well as "),e("code",null,"Show in folder context menus"),t(". And you\u2019re all set.")],-1),le=e("h3",{id:"updating-pulsar",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#updating-pulsar","aria-hidden":"true"},"#"),t(" Updating Pulsar")],-1),de=e("p",null,"You should consider updating Pulsar periodically for the latest improvements to the software. Additionally, When Pulsar receives hotfixes for security vulnerabilities you will want to update your version of Pulsar as soon as possible.",-1),ue=e("p",null,"If you have installed Pulsar via a package manager then you should use the instructions provided by that package manager for updating your installation.",-1),he=e("h3",{id:"portable-mode",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#portable-mode","aria-hidden":"true"},"#"),t(" Portable Mode")],-1),ce=e("p",null,[t("Pulsar stores configuration and state in a "),e("code",null,".pulsar"),t(" directory usually located in your home directory ("),e("code",null,"%userprofile%"),t(" on Windows). You can however run Pulsar in portable mode where both the app and the configuration are stored together such as on a removable storage device.")],-1),pe=e("p",null,"To setup Pulsar in portable mode download the relevant package and extract it to your removable storage.",-1),me=e("p",null,[t("Download the "),e("code",null,".appimage"),t(" or "),e("code",null,".tar.gz"),t(" release then create a "),e("code",null,".pulsar"),t(" directory alongside the directory that contains the Pulsar binary, for example:")],-1),fe=e("div",{class:"language-text ext-text line-numbers-mode"},[e("pre",{class:"language-text"},[e("code",null,`/media/myusb/pulsar-1.100.0/.pulsar +/media/myusb/.pulsar +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),ge=e("p",null,[t("Download the "),e("code",null,".zip"),t(" release then create a "),e("code",null,".pulsar"),t(" directory alongside the Pulsar.app application, for example:")],-1),be=e("div",{class:"language-text ext-text line-numbers-mode"},[e("pre",{class:"language-text"},[e("code",null,`/MyUSB/Pulsar.app +/MyUSB/.pulsar +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),ye=e("p",null,[t("Download the "),e("code",null,"Portable"),t(" release then create a "),e("code",null,".pulsar"),t(" directory alongside the directory that contains pulsar.exe, for example:")],-1),we=e("div",{class:"language-text ext-text line-numbers-mode"},[e("pre",{class:"language-text"},[e("code",null,`e:\\pulsar-1.100.0\\pulsar.exe +e:\\.pulsar +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),ve=d('

    Portable Notes

    • The .pulsar directory must be writeable
    • You can move an existing .pulsar directory to your portable device
    • Pulsar can also store its Electron user data in your .pulsar directory - just create a subdirectory called electronUserData inside .pulsar
    • Alternatively you can set the ATOM_HOME environment variable to point wherever you want (you can write a .sh or .cmd script to temporarily set it and launch it from that)
    • Portable mode installations will not automatically update

    Building Pulsar from Source

    ',3),_e=d(`

    Proxy and Firewall Settings

    Behind a Firewall?

    If you are behind a firewall and seeing SSL errors when installing packages you can disable strict SSL by running:

    $ pulsar -p config set strict-ssl false
    +

    Using a Proxy?

    If you are using a HTTP(S) proxy you can configure ppm to use it by running:

    $ pulsar -p config set https-proxy <YOUR_PROXY_ADDRESS>
    +

    You can run pulsar -p config get https-proxy to verify it has been set correctly.

    Pulsar Basics

    Now that Pulsar is installed on your system, let's fire it up, configure it and get acquainted with the editor.

    When you launch Pulsar for the first time, you should get a screen that looks like this:

    Pulsar's welcome screen

    This is the Pulsar welcome screen and gives you a pretty good starting point for how to get started with the editor.

    Terminology

    ',14),ke=d('

    Command Palette

    In that welcome screen, we are introduced to probably the most important command in Pulsar, the Command Palette. If you press Cmd+Shift+PCtrl+Shift+P while focused in an editor pane, the command palette will pop up.

    Note:

    Throughout the book, we will use shortcut keybindings like LNX: Ctrl+Shift+P - MAC: Cmd+Shift+P - WIN: Ctrl+Shift+P to demonstrate how to run a command and tabbed sections where necessary where instructions for different platforms may differ.

    If you have customized your Pulsar keymap, you can always see the keybinding you have mapped in the Command Palette or the Keybindings tab in the Settings View.

    This search-driven menu can do just about any major task that is possible in Pulsar. Instead of clicking around all the application menus to look for something, you can press LNX/WIN: Ctrl+Shift+P - MAC: Cmd+Shift+P and search for the command.

    Command Palette

    Not only can you see and quickly search through thousands of possible commands, but you can also see if there is a keybinding associated with it. This is great because it means you can guess your way to doing interesting things while also learning the shortcut key strokes for doing it.

    For the rest of the book, we will try to be clear as to the text you can search for in the Command Palette in addition to the keybinding for different commands.

    Settings and Preferences

    Pulsar has a number of settings and preferences you can modify in the Settings View.

    Settings View

    This includes things like changing the theme, specifying how to handle wrapping, font settings, tab size, scroll speed and much more. You can also use this screen to install new packages and themes, which we'll cover in Pulsar Packages.

    To open the Settings View, you can:

    • Use the LNX: Edit > Preferences - MAC: Pulsar > Preferences - WIN: File > Settings menu item in the menu bar
    • Search for settings-view:open in the Command Palette
    • Use the LNX/WIN: Ctrl+, - MAC: Cmd+, keybinding.

    Changing the Theme

    The Settings View also lets you change the themes for Pulsar. Pulsar ships with 4 different UI themes, dark and light variants of the Pulsar and One theme, as well as 8 different syntax themes. You can modify the active theme by clicking on the Themes tab in the sidebar of the Settings View, or you can install new themes by clicking the Install tab.

    Changing the theme from the Settings View

    The UI themes control the style of UI elements like the tabs and the tree view, while the syntax themes control the syntax highlighting of text you load into the editor. To change the syntax or UI theme, simply pick something different in the appropriate dropdown list.

    ',17),xe={href:"https://web.pulsar-edit.dev",target:"_blank",rel:"noopener noreferrer"},Pe=e("h4",{id:"soft-wrap",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#soft-wrap","aria-hidden":"true"},"#"),t(" Soft Wrap")],-1),Se=e("p",null,"You can use the Settings View to specify your whitespace and wrapping preferences.",-1),Te=e("p",null,[e("img",{src:f,alt:"Whitespace and wrapping preferences settings"})],-1),Ce=e("p",null,[t('Enabling "Soft Tabs" will insert spaces instead of actual tab characters when you press the '),e("kbd",null,"Tab"),t(' key and the "Tab Length" setting specifies how many spaces to insert when you do so, or how many spaces are used to represent a tab if "Soft Tabs" is disabled.')],-1),Ae=e("p",null,'The "Soft Wrap" option will wrap lines that are too long to fit in your current window. If soft wrapping is disabled, the lines will simply run off the side of the screen and you will have to scroll the window to see the rest of the content. If "Soft Wrap At Preferred Line Length" is toggled, the lines will wrap at 80 characters instead of the end of the screen. You can also change the default line length to a value other than 80 on this screen.',-1),Fe=d('

    Opening, Modifying, and Saving Files

    Now that your editor is looking and acting how you want, let's start opening up and editing files. This is a text editor after all, right?

    Opening a File

    There are several ways to open a file in Pulsar. You can do it by choosing File > Open from the menu bar or by pressing

    LNX/WIN: Ctrl+O - MAC: Cmd+O

    to choose a file from the standard dialog.

    Open file by dialog

    This is useful for opening a file that is not contained in the project you're currently in (more on that next), or if you're starting from a new window for some reason.

    Another way to open a file in Pulsar is from the command line using the pulsar command.

    ',9),Oe={class:"custom-container note"},Ie=e("p",{class:"custom-container-title"},"Note",-1),ze=e("p",null,[e("strong",null,"Install Shell Commands on macOS")],-1),We=e("p",null,[t('The Pulsar menu has an item named "Install Shell Commands" which installs the '),e("code",null,"pulsar"),t(" and "),e("code",null,"ppm"),t(" commands if Pulsar wasn't able to install them itself on a macOS system.")],-1),Ne={href:"https://github.com/pulsar-edit/pulsar/issues/187",target:"_blank",rel:"noopener noreferrer"},Ye=d(`

    You can run the pulsar command with one or more file paths to open up those files in Pulsar.

    $ pulsar --help
    +> Pulsar Editor v1.100.0
    +
    +> Usage: pulsar [options] [path ...]
    +
    +> One or more paths to files or folders may be specified. If there is an
    +> existing Pulsar window that contains all of the given folders, the paths
    +> will be opened in that window. Otherwise, they will be opened in a new
    +> window.
    +
    +> ...
    +

    This is a great tool if you're used to the terminal or you work from the terminal a lot. Just fire off pulsar [files] and you're ready to start editing. You can even open a file at a certain line (and optionally column) so the cursor will be positioned exactly where you want. For example, you may search some keyword in a repository to find the line you want to edit:

    $ git grep -n 'Opening a File$'
    +getting-started/sections/pulsar-basics.md:130:##### Opening a File
    +

    and then jump to the beginning of that line by appending a colon and the line number to the file path:

    $ pulsar getting-started/sections/pulsar-basics.md:130
    +

    Sometimes you may want the cursor to jump to the exact column position of the searched keyword. Just append another colon plus the column number:

    $ git grep -n --column 'Windows Explorer'
    +getting-started/sections/pulsar-basics.md.md:150:722
    +$ pulsar getting-started/sections/pulsar-basics.md:150:722
    +

    Editing and Saving a File

    `,9),je={href:"https://web.pulsar-edit.dev",target:"_blank",rel:"noopener noreferrer"},Le=e("p",null,[t("To save a file you can choose "),e("em",null,"File > Save"),t(" from the menu bar or "),e("kbd",null,"Ctrl+S"),t(" to save the file. If you choose "),e("em",null,"File > Save As"),t(" or press "),e("kbd",null,"Ctrl+Shift+S"),t(" then you can save the current content in your editor under a different file name. Finally,you can choose "),e("em",null,"File > Save All"),t(" to save all the open files in Pulsar.")],-1),Ve=e("p",null,[t("To save a file you can choose "),e("em",null,"File > Save"),t(" from the menu bar or "),e("kbd",null,"Cmd+S"),t(" to save the file. If you choose "),e("em",null,"File > Save As"),t(" or press "),e("kbd",null,"Cmd+Shift+S"),t(" then you can save the current content in your editor under a different file name. Finally,you can choose "),e("em",null,"File > Save All"),t(" or press "),e("kbd",null,"Alt+Cmd+S"),t(" to save all the open files in Pulsar.")],-1),De=e("p",null,[e("kbd",null,"Ctrl+S"),t(" to save the file. If you choose "),e("em",null,"File > Save As"),t(" or press "),e("kbd",null,"Ctrl+Shift+S"),t(" then you can save the current content in your editor under a different file name. Finally,you can choose "),e("em",null,"File > Save All"),t(" to save all the open files in Pulsar.")],-1),Ee=e("h3",{id:"opening-directories",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#opening-directories","aria-hidden":"true"},"#"),t(" Opening Directories")],-1),qe=e("p",null,[t("Pulsar doesn't just work with single files though; you will most likely spend most of your time working on projects with multiple files. To open a directory, choose the menu item "),e("em",null,"File > Open Folder"),t(" and select a directory from the dialog. You can also add more than one directory to your current Pulsar window, by choosing "),e("em",null,"File > Add Project Folder"),t(" from the menu bar or pressing "),e("kbd",null,"Ctrl+Shift+A"),t(".")],-1),Me=e("p",null,[t("Pulsar doesn't just work with single files though; you will most likely spend most of your time working on projects with multiple files. To open a directory, choose the menu item "),e("em",null,"File > Open"),t(" and select a directory from the dialog. You can also add more than one directory to your current Pulsar window, by choosing "),e("em",null,"File > Add Project Folder"),t(" from the menu bar or pressing "),e("kbd",null,"Cmd+Shift+O"),t(".")],-1),Ue=e("p",null,[t("Pulsar doesn't just work with single files though; you will most likely spend most of your time working on projects with multiple files. To open a directory, choose the menu item "),e("em",null,"File > Open Folder"),t(" and select a directory from the dialog. You can also add more than one directory to your current Pulsar window, by choosing "),e("em",null,"File > Add Project Folder"),t(" from the menu bar or pressing "),e("kbd",null,"Ctrl+Shift+A"),t(".")],-1),Be=d('

    You can open any number of directories from the command line by passing their paths to the pulsar command line tool. For example, you could run the command pulsar ./hopes ./dreams to open both the hopes and the dreams directories at the same time.

    When you open Pulsar with one or more directories, you will automatically get a Tree View on the side of your window.

    Tree View in an open project

    The Tree View allows you to explore and modify the file and directory structure of your project. You can open, rename, delete and create new files from this view.

    ',4),$e=e("p",null,[t("You can also hide and show it with "),e("kbd",null,"Ctrl+\\"),t(" or the "),e("code",null,"tree-view: toggle"),t(" command from the Command Palette, and "),e("kbd",null,"Alt+\\"),t(" will focus it. When the Tree view has focus you can press "),e("kbd",null,"A"),t(", "),e("kbd",null,"M"),t(", or "),e("kbd",null,"Delete"),t(" to add, move or delete files and folders. You can also right-click on a file or folder in the Tree view to see many of the various options, including all of these plus showing the file in your native filesystem or copying the file path to the clipboard.")],-1),Re=e("p",null,[t("You can also hide and show it with "),e("kbd",null,"Cmd+\\"),t(" or the "),e("code",null,"tree-view: toggle"),t(" command from the Command Palette, and "),e("kbd",null,"Ctrl+0"),t(" will focus it. When the Tree view has focus you can press "),e("kbd",null,"A"),t(", "),e("kbd",null,"M"),t(", or "),e("kbd",null,"Delete"),t(" to add, move or delete files and folders. You can also right-click on a file or folder in the Tree view to see many of the various options, including all of these plus showing the file in Finder or copying the file path to the clipboard.")],-1),Ge=e("p",null,[t("You can also hide and show it with "),e("kbd",null,"Ctrl+\\"),t(" or the "),e("code",null,"tree-view: toggle"),t(" command from the Command Palette, and "),e("kbd",null,"Alt+\\"),t(" will focus it. When the Tree view has focus you can press "),e("kbd",null,"A"),t(", "),e("kbd",null,"M"),t(", or "),e("kbd",null,"Delete"),t(" to add, move or delete files and folders. You can also right-click on a file or folder in the Tree view to see many of the various options, including all of these plus showing the file in Windows Explorer or copying the file path to the clipboard.")],-1),He=e("div",{class:"custom-container note"},[e("p",{class:"custom-container-title"},"Note"),e("p",null,[e("strong",null,"Pulsar Packages")]),e("p",null,"Like many parts of Pulsar, the Tree View is not built directly into the editor, but is its own standalone package that is shipped with Pulsar by default. Packages that are bundled with Pulsar are referred to as Core packages. Ones that aren't bundled with Pulsar are referred to as Community packages."),e("p",null,"You can find the source code to the Tree View on GitHub at https://github.com/pulsar-edit/tree-view."),e("p",null,"This is one of the interesting things about Pulsar. Many of its core features are actually just packages implemented the same way you would implement any other functionality. This means that if you don't like the Tree View for example, you could write your own implementation of that functionality and replace it entirely.")],-1),Xe=e("h4",{id:"opening-a-file-in-a-project",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#opening-a-file-in-a-project","aria-hidden":"true"},"#"),t(" Opening a File in a Project")],-1),Je=e("p",null,"Once you have a project open in Pulsar, you can easily find and open any file within that project.",-1),Ke=e("p",null,[t("If you press "),e("kbd",null,"Ctrl+T"),t(" or "),e("kbd",null,"Ctrl+P"),t(", the Fuzzy Finder will pop up. This will let you quickly search for any file in your project by typing parts of the path.")],-1),Qe=e("p",null,[e("img",{src:p,alt:"Opening files with the Fuzzy Finder",title:"Opening files with the Fuzzy Finder"})],-1),Ze=e("p",null,[t("You can also search through only the files currently opened (rather than every file in your project) with "),e("kbd",null,"Ctrl+B"),t('. This searches through your "buffers" or open files. You can also limit this fuzzy search with '),e("kbd",null,"Ctrl+Shift+B"),t(", which searches only through the files which are new or have been modified since your last Git commit.")],-1),et=e("p",null,[t("If you press "),e("kbd",null,"Cmd+T"),t(" or "),e("kbd",null,"Cmd+P"),t(", the Fuzzy Finder will pop up. This will let you quickly search for any file in your project by typing parts of the path.")],-1),tt=e("p",null,[e("img",{src:p,alt:"Opening files with the Fuzzy Finder",title:"Opening files with the Fuzzy Finder"})],-1),at=e("p",null,[t("You can also search through only the files currently opened (rather than every file in your project) with "),e("kbd",null,"Cmd+B"),t('. This searches through your "buffers" or open files. You can also limit this fuzzy search with '),e("kbd",null,"Cmd+Shift+B"),t(", which searches only through the files which are new or have been modified since your last Git commit.")],-1),nt=e("p",null,[t("If you press "),e("kbd",null,"Ctrl+T"),t(" or "),e("kbd",null,"Ctrl+P"),t(", the Fuzzy Finder will pop up. This will let you quickly search for any file in your project by typing parts of the path.")],-1),ot=e("p",null,[e("img",{src:p,alt:"Opening files with the Fuzzy Finder",title:"Opening files with the Fuzzy Finder"})],-1),st=e("p",null,[t("You can also search through only the files currently opened (rather than every file in your project) with "),e("kbd",null,"Ctrl+B"),t('. This searches through your "buffers" or open files. You can also limit this fuzzy search with '),e("kbd",null,"Ctrl+Shift+B"),t(", which searches only through the files which are new or have been modified since your last Git commit.")],-1),it=e("code",null,"core.ignoredNames",-1),rt=e("code",null,"fuzzy-finder.ignoredNames",-1),lt=e("code",null,"core.excludeVCSIgnoredPaths",-1),dt={href:"https://git-scm.com/docs/gitignore",target:"_blank",rel:"noopener noreferrer"},ut=e("code",null,".gitignore",-1),ht=e("code",null,"core.ignoredNames",-1),ct=e("code",null,"fuzzy-finder.ignoredNames",-1),pt={href:"https://github.com/isaacs/minimatch",target:"_blank",rel:"noopener noreferrer"},mt=d('

    Tip

    Configuration Setting Notation

    Sometimes you'll see us refer to configuration settings all spelled out like "Ignored Names in Core Settings". Other times you'll see us use the shorthand name like core.ignoredNames. Both of these refer to the same thing. The shorthand is the package name, then a dot ., followed by the "camel-cased" name of the setting.

    If you have a phrase you want to camel-case, follow these steps:

    1. Lowercase the first word
    2. Capitalize the first letter in all other words
    3. Remove the spaces

    So "Ignored Names" becomes "ignoredNames".

    Summary

    You should now have a basic understanding of what Pulsar is and what you want to do with it. You should also have it installed on your system and be able to use it for the most basic text editing operations.

    Now you're ready to start digging into the fun stuff.

    ',4);function ft(gt,bt){const r=c("RouterLink"),l=c("ExternalLinkIcon"),u=c("Tabs");return x(),P("div",null,[T,e("div",C,[A,e("p",null,[t("This document is under construction, please check back soon for updates. Please see "),n(r,{to:"/community/"},{default:a(()=>[t("our community links")]),_:1}),t(" and feel free to ask for assistance or inquire as to the status of this document.")])]),F,O,h('TODO: We probably want to make this our own, it is very "Atom" still'),I,e("p",null,[t("The entire Pulsar editor is free and open source and available under our "),e("a",z,[t("Organizational"),n(l)]),t(" repositories.")]),W,h("TODO: We need a section here somewhere about how to get pulsar onto the PATH for all systems as it seems to be broken on mac and windows. Also needed for tar.gz and appimage"),N,e("p",null,[t("Installing Pulsar should be fairly simple. Generally, you can go to "),e("a",Y,[t("pulsar-edit.dev"),n(l)]),t(" and you should see a download button.")]),j,L,n(u,{id:"94",data:[{title:"Linux"},{title:"macOS"},{title:"Windows"}],"tab-id":"getting-started"},{tab0:a(({title:o,value:s,isActive:i})=>[h("TODO: When/if we introduce other repository based downloads then we can re-instate much of this from the archived atom section"),V,D,E,q,M,e("p",null,[t("For deeper integration into the system consider using "),e("a",U,[t("AppImageLauncher"),n(l)]),t(".")]),B,$,R,e("p",null,[t("To install Pulsar on Debian, Ubuntu, or related distributions download the "),G,t(" from either the latest release or beta/dev version from "),n(r,{to:"/download.html"},{default:a(()=>[t("Pulsar Downloads")]),_:1}),t(".")]),H,X,J,e("p",null,[t("To install Pulsar on Fedora, RHEL, or related distributions download the "),K,t(" from either the latest release or beta/dev version from "),n(r,{to:"/download.html"},{default:a(()=>[t("Pulsar Downloads")]),_:1}),t(".")]),Q,Z]),tab1:a(({title:o,value:s,isActive:i})=>[e("p",null,[t("Pulsar follows the standard macOS installation process. Grab the correct download "),ee,t(" for your system from "),n(r,{to:"/download.html"},{default:a(()=>[t("Pulsar Downloads")]),_:1}),t(". Once you have the file, you can open it to run the installer and drag the new "),te,t(' application into your "Applications" folder.')]),ae]),tab2:a(({title:o,value:s,isActive:i})=>[e("p",null,[t("Pulsar is available as a 64-bit Windows installer and "),e("a",ne,[t("portable app"),n(l)]),t(" that can be downloaded from "),n(r,{to:"/download.html"},{default:a(()=>[t("Pulsar Downloads")]),_:1}),t(".")]),oe,se,ie,re]),_:1}),le,de,e("p",null,[t("Currently Pulsar does not support automatic updates. What this means is that new versions will have to be obtained via the "),n(r,{to:"/download.html"},{default:a(()=>[t("Pulsar Downloads")]),_:1}),t(" here on our website. This is something on our roadmap to change as soon as possible.")]),ue,h("TODO: Auto upgrade instructions - selectively pull info from atom archive as this becomes possible"),he,ce,pe,n(u,{id:"188",data:[{title:"Linux"},{title:"macOS"},{title:"Windows"}],"tab-id":"getting-started"},{tab0:a(({title:o,value:s,isActive:i})=>[me,fe]),tab1:a(({title:o,value:s,isActive:i})=>[ge,be]),tab2:a(({title:o,value:s,isActive:i})=>[ye,we]),_:1}),ve,e("p",null,[t("The "),n(r,{to:"/docs/launch-manual/sections/core-hacking/#building-pulsar"},{default:a(()=>[t("Hacking the Core")]),_:1}),t(" section of the launch manual covers instructions on how to clone and build the source code if you prefer that option.")]),_e,e("p",null,[t("You can find definitions for all the various terms that we use throughout the manual in our "),n(r,{to:"/docs/launch-manual/sections/getting-started/resources/glossary/"},{default:a(()=>[t("Glossary")]),_:1}),t(".")]),ke,e("p",null,[t("There are also dozens of themes on the "),e("a",xe,[t("Pulsar Package Repository"),n(l)]),t(" that you can choose from if you want something different. We will cover customizing a theme in "),n(r,{to:"/docs/launch-manual/sections/using-pulsar/#basic-customization"},{default:a(()=>[t("Style Tweaks")]),_:1}),t(" and creating your own theme in "),n(r,{to:"/docs/launch-manual/sections/core-hacking/#creating-a-theme"},{default:a(()=>[t("Creating a Theme")]),_:1}),t(".")]),Pe,Se,Te,Ce,Ae,e("p",null,[t("In "),n(r,{to:"/docs/launch-manual/sections/using-pulsar/#basic-customization"},{default:a(()=>[t("Basic Customization")]),_:1}),t(" we will see how to set different wrap preferences for different types of files (for example, if you want to wrap Markdown files but not other files).")]),Fe,e("div",Oe,[Ie,ze,We,e("p",null,[t("On Linux commands are installed automatically as a part of Pulsar's installation process. Windows requires the path to be "),e("a",Ne,[t("exposed manually"),n(l)]),t(" by the user at this time.")])]),Ye,e("p",null,[t("Editing a file is pretty straightforward. You can click around and scroll with your mouse and type to change the content. There is no special editing mode or key commands. If you prefer editors with modes or more complex key commands, you should take a look at the "),e("a",je,[t("Pulsar Package Repository"),n(l)]),t(". There are a lot of packages that emulate popular styles.")]),n(u,{id:"438",data:[{title:"Linux"},{title:"macOS"},{title:"Windows"}],"tab-id":"getting-started"},{tab0:a(({title:o,value:s,isActive:i})=>[Le]),tab1:a(({title:o,value:s,isActive:i})=>[Ve]),tab2:a(({title:o,value:s,isActive:i})=>[De]),_:1}),Ee,n(u,{id:"458",data:[{title:"Linux"},{title:"macOS"},{title:"Windows"}],"tab-id":"getting-started"},{tab0:a(({title:o,value:s,isActive:i})=>[qe]),tab1:a(({title:o,value:s,isActive:i})=>[Me]),tab2:a(({title:o,value:s,isActive:i})=>[Ue]),_:1}),Be,n(u,{id:"487",data:[{title:"Linux"},{title:"macOS"},{title:"Windows"}],"tab-id":"getting-started"},{tab0:a(({title:o,value:s,isActive:i})=>[$e]),tab1:a(({title:o,value:s,isActive:i})=>[Re]),tab2:a(({title:o,value:s,isActive:i})=>[Ge]),_:1}),He,Xe,Je,n(u,{id:"524",data:[{title:"Linux"},{title:"macOS"},{title:"Windows"}],"tab-id":"getting-started"},{tab0:a(({title:o,value:s,isActive:i})=>[Ke,Qe,Ze]),tab1:a(({title:o,value:s,isActive:i})=>[et,tt,at]),tab2:a(({title:o,value:s,isActive:i})=>[nt,ot,st]),_:1}),e("p",null,[t("The fuzzy finder uses the "),it,t(", "),rt,t(" and "),lt,t(" configuration settings to filter out files and folders that will not be shown. If you have a project with tons of files you don't want it to search through, you can add patterns or paths to either of these config settings or your "),e("a",dt,[t("standard "),ut,t(" files"),n(l)]),t(". We'll learn more about config settings in "),n(r,{to:"/docs/launch-manual/sections/using-pulsar/#global-configuration-settings"},{default:a(()=>[t("Global Configuration Settings")]),_:1}),t(", but for now you can easily set these in the Settings View under Core Settings.")]),e("p",null,[t("Both "),ht,t(" and "),ct,t(" are interpreted as glob patterns as implemented by the "),e("a",pt,[t("minimatch Node module"),n(l)]),t(".")]),mt])}const _t=k(S,[["render",ft],["__file","index.html.vue"]]);export{_t as default}; diff --git a/assets/index.html.130de9b2.js b/assets/index.html.130de9b2.js new file mode 100644 index 0000000000..0d45f54dc1 --- /dev/null +++ b/assets/index.html.130de9b2.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-28a80d22","path":"/tag/joke/","title":"joke Tag","lang":"en-US","frontmatter":{"title":"joke Tag","blog":{"type":"category","name":"joke","key":"tag"},"layout":"Blog"},"excerpt":"","headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null}');export{e as data}; diff --git a/assets/index.html.151d8d55.js b/assets/index.html.151d8d55.js new file mode 100644 index 0000000000..fe016d4581 --- /dev/null +++ b/assets/index.html.151d8d55.js @@ -0,0 +1 @@ +import{_ as e,o as c,c as t}from"./app.0e1565ce.js";const n={};function _(o,r){return c(),t("div")}const a=e(n,[["render",_],["__file","index.html.vue"]]);export{a as default}; diff --git a/assets/index.html.16e14736.js b/assets/index.html.16e14736.js new file mode 100644 index 0000000000..44645ffcda --- /dev/null +++ b/assets/index.html.16e14736.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-f5401b6a","path":"/tag/sunset/","title":"sunset Tag","lang":"en-US","frontmatter":{"title":"sunset Tag","blog":{"type":"category","name":"sunset","key":"tag"},"layout":"Blog"},"excerpt":"","headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null}');export{e as data}; diff --git a/assets/index.html.1725108a.js b/assets/index.html.1725108a.js new file mode 100644 index 0000000000..40f18cc77a --- /dev/null +++ b/assets/index.html.1725108a.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-5bc93818","path":"/category/","title":"Category","lang":"en-US","frontmatter":{"title":"Category","blog":{"type":"category","key":"category"},"layout":"Blog"},"excerpt":"","headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null}');export{e as data}; diff --git a/assets/index.html.19e04ff4.js b/assets/index.html.19e04ff4.js new file mode 100644 index 0000000000..8b9462d0b4 --- /dev/null +++ b/assets/index.html.19e04ff4.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-5b38f390","path":"/tag/modernization/","title":"modernization Tag","lang":"en-US","frontmatter":{"title":"modernization Tag","blog":{"type":"category","name":"modernization","key":"tag"},"layout":"Blog"},"excerpt":"","headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null}');export{e as data}; diff --git a/assets/index.html.1b0cb753.js b/assets/index.html.1b0cb753.js new file mode 100644 index 0000000000..fe016d4581 --- /dev/null +++ b/assets/index.html.1b0cb753.js @@ -0,0 +1 @@ +import{_ as e,o as c,c as t}from"./app.0e1565ce.js";const n={};function _(o,r){return c(),t("div")}const a=e(n,[["render",_],["__file","index.html.vue"]]);export{a as default}; diff --git a/assets/index.html.1cce4ebf.js b/assets/index.html.1cce4ebf.js new file mode 100644 index 0000000000..fe016d4581 --- /dev/null +++ b/assets/index.html.1cce4ebf.js @@ -0,0 +1 @@ +import{_ as e,o as c,c as t}from"./app.0e1565ce.js";const n={};function _(o,r){return c(),t("div")}const a=e(n,[["render",_],["__file","index.html.vue"]]);export{a as default}; diff --git a/assets/index.html.22737491.js b/assets/index.html.22737491.js new file mode 100644 index 0000000000..e5842d62e5 --- /dev/null +++ b/assets/index.html.22737491.js @@ -0,0 +1 @@ +const t=JSON.parse('{"key":"v-634d92c5","path":"/docs/atom-archive/getting-started/","title":"Chapter 1 : Getting started","lang":"en-US","frontmatter":{"title":"Chapter 1 : Getting started","sitemap":{"priority":0.1}},"excerpt":"","headers":[{"level":2,"title":"Getting Started","slug":"getting-started","link":"#getting-started","children":[{"level":3,"title":"Why Atom?","slug":"why-atom","link":"#why-atom","children":[]},{"level":3,"title":"Installing Atom","slug":"installing-atom","link":"#installing-atom","children":[]},{"level":3,"title":"Summary","slug":"summary","link":"#summary","children":[]}]}],"git":{"updatedTime":1669438016000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":4},{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1},{"name":"confused-Techie","email":"dev@lhbasics.com","commits":1}]},"readingTime":{"minutes":0.29,"words":86},"filePathRelative":"docs/atom-archive/getting-started/index.md"}');export{t as data}; diff --git a/assets/index.html.234bb87b.js b/assets/index.html.234bb87b.js new file mode 100644 index 0000000000..df66a418b7 --- /dev/null +++ b/assets/index.html.234bb87b.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-007c0ae2","path":"/tag/video/","title":"video Tag","lang":"en-US","frontmatter":{"title":"video Tag","blog":{"type":"category","name":"video","key":"tag"},"layout":"Blog"},"excerpt":"","headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null}');export{e as data}; diff --git a/assets/index.html.2f30f8af.js b/assets/index.html.2f30f8af.js new file mode 100644 index 0000000000..410b116741 --- /dev/null +++ b/assets/index.html.2f30f8af.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-33e16b10","path":"/tag/update/","title":"update Tag","lang":"en-US","frontmatter":{"title":"update Tag","blog":{"type":"category","name":"update","key":"tag"},"layout":"Blog"},"excerpt":"","headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null}');export{e as data}; diff --git a/assets/index.html.30ea87d2.js b/assets/index.html.30ea87d2.js new file mode 100644 index 0000000000..fe016d4581 --- /dev/null +++ b/assets/index.html.30ea87d2.js @@ -0,0 +1 @@ +import{_ as e,o as c,c as t}from"./app.0e1565ce.js";const n={};function _(o,r){return c(),t("div")}const a=e(n,[["render",_],["__file","index.html.vue"]]);export{a as default}; diff --git a/assets/index.html.320bab91.js b/assets/index.html.320bab91.js new file mode 100644 index 0000000000..fe016d4581 --- /dev/null +++ b/assets/index.html.320bab91.js @@ -0,0 +1 @@ +import{_ as e,o as c,c as t}from"./app.0e1565ce.js";const n={};function _(o,r){return c(),t("div")}const a=e(n,[["render",_],["__file","index.html.vue"]]);export{a as default}; diff --git a/assets/index.html.35187d4a.js b/assets/index.html.35187d4a.js new file mode 100644 index 0000000000..23a1de7e61 --- /dev/null +++ b/assets/index.html.35187d4a.js @@ -0,0 +1,2 @@ +import{_ as n,o,c as r,e as s,a as i,b as e,d as a,f as c,r as d}from"./app.0e1565ce.js";const l={},p=c('

    Pulsar Code of Conduct

    Our Pledge

    In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.

    Our Standards

    Examples of behavior that contributes to creating a positive environment include:

    • Using welcoming and inclusive language
    • Being respectful of differing viewpoints and experiences
    • Gracefully accepting constructive criticism
    • Focusing on what is best for the community
    • Showing empathy towards other community members
    • Considering all suggestions regardless of personal opinions

    Examples of unacceptable behavior by participants include:

    • The use of sexualized language or imagery and unwelcome sexual attention or advances
    • Trolling, insulting/derogatory comments, and personal or political attacks
    • Public or private harassment
    • Publishing others' private information, such as a physical or electronic address, without explicit permission
    • Other conduct which could reasonably be considered inappropriate in a professional setting

    Our Responsibilities

    Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.

    Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.

    Scope

    This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.

    Enforcement

    ',14),h={href:"https://discord.gg/7aEbB9dGRT",target:"_blank",rel:"noopener noreferrer"},u=i("p",null,"Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.",-1),f=i("h2",{id:"attribution",tabindex:"-1"},[i("a",{class:"header-anchor",href:"#attribution","aria-hidden":"true"},"#"),e(" Attribution")],-1),m={href:"https://contributor-covenant.org",target:"_blank",rel:"noopener noreferrer"},b={href:"https://contributor-covenant.org/version/1/4/",target:"_blank",rel:"noopener noreferrer"};function g(v,y){const t=d("ExternalLinkIcon");return o(),r("div",null,[s(`This page is generated from the TOOLING.md page on the org-level +documentation at https://github.com/pulsar-edit/.github`),p,i("p",null,[e("Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team using our "),i("a",h,[e("Discord Server"),a(t)]),e(". All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.")]),u,f,i("p",null,[e("This Code of Conduct is adapted from the "),i("a",m,[e("Contributor Covenant"),a(t)]),e(", version 1.4, available at "),i("a",b,[e("https://contributor-covenant.org/version/1/4"),a(t)])])])}const x=n(l,[["render",g],["__file","index.html.vue"]]);export{x as default}; diff --git a/assets/index.html.371049af.js b/assets/index.html.371049af.js new file mode 100644 index 0000000000..0cb78da76d --- /dev/null +++ b/assets/index.html.371049af.js @@ -0,0 +1,2 @@ +import{_ as r,o as s,c as n,e as h,a as e,b as t,d as o,f as i,r as l}from"./app.0e1565ce.js";const d={},c=e("h1",{id:"pulsar-edit-privacy-policy",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#pulsar-edit-privacy-policy","aria-hidden":"true"},"#"),t(" Pulsar Edit Privacy Policy")],-1),u=e("p",null,"The Pulsar team greatly values your, and everybody's, privacy. We therefore keep any collection of personal data to an absolute minimum, but what little is collected across the entirety of the Pulsar Project will be detailed here.",-1),p=e("p",null,"Also please note that this Privacy Policy excludes any Community Packages that may be installed as those are created and maintained by a third party.",-1),m=e("p",null,`At the bottom of each service listed in this document there will be a "TLDR" to quickly sum up all data that is collected, how it's used, and who can use it.`,-1),f=e("p",null,"Date of the Last Change to the Privacy Policy: 2022, December 10th",-1),b=e("h2",{id:"pulsar-backend-https-api-pulsar-edit-dev",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#pulsar-backend-https-api-pulsar-edit-dev","aria-hidden":"true"},"#"),t(" Pulsar Backend ("),e("code",null,"https://api.pulsar-edit.dev"),t(")")],-1),g=e("code",null,"https://web.pulsar-edit.dev",-1),y={href:"https://github.com/pulsar-edit/ppm",target:"_blank",rel:"noopener noreferrer"},w=e("p",null,"All actions are subject to this service's Privacy Policy, including browsing, publishing, installing, deleting, starring, or unstarring packages.",-1),_=e("p",null,"Over the course of using the Pulsar Backend the following personal details are collected about you, and used as described below.",-1),P=e("h3",{id:"what-information-is-collected",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#what-information-is-collected","aria-hidden":"true"},"#"),t(" What Information is Collected")],-1),v=e("ul",null,[e("li",null,"IP Address"),e("li",null,"Browser Used and/or Version"),e("li",null,"Operating System and/or Version")],-1),k=e("h3",{id:"how-is-this-information-collected",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#how-is-this-information-collected","aria-hidden":"true"},"#"),t(" How is this Information Collected")],-1),T={href:"https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent",target:"_blank",rel:"noopener noreferrer"},x=i('

    What is Done with this Information

    • Your IP address is logged from the Pulsar Backend to aid in later troubleshooting that may need to occur.
    • Your Browser Details and Operating System Details are logged by the service used to Host the Pulsar Backend. But no further action is taken with this information once logged.

    Who has Access to this Information

    The only individuals that have access to the above details are Pulsar's Core Admin Team in charge of the Pulsar Backend.

    How Long is this information Kept

    The logs that contain the above information is kept in the cloud for 30 days before it is automatically deleted.

    TLDR

    • The Pulsar Backend logs your IP Address, and your User-Agent Details of your Web Request when accessing it's service.
    • These logs are kept for 30 days until they are deleted automatically.
    • The only people that have access to these logs are Pulsar's Core Admin Team in charge of the Pulsar Backend.
    • The only reason this data is used is to aid in troubleshooting when/if needed.

    Pulsar User Account

    When you create a user account with Pulsar to star, or publish packages, you provide some details to Pulsar to create the account. It should be made explicit that Pulsar does not store any type of credentials your account at any time.

    What Information is Collected

    • GitHub Username
    • GitHub Gravatar URL
    • GitHub node_id (Think of your node_id like the random number assigned to you when making a user account. This is public information)

    How is this Information Collected

    When you sign up with Pulsar, and choose to make an account, this is the data collected to make sure your account works, and is able to protect Packages from Malicious Actors pretending to be someone they're not.

    What is Done with this Information

    This information is used to let you authenticate against the Pulsar Backend when publishing, deleted, or starring a package.

    Who has Access to this Information

    The only people who have access to this information are Pulsar's Core Admin Team in charge of the Backend.

    How Long is this Information Kept

    The above details will be kept until either you request deletion of your account, or there is a built in supported method to delete your Pulsar User Account.

    How do I have this Information Deleted

    ',21),A={href:"https://discord.gg/7aEbB9dGRT",target:"_blank",rel:"noopener noreferrer"},I={href:"https://pulsar-edit.dev/community.html",target:"_blank",rel:"noopener noreferrer"},B=i('

    TLDR

    • Creating a Pulsar User Account provides Pulsar-Edit with your GitHub name, GitHub Gravatar URL, and GitHub node_id.
    • This information is kept until you request deletion or there is a built in way to delete your Pulsar User Account.
    • The only people who can access this information is the Pulsar Core Admin Team.
    • This information is purely there to help you use the Pulsar Backend to publish, delete, star, or otherwise interact with Packages.
    • Pulsar User Accounts contain Zero details of how you log in, or any type of API, OAuth, or other token to access your account.

    Pulsar Website (https://pulsar-edit.dev)

    The Pulsar Website is a service you connect to anytime you visit our website.

    ',4),C={href:"https://pages.github.com/",target:"_blank",rel:"noopener noreferrer"},W=e("h3",{id:"tldr-2",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#tldr-2","aria-hidden":"true"},"#"),t(" TLDR")],-1),D=e("ul",null,[e("li",null,[t("The Pulsar Website Collects "),e("strong",null,"Zero"),t(" Data about you, or where you are.")]),e("li",null,"It does not Log anything, save anything, or extract anything."),e("li",null,"It's just a basic website.")],-1),H=e("h2",{id:"pulsar-editor",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#pulsar-editor","aria-hidden":"true"},"#"),t(" Pulsar Editor")],-1),L={href:"https://github.com/pulsar-edit/pulsar",target:"_blank",rel:"noopener noreferrer"},U={href:"https://atom.io",target:"_blank",rel:"noopener noreferrer"},E={href:"https://github.com/pulsar-edit/pulsar/pull/40",target:"_blank",rel:"noopener noreferrer"},G=e("p",null,[t("That is to say now, the Pulsar Editor collects "),e("strong",null,"Zero"),t(" information about its users.")],-1),R=e("h3",{id:"tldr-3",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#tldr-3","aria-hidden":"true"},"#"),t(" TLDR")],-1),q=e("li",null,[t("The Pulsar Editor Collects "),e("strong",null,"Zero"),t(" Data about you, or where you are.")],-1),V=e("li",null,"It does not log anything, send anything to remote servers, or extract anything.",-1),O={href:"https://github.com/pulsar-edit/pulsar/pull/40",target:"_blank",rel:"noopener noreferrer"};function N(S,Y){const a=l("ExternalLinkIcon");return s(),n("div",null,[h(`This page is generated from the PRIVACY_POLICY.md page on the org-level +documentation at https://github.com/pulsar-edit/.github`),c,u,p,m,f,b,e("p",null,[t("The Pulsar Backend is the service you connect to when using all Pulsar Packages on the web via "),g,t(" and when using the built in Pulsar Settings View or "),e("a",y,[t("Package Manager"),o(a)]),t(" to interact with community packages.")]),w,_,P,v,k,e("p",null,[t("When you send a request of any kind to the Pulsar Backend this information is made available to us through the web request itself and the "),e("a",T,[t("User Agent"),o(a)]),t(" of the request.")]),x,e("p",null,[t("To request removal of the above details feel free to contact the Pulsar Core Admin Team, for quicker response times, contact use through the "),e("a",A,[t("Pulsar Discord"),o(a)]),t(". Or you can contact us through any of the "),e("a",I,[t("supported methods"),o(a)]),t(", or even via email to admin@pulsar-edit.dev")]),B,e("p",null,[t("This Website is hosted on "),e("a",C,[t("GitHub Pages"),o(a)]),t(" which while conveinent for us, also means that we have no built in mechanism to access any personal data of any kind through this service. That is to say we don't; no analytics are set up on the website, and no extra code is run on the website to collect any data.")]),W,D,H,e("p",null,[t("The main Pulsar Application, available "),e("a",L,[t("here"),o(a)]),t(" is the program you use anytime you launch Pulsar, or edit text within it.")]),e("p",null,[t("While "),e("a",U,[t("Atom"),o(a)]),t(" the original implementation of the Editor did collect telemetry and metrics about the users, when we began work on the editor one of the first things that we removed was the "),e("a",E,[t("telemetry"),o(a)]),t(".")]),G,R,e("ul",null,[q,V,e("li",null,[t("You can view when this was removed from our upstream "),e("a",O,[t("here"),o(a)]),t(".")])])])}const j=r(d,[["render",N],["__file","index.html.vue"]]);export{j as default}; diff --git a/assets/index.html.39ac8650.js b/assets/index.html.39ac8650.js new file mode 100644 index 0000000000..fe016d4581 --- /dev/null +++ b/assets/index.html.39ac8650.js @@ -0,0 +1 @@ +import{_ as e,o as c,c as t}from"./app.0e1565ce.js";const n={};function _(o,r){return c(),t("div")}const a=e(n,[["render",_],["__file","index.html.vue"]]);export{a as default}; diff --git a/assets/index.html.39d9060f.js b/assets/index.html.39d9060f.js new file mode 100644 index 0000000000..22a7c03661 --- /dev/null +++ b/assets/index.html.39d9060f.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-49ddb9a0","path":"/docs/resources/pulsar-api/","title":"Pulsar API","lang":"en-US","frontmatter":{"title":"Pulsar API"},"excerpt":"","headers":[],"git":{"updatedTime":1694141555000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":2},{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":0.13,"words":39},"filePathRelative":"docs/resources/pulsar-api/index.md"}');export{e as data}; diff --git a/assets/index.html.3aa61e11.js b/assets/index.html.3aa61e11.js new file mode 100644 index 0000000000..aa7ee6f116 --- /dev/null +++ b/assets/index.html.3aa61e11.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-0da0b37b","path":"/tag/ci/","title":"ci Tag","lang":"en-US","frontmatter":{"title":"ci Tag","blog":{"type":"category","name":"ci","key":"tag"},"layout":"Blog"},"excerpt":"","headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null}');export{e as data}; diff --git a/assets/index.html.3b03ddf1.js b/assets/index.html.3b03ddf1.js new file mode 100644 index 0000000000..5164277d01 --- /dev/null +++ b/assets/index.html.3b03ddf1.js @@ -0,0 +1 @@ +import{_ as i,o as n,c as r,a as e,b as a,d as o,f as s,r as h}from"./app.0e1565ce.js";const l={},c=e("div",{class:"custom-container danger"},[e("p",{class:"custom-container-title"},"STOP"),e("p",null,[a("This is being kept for archival purposes only from the original Atom documentation. As this may no longer be relevant to Pulsar, use this at your own risk. Current Pulsar documentation is found at "),e("a",{href:"/docs/launch-manual/getting-started"},"documentation home"),a(".")])],-1),d={href:"https://flight-manual.atom.io",target:"_blank",rel:"noopener noreferrer"},p=s('

    This includes their original layout and formatting of how things were expected to appear. Anything listed here, may work, and it may not. This is taking into consideration their sunset and our picking up of their torch.

    Chapters

    ',3);function u(m,f){const t=h("ExternalLinkIcon");return n(),r("div",null,[c,e("p",null,[a("This is an archive of the old Atom documentation as it appeared on their "),e("a",d,[a("Flight Manual"),o(t)]),a(". Anything here is just a historical reference of our past, when Atom existed as a GitHub supported project.")]),p])}const _=i(l,[["render",u],["__file","index.html.vue"]]);export{_ as default}; diff --git a/assets/index.html.3d557021.js b/assets/index.html.3d557021.js new file mode 100644 index 0000000000..b9aebccec7 --- /dev/null +++ b/assets/index.html.3d557021.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-75ed4ea4","path":"/encrypted/","title":"Encrypted","lang":"en-US","frontmatter":{"title":"Encrypted","blog":{"type":"type","key":"encrypted"},"layout":"Blog"},"excerpt":"","headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null}');export{e as data}; diff --git a/assets/index.html.3d7eaf24.js b/assets/index.html.3d7eaf24.js new file mode 100644 index 0000000000..a91641946d --- /dev/null +++ b/assets/index.html.3d7eaf24.js @@ -0,0 +1 @@ +import{_ as r,o as c,c as h,a as e,b as o,d as t,w as n,r as s}from"./app.0e1565ce.js";const l={},d=e("h2",{id:"welcome-to-pulsar",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#welcome-to-pulsar","aria-hidden":"true"},"#"),o(" Welcome to Pulsar")],-1),u=e("p",null,"Here you will find everything you need to know about Pulsar, from how to install it all the way to hacking away at its core to make it your own.",-1),_=e("p",null,"The documentation has been broken down into various sections which you can find below.",-1),f={id:"launch-manual",tabindex:"-1"},m=e("a",{class:"header-anchor",href:"#launch-manual","aria-hidden":"true"},"#",-1),p=e("i",{class:"fa-solid fa-rocket"},null,-1),g=e("p",null,"The Launch Manual contains everything you need to know about Pulsar. It covers topics all the way from the initial installation through to developing and publishing your own packages.",-1),k={id:"packages",tabindex:"-1"},w=e("a",{class:"header-anchor",href:"#packages","aria-hidden":"true"},"#",-1),y=e("i",{class:"fa-solid fa-box-open"},null,-1),b=e("p",null,"This section is dedicated to info and documentation around Pulsar's numerous packages which make up both the core editor and community ecosystem.",-1),x=e("h3",{id:"resources",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#resources","aria-hidden":"true"},"#"),o(),e("a",{href:"/docs/resources"},[e("i",{class:"fa-solid fa-wrench"}),o(" Resources")])],-1),v=e("p",null,"There are a number of miscellaneous resources here that do not necessarily fit into other topics. This is where you will find information about things like the tools used by the team and contributors, a glossary to translate any confusing jargon and details of the various APIs that make up Pulsar.",-1),T={id:"atom-archive",tabindex:"-1"},A=e("a",{class:"header-anchor",href:"#atom-archive","aria-hidden":"true"},"#",-1),P=e("i",{class:"fa-solid fa-box-archive"},null,-1),L={href:"https://flight-manual.atom.io/",target:"_blank",rel:"noopener noreferrer"},I=e("p",null,"Anything here is just a historical reference of our past, when Atom existed as a GitHub supported project.",-1),j=e("p",null,"This includes their original layout and formatting of how things were expected to appear. Anything listed here, may work, and it may not. This is taking into consideration their sunset and our picking up of their torch.",-1);function B(E,M){const a=s("RouterLink"),i=s("ExternalLinkIcon");return c(),h("div",null,[d,u,_,e("h3",f,[m,o(),t(a,{to:"/docs/launch-manual/"},{default:n(()=>[p,o(" Launch Manual")]),_:1})]),g,e("h3",k,[w,o(),t(a,{to:"/docs/packages/"},{default:n(()=>[y,o(" Packages")]),_:1})]),b,x,v,e("h3",T,[A,o(),t(a,{to:"/docs/atom-archive/"},{default:n(()=>[P,o(" Atom Archive")]),_:1})]),e("p",null,[o("This is an archive of the old Atom documentation as it appeared on their "),e("a",L,[o("Flight Manual"),t(i)]),o(".")]),I,j])}const R=r(l,[["render",B],["__file","index.html.vue"]]);export{R as default}; diff --git a/assets/index.html.3e6c1463.js b/assets/index.html.3e6c1463.js new file mode 100644 index 0000000000..5a5fbea4ec --- /dev/null +++ b/assets/index.html.3e6c1463.js @@ -0,0 +1 @@ +const e=JSON.parse(`{"key":"v-147825fb","path":"/docs/","title":"Documentation Home","lang":"en-us","frontmatter":{"lang":"en-us","title":"Documentation Home","description":"Home for Pulsar's Documentation"},"excerpt":"","headers":[{"level":2,"title":"Welcome to Pulsar","slug":"welcome-to-pulsar","link":"#welcome-to-pulsar","children":[{"level":3,"title":"Launch Manual","slug":"launch-manual","link":"#launch-manual","children":[]},{"level":3,"title":"Packages","slug":"packages","link":"#packages","children":[]},{"level":3,"title":"Resources","slug":"resources","link":"#resources","children":[]},{"level":3,"title":"Atom Archive","slug":"atom-archive","link":"#atom-archive","children":[]}]}],"git":{"updatedTime":1668306676000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":4},{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1},{"name":"Sertonix","email":"sert0nix@protonmail.com","commits":1}]},"readingTime":{"minutes":0.94,"words":283},"filePathRelative":"docs/index.md"}`);export{e as data}; diff --git a/assets/index.html.421853eb.js b/assets/index.html.421853eb.js new file mode 100644 index 0000000000..fe016d4581 --- /dev/null +++ b/assets/index.html.421853eb.js @@ -0,0 +1 @@ +import{_ as e,o as c,c as t}from"./app.0e1565ce.js";const n={};function _(o,r){return c(),t("div")}const a=e(n,[["render",_],["__file","index.html.vue"]]);export{a as default}; diff --git a/assets/index.html.4393ebb6.js b/assets/index.html.4393ebb6.js new file mode 100644 index 0000000000..e83f188e14 --- /dev/null +++ b/assets/index.html.4393ebb6.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-586be6a4","path":"/category/news/","title":"news Category","lang":"en-US","frontmatter":{"title":"news Category","blog":{"type":"category","name":"news","key":"category"},"layout":"Blog"},"excerpt":"","headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null}');export{e as data}; diff --git a/assets/index.html.450e1e45.js b/assets/index.html.450e1e45.js new file mode 100644 index 0000000000..884a968dab --- /dev/null +++ b/assets/index.html.450e1e45.js @@ -0,0 +1 @@ +const t=JSON.parse('{"key":"v-9ffc7398","path":"/tag/community/","title":"community Tag","lang":"en-US","frontmatter":{"title":"community Tag","blog":{"type":"category","name":"community","key":"tag"},"layout":"Blog"},"excerpt":"","headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null}');export{t as data}; diff --git a/assets/index.html.46dbbb19.js b/assets/index.html.46dbbb19.js new file mode 100644 index 0000000000..fe016d4581 --- /dev/null +++ b/assets/index.html.46dbbb19.js @@ -0,0 +1 @@ +import{_ as e,o as c,c as t}from"./app.0e1565ce.js";const n={};function _(o,r){return c(),t("div")}const a=e(n,[["render",_],["__file","index.html.vue"]]);export{a as default}; diff --git a/assets/index.html.47212b5b.js b/assets/index.html.47212b5b.js new file mode 100644 index 0000000000..763f8b0492 --- /dev/null +++ b/assets/index.html.47212b5b.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-3973978e","path":"/docs/atom-archive/behind-atom/","title":"Chapter 4 : Behind Atom","lang":"en-US","frontmatter":{"title":"Chapter 4 : Behind Atom","sitemap":{"priority":0.1}},"excerpt":"","headers":[{"level":2,"title":"Behind Atom","slug":"behind-atom","link":"#behind-atom","children":[{"level":3,"title":"Configuration API","slug":"configuration-api","link":"#configuration-api","children":[]},{"level":3,"title":"Keymaps In-Depth","slug":"keymaps-in-depth","link":"#keymaps-in-depth","children":[]},{"level":3,"title":"Scoped Settings, Scopes and Scope Descriptors","slug":"scoped-settings-scopes-and-scope-descriptors","link":"#scoped-settings-scopes-and-scope-descriptors","children":[]},{"level":3,"title":"Serialization in Atom","slug":"serialization-in-atom","link":"#serialization-in-atom","children":[]},{"level":3,"title":"Developing Node Modules","slug":"developing-node-modules","link":"#developing-node-modules","children":[]},{"level":3,"title":"Interacting With Other Packages Via Services","slug":"interacting-with-other-packages-via-services","link":"#interacting-with-other-packages-via-services","children":[]},{"level":3,"title":"Maintaining Your Packages","slug":"maintaining-your-packages","link":"#maintaining-your-packages","children":[]},{"level":3,"title":"How Atom Uses Chromium Snapshots","slug":"how-atom-uses-chromium-snapshots","link":"#how-atom-uses-chromium-snapshots","children":[]},{"level":3,"title":"Summary","slug":"summary","link":"#summary","children":[]}]}],"git":{"updatedTime":1669438016000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":4},{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1},{"name":"confused-Techie","email":"dev@lhbasics.com","commits":1}]},"readingTime":{"minutes":0.59,"words":176},"filePathRelative":"docs/atom-archive/behind-atom/index.md"}');export{e as data}; diff --git a/assets/index.html.4798fcfd.js b/assets/index.html.4798fcfd.js new file mode 100644 index 0000000000..b520da584b --- /dev/null +++ b/assets/index.html.4798fcfd.js @@ -0,0 +1,343 @@ +import{_ as p}from"./spec-deps.b3f6a1b6.js";import{_ as i}from"./dep-cop.6353fb49.js";import{_ as l,o as r,c as d,a as e,b as n,d as s,w as u,f as t,r as o}from"./app.0e1565ce.js";const m={},h=t(`

    STOP

    This is being kept for archival purposes only from the original Atom documentation. As this may no longer be relevant to Pulsar, use this at your own risk. Current Pulsar documentation is found at documentation home.

    Upgrading to 1.0 APIs

    Note

    Note: Atom has been at v1.x for over a year, so this appendix is mostly obsolete at this point. We're retaining it for historic and reference purposes.

    Atom is at 1.0! Much of the effort leading up to the 1.0 has been cleaning up APIs in an attempt to future proof, and make a more pleasant experience developing packages. If you have developed packages or syntaxes for Atom before the 1.0 API, you can find some tips on upgrading your work in this appendix.

    Upgrading Your Package

    This document will guide you through the large bits of upgrading your package to work with 1.0 APIs.

    TL;DR

    We've set deprecation messages and errors in strategic places to help make sure you don't miss anything. You should be able to get 95% of the way to an updated package just by fixing errors and deprecations. There are a couple of things you can do to get the full effect of all the errors and deprecations.

    Use atom-space-pen-views

    If you use any class from require 'atom' with a $ or View in the name, add the atom-space-pen-views module to your package's package.json file's dependencies:

    {
    +	"dependencies": {
    +		"atom-space-pen-views": "^2.0.3"
    +	}
    +}
    +

    Then run apm install in your package directory.

    Require views from atom-space-pen-views

    Anywhere you are requiring one of the following from atom you need to require them from atom-space-pen-views instead.

    # require these from 'atom-space-pen-views' rather than 'atom'
    +$
    +$$
    +$$$
    +View
    +TextEditorView
    +ScrollView
    +SelectListView
    +

    So this:

    # Old way
    +{$, TextEditorView, View, GitRepository} = require 'atom'
    +

    Would be replaced by this:

    # New way
    +{GitRepository} = require 'atom'
    +{$, TextEditorView, View} = require 'atom-space-pen-views'
    +
    Run specs and test your package

    You wrote specs, right!? Here's where they shine. Run them with cmd-shift-P, and search for run package specs. It will show all the deprecation messages and errors.

    Update the engines field

    When you are deprecation free and all done converting, upgrade the engines field in your package.json:

    {
    +	"engines": {
    +		"atom": ">=0.174.0 <2.0.0"
    +	}
    +}
    +
    Examples
    `,25),k={href:"https://github.com/atom/atom/issues/4011",target:"_blank",rel:"noopener noreferrer"},v=t('

    Deprecations

    All of the methods in Atom core that have changes will emit deprecation messages when called. These messages are shown in two places: your package specs, and in Deprecation Cop.

    Specs

    Just run your specs, and all the deprecations will be displayed in yellow.

    Deprecations in Specs

    Note

    Note: Deprecations are only displayed when executing specs through the "Window: Run Package Specs" command in the Atom UI. Deprecations are not displayed when running specs at the terminal.

    Deprecation Cop

    Run Atom in Dev Mode, atom --dev, with your package loaded, and open Deprecation Cop (search for "deprecation" in the command palette). Deprecated methods will appear in Deprecation Cop only after they have been called.

    Deprecation Cop

    When Deprecation Cop is open, and deprecated methods are called, a Refresh button will appear in the top right of the Deprecation Cop interface. So exercise your package, then come back to Deprecation Cop and click the Refresh button.

    Upgrading your Views

    Previous to 1.0, views were baked into Atom core. These views were based on jQuery and space-pen. They looked something like this:

    # The old way: getting views from atom
    +{$, TextEditorView, View} = require 'atom'
    +
    +module.exports =
    +class SomeView extends View
    +  @content: ->
    +    @div class: 'find-and-replace', =>
    +      @div class: 'block', =>
    +        @subview 'myEditor', new TextEditorView(mini: true)
    +  #...
    +
    The New

    require 'atom' no longer provides view helpers or jQuery. Atom Core is now 'view agnostic'. The preexisting view system is available from a new Node module: atom-space-pen-views.

    atom-space-pen-views now provides jQuery, space-pen views, and Atom specific views:

    # These are now provided by atom-space-pen-views
    +$
    +$$
    +$$$
    +View
    +TextEditorView
    +ScrollView
    +SelectListView
    +
    Adding the module dependencies

    To use the new views, you need to specify the atom-space-pen-views module in your package's package.json file's dependencies:

    {
    +	"dependencies": {
    +		"atom-space-pen-views": "^2.0.3"
    +	}
    +}
    +

    space-pen bundles jQuery. If you do not need space-pen or any of the views, you can require jQuery directly.

    {
    +	"dependencies": {
    +		"jquery": "^2"
    +	}
    +}
    +
    Converting your views

    Sometimes it is as simple as converting the requires at the top of each view page. I assume you read the 'TL;DR' section and have updated all of your requires.

    Upgrading classes extending any space-pen View
    afterAttach and beforeRemove updated

    The afterAttach and beforeRemove hooks have been replaced with attached and detached and the semantics have changed.

    afterAttach was called whenever the node was attached to another DOM node, even if that parent node wasn't present in the DOM. afterAttach also was called with a boolean indicating whether or not the element and its parents were on the DOM. Now the attached hook is only called when the node and all of its parents are actually on the DOM, and is not called with a boolean.

    beforeRemove was only called when $.fn.remove was called, which was typically used when the node was completely removed from the DOM. The new detached hook is called whenever the DOM node is detached, which could happen if the node is being detached for reattachment later. In short, if beforeRemove is called the node is never coming back. With detached it might be attached again later.

    # Old way
    +{View} = require 'atom'
    +class MyView extends View
    +  afterAttach: (onDom) ->
    +    #...
    +
    +  beforeRemove: ->
    +    #...
    +
    # New way
    +{View} = require 'atom-space-pen-views'
    +class MyView extends View
    +  attached: ->
    +    # Always called with the equivalent of @afterAttach(true)!
    +    #...
    +
    +  detached: ->
    +    #...
    +
    subscribe and subscribeToCommand methods removed

    The subscribe and subscribeToCommand methods have been removed. See the Eventing and Disposables section for more info.

    Upgrading to the new TextEditorView
    `,34),b=e("code",null,"TextEditorView",-1),g=e("code",null,"TextEditor",-1),f=e("code",null,"TextEditorView::getModel",-1),w={href:"https://github.com/atom/atom-space-pen-views#texteditorview",target:"_blank",rel:"noopener noreferrer"},y=e("code",null,"TextEditorView",-1),x={href:"https://atom.io/docs/api/latest/TextEditor",target:"_blank",rel:"noopener noreferrer"},_=e("code",null,"TextEditor",-1),V=t(`
    Upgrading classes extending ScrollView

    The ScrollView has very minor changes.

    You can no longer use @off to remove default behavior for core:move-up, core:move-down, etc.

    # Old way to turn off default behavior
    +class ResultsView extends ScrollView
    +  initialize: (@model) ->
    +    super()
    +    # turn off default scrolling behavior from ScrollView
    +    @off 'core:move-up'
    +    @off 'core:move-down'
    +    @off 'core:move-left'
    +    @off 'core:move-right'
    +
    # New way to turn off default behavior
    +class ResultsView extends ScrollView
    +  initialize: (@model) ->
    +    disposable = super()
    +    # turn off default scrolling behavior from ScrollView
    +    disposable.dispose()
    +
    `,5),D={href:"https://github.com/atom/find-and-replace/pull/311/files#diff-9",target:"_blank",rel:"noopener noreferrer"},T={href:"https://github.com/atom/atom-space-pen-views#scrollview",target:"_blank",rel:"noopener noreferrer"},O=t(`
    Upgrading classes extending SelectListView

    Your SelectListView might look something like this:

    # Old!
    +class CommandPaletteView extends SelectListView
    +  initialize: ->
    +    super()
    +    @addClass('command-palette overlay from-top')
    +    atom.workspaceView.command 'command-palette:toggle', => @toggle()
    +
    +  confirmed: ({name, jQuery}) ->
    +    @cancel()
    +    # do something with the result
    +
    +  toggle: ->
    +    if @hasParent()
    +      @cancel()
    +    else
    +      @attach()
    +
    +  attach: ->
    +    @storeFocusedElement()
    +
    +    items = [] # TODO: build items
    +    @setItems(items)
    +
    +    atom.workspaceView.append(this)
    +    @focusFilterEditor()
    +
    +  confirmed: ({name, jQuery}) ->
    +    @cancel()
    +

    This attaches and detaches itself from the DOM when toggled, canceling magically detaches it from the DOM, and it uses the classes overlay and from-top.

    The new SelectListView no longer automatically detaches itself from the DOM when cancelled. It's up to you to implement whatever cancel behavior you want. Using the new APIs to mimic the semantics of the old class, it should look like this:

    # New!
    +class CommandPaletteView extends SelectListView
    +  initialize: ->
    +    super()
    +    # no more need for the \`overlay\` and \`from-top\` classes
    +    @addClass('command-palette')
    +    atom.commands.add 'atom-workspace', 'command-palette:toggle', => @toggle()
    +
    +  # You need to implement the \`cancelled\` method and hide.
    +  cancelled: ->
    +    @hide()
    +
    +  confirmed: ({name, jQuery}) ->
    +    @cancel()
    +    # do something with the result
    +
    +  toggle: ->
    +    # Toggling now checks panel visibility,
    +    # and hides / shows rather than attaching to / detaching from the DOM.
    +    if @panel?.isVisible()
    +      @cancel()
    +    else
    +      @show()
    +
    +  show: ->
    +    # Now you will add your select list as a modal panel to the workspace
    +    @panel ?= atom.workspace.addModalPanel(item: this)
    +    @panel.show()
    +
    +    @storeFocusedElement()
    +
    +    items = [] # TODO: build items
    +    @setItems(items)
    +
    +    @focusFilterEditor()
    +
    +  hide: ->
    +    @panel?.hide()
    +
    `,6),E={href:"https://github.com/atom/command-palette/pull/19/files",target:"_blank",rel:"noopener noreferrer"},M=e("code",null,"CommandPaletteView",-1),C={href:"https://github.com/atom/atom-space-pen-views#selectlistview",target:"_blank",rel:"noopener noreferrer"},S=e("code",null,"SelectListView",-1),j=t(`

    Using the model layer rather than the view layer

    The API no longer exposes any specialized view objects or view classes. atom.workspaceView, and all the view classes: WorkspaceView, EditorView, PaneView, etc. have been globally deprecated.

    Nearly all of the atom-specific actions performed by the old view objects can now be managed via the model layer. For example, here's adding a panel to the interface using the atom.workspace model instead of the workspaceView:

    # Old!
    +div = document.createElement('div')
    +atom.workspaceView.appendToTop(div)
    +
    # New!
    +div = document.createElement('div')
    +atom.workspace.addTopPanel(item: div)
    +

    For actions that still require the view, such as dispatching commands or munging css classes, you'll access the view via the atom.views.getView() method. This will return a subclass of HTMLElement rather than a jQuery object or an instance of a deprecated view class (e.g. WorkspaceView).

    # Old!
    +workspaceView = atom.workspaceView
    +editorView = workspaceView.getActiveEditorView()
    +paneView = editorView.getPaneView()
    +
    # New!
    +# Generally, just use the models
    +workspace = atom.workspace
    +editor = workspace.getActiveTextEditor()
    +pane = editor.getPane()
    +
    +# If you need views, get them with \`getView\`
    +workspaceElement = atom.views.getView(atom.workspace)
    +editorElement = atom.views.getView(editor)
    +paneElement = atom.views.getView(pane)
    +

    Updating Specs

    atom.workspaceView, the WorkspaceView class and the EditorView class have been deprecated. These two objects are used heavily throughout specs, mostly to dispatch events and commands. This section will explain how to remove them while still retaining the ability to dispatch events and commands.

    Removing WorkspaceView references

    WorkspaceView has been deprecated. Everything you could do on the view, you can now do on the Workspace model.

    Requiring WorkspaceView from atom and accessing any methods on it will throw a deprecation warning. Many specs lean heavily on WorkspaceView to trigger commands and fetch EditorView objects.

    Your specs might contain something like this:

    # Old!
    +{WorkspaceView} = require 'atom'
    +describe 'FindView', ->
    +  beforeEach ->
    +    atom.workspaceView = new WorkspaceView()
    +

    Instead, we will use the atom.views.getView() method. This will return a plain HTMLElement, not a WorkspaceView or jQuery object.

    # New!
    +describe 'FindView', ->
    +  workspaceElement = null
    +  beforeEach ->
    +    workspaceElement = atom.views.getView(atom.workspace)
    +
    Attaching the workspace to the DOM

    The workspace needs to be attached to the DOM in some cases. For example, view hooks only work (attached() on View, attachedCallback() on custom elements) when there is a descendant attached to the DOM.

    You might see this in your specs:

    # Old!
    +atom.workspaceView.attachToDom()
    +

    Change it to:

    # New!
    +jasmine.attachToDOM(workspaceElement)
    +
    Removing EditorView references

    Like WorkspaceView, EditorView has been deprecated. Everything you needed to do on the view you are now able to do on the TextEditor model.

    In many cases, you will not even need to get the editor's view anymore. Any of those instances should be updated to use the TextEditor instance instead. You should really only need the editor's view when you plan on triggering a command on the view in a spec.

    Your specs might contain something like this:

    # Old!
    +describe 'Something', ->
    +  [editorView] = []
    +  beforeEach ->
    +    editorView = atom.workspaceView.getActiveView()
    +

    We're going to use atom.views.getView() again to get the editor element. As in the case of the workspaceElement, getView will return a subclass of HTMLElement rather than an EditorView or jQuery object.

    # New!
    +describe 'Something', ->
    +  [editor, editorElement] = []
    +  beforeEach ->
    +    editor = atom.workspace.getActiveTextEditor()
    +    editorElement = atom.views.getView(editor)
    +
    Dispatching commands

    Since the editorElement objects are no longer jQuery objects, they no longer support trigger(). Additionally, Atom has a new command dispatcher, atom.commands, that we use rather than commandeering jQuery's trigger method.

    From this:

    # Old!
    +workspaceView.trigger 'a-package:toggle'
    +editorView.trigger 'find-and-replace:show'
    +

    To this:

    # New!
    +atom.commands.dispatch workspaceElement, 'a-package:toggle'
    +atom.commands.dispatch editorElement, 'find-and-replace:show'
    +

    Eventing and Disposables

    A couple large things changed with respect to events:

    `,38),A={href:"https://atom.io/docs/api/latest/Disposable",target:"_blank",rel:"noopener noreferrer"},q=e("code",null,"Disposable",-1),I=e("li",null,[n("The "),e("code",null,"subscribe()"),n(" method is no longer available on "),e("code",null,"space-pen"),n(),e("code",null,"View"),n(" objects")],-1),N=e("li",null,[n("An Emitter is now provided from "),e("code",null,"require 'atom'")],-1),P=e("h5",{id:"consuming-events",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#consuming-events","aria-hidden":"true"},"#"),n(" Consuming Events")],-1),W={href:"https://atom.io/docs/api/latest/Disposable",target:"_blank",rel:"noopener noreferrer"},R=e("code",null,"Disposable",-1),L=e("code",null,"dispose()",-1),U=t(`
    # Old!
    +editor.on 'changed', ->
    +
    # New!
    +disposable = editor.onDidChange ->
    +
    +# You can unsubscribe at some point in the future via \`dispose()\`
    +disposable.dispose()
    +

    Deprecation warnings will guide you toward the correct methods.

    Using a CompositeDisposable

    You can group multiple disposables into a single disposable with a CompositeDisposable.

    {CompositeDisposable} = require 'atom'
    +
    +class Something
    +  constructor: ->
    +    editor = atom.workspace.getActiveTextEditor()
    +    @disposables = new CompositeDisposable
    +    @disposables.add editor.onDidChange ->
    +    @disposables.add editor.onDidChangePath ->
    +
    +  destroy: ->
    +    @disposables.dispose()
    +
    Removing View::subscribe and Subscriber::subscribe calls

    There were a couple permutations of subscribe(). In these examples, a CompositeDisposable is used as it will commonly be useful where conversion is necessary.

    subscribe(unsubscribable)

    This one is very straight forward.

    # Old!
    +@subscribe editor.on 'changed', ->
    +
    # New!
    +disposables = new CompositeDisposable
    +disposables.add editor.onDidChange ->
    +
    subscribe(modelObject, event, method)

    When the modelObject is an Atom model object, the change is very simple. Just use the correct event method, and add it to your CompositeDisposable.

    # Old!
    +@subscribe editor, 'changed', ->
    +
    # New!
    +disposables = new CompositeDisposable
    +disposables.add editor.onDidChange ->
    +
    subscribe(jQueryObject, selector(optional), event, method)

    Things are a little more complicated when subscribing to a DOM or jQuery element. Atom no longer provides helpers for subscribing to elements. You can use jQuery or the native DOM APIs, whichever you prefer.

    # Old!
    +@subscribe $(window), 'focus', ->
    +
    # New!
    +{Disposable, CompositeDisposable} = require 'atom'
    +disposables = new CompositeDisposable
    +
    +# New with jQuery
    +focusCallback = ->
    +$(window).on 'focus', focusCallback
    +disposables.add new Disposable ->
    +  $(window).off 'focus', focusCallback
    +
    +# New with native APIs
    +focusCallback = ->
    +window.addEventListener 'focus', focusCallback
    +disposables.add new Disposable ->
    +  window.removeEventListener 'focus', focusCallback
    +
    Providing Events: Using the Emitter
    `,21),$=e("code",null,"emissary",-1),Y=e("code",null,"Emitter",-1),F=e("code",null,"require 'atom'",-1),Q=e("code",null,"Emitter",-1),H={href:"https://atom.io/docs/api/latest/Emitter",target:"_blank",rel:"noopener noreferrer"},z=e("code",null,"Emitter",-1),G=t(`
    # New!
    +{Emitter} = require 'atom'
    +
    +class Something
    +  constructor: ->
    +    @emitter = new Emitter
    +
    +  destroy: ->
    +    @emitter.dispose()
    +
    +  onDidChange: (callback) ->
    +    @emitter.on 'did-change', callback
    +
    +  methodThatFiresAChange: ->
    +    @emitter.emit 'did-change', {data: 2}
    +
    +# Using the evented class
    +something = new Something
    +something.onDidChange (eventObject) ->
    +  console.log eventObject.data # => 2
    +something.methodThatFiresAChange()
    +

    Subscribing To Commands

    `,2),B=e("code",null,"$.fn.command",-1),J=e("code",null,"View::subscribeToCommand",-1),K=e("code",null,"atom.commands.add",-1),X=e("code",null,"CompositeDisposable",-1),Z={href:"https://atom.io/docs/api/latest/CommandRegistry#instance-add",target:"_blank",rel:"noopener noreferrer"},ee=t(`
    # Old!
    +atom.workspaceView.command 'core:close core:cancel', ->
    +
    +# When inside a View class, you might see this
    +@subscribeToCommand 'core:close core:cancel', ->
    +
    # New!
    +@disposables.add atom.commands.add 'atom-workspace',
    +  'core:close': ->
    +  'core:cancel': ->
    +
    +# You can register commands directly on individual DOM elements in addition to
    +# using selectors. When in a View class, you should have a \`@element\` object
    +# available. \`@element\` is a plain HTMLElement object
    +@disposables.add atom.commands.add @element,
    +  'core:close': ->
    +  'core:cancel': ->
    +

    Upgrading your stylesheet's selectors

    `,3),ne={href:"https://blog.atom.io/2014/11/18/avoiding-style-pollution-with-the-shadow-dom.html",target:"_blank",rel:"noopener noreferrer"},se=t('

    Note

    Note: The Shadow DOM was removed in Atom 1.13. The ::shadow and /deep/ selectors and the context-targeted style sheets described below won't work and should not be used anymore.

    Upgrading Your UI Theme Or Package Selectors

    In addition to changes in Atom's scripting API, we'll also be making some breaking changes to Atom's DOM structure, requiring style sheets and keymaps in both packages and themes to be updated.

    Deprecation Cop

    Deprecation Cop will list usages of deprecated selector patterns to guide you. You can access it via the Command Palette (cmd-shift-p, then search for Deprecation). It breaks the deprecations down by package:

    Deprecation Cop

    Custom Tags

    Rather than adding classes to standard HTML elements to indicate their role, Atom now uses custom element names. For example, <div class="workspace"> has now been replaced with <atom-workspace>. Selectors should be updated accordingly. Note that tag names have lower specificity than classes in CSS, so you'll need to take care in converting things.

    Old SelectorNew Selector
    .editoratom-text-editor
    .editor.miniatom-text-editor[mini]
    .workspaceatom-workspace
    .horizontalatom-workspace-axis.horizontal
    .verticalatom-workspace-axis.vertical
    .pane-containeratom-pane-container
    .paneatom-pane
    .tool-panelatom-panel
    .panel-topatom-panel.top
    .panel-bottomatom-panel.bottom
    .panel-leftatom-panel.left
    .panel-rightatom-panel.right
    .overlayatom-panel.modal

    Supporting the Shadow DOM

    ',10),ae={href:"https://www.html5rocks.com/en/tutorials/webcomponents/shadowdom",target:"_blank",rel:"noopener noreferrer"},te=e("ul",null,[e("li",null,"Highlight decorations"),e("li",null,"Gutter decorations"),e("li",null,"Line decorations"),e("li",null,"Scrollbar styling"),e("li",null,[n("Anything targeting a child selector of "),e("code",null,".editor")])],-1),oe=e("p",null,"During a transition phase, it will be possible to enable or disable the text editor's shadow DOM in the settings, so themes will need to be compatible with both approaches.",-1),ie=e("h5",{id:"shadow-dom-selectors",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#shadow-dom-selectors","aria-hidden":"true"},"#"),n(" Shadow DOM Selectors")],-1),ce=e("code",null,"::shadow",-1),pe=e("code",null,"/deep/",-1),le={href:"https://www.html5rocks.com/en/tutorials/webcomponents/shadowdom-201#toc-style-cat-hat",target:"_blank",rel:"noopener noreferrer"},re=t(`
    ::shadow

    The ::shadow pseudo-element allows you to bypass a single shadow root. For example, say you want to update a highlight decoration for a linter package. Initially, the style looks as follows:

    // Without shadow DOM support
    +atom-text-editor .highlight.my-linter {
    +	background: hotpink;
    +}
    +

    In order for this style to apply with the shadow DOM enabled, you will need to add a second selector with the ::shadow pseudo-element. You should leave the original selector in place so your theme continues to work with the shadow DOM disabled during the transition period.

    // With shadow DOM support
    +atom-text-editor .highlight.my-linter,
    +atom-text-editor::shadow .highlight.my-linter {
    +	background: hotpink;
    +}
    +
    `,5),de={href:"https://github.com/atom/find-and-replace/blob/95351f261bc384960a69b66bf12eae8002da63f9/stylesheets/find-and-replace.less#L9-L29",target:"_blank",rel:"noopener noreferrer"},ue=e("code",null,"::shadow",-1),me=t(`
    /deep/

    The /deep/ combinator overrides all shadow boundaries, making it useful for rules you want to apply globally such as scrollbar styling. Here's a snippet containing scrollbar styling for the Atom Dark UI theme before shadow DOM support:

    // Without shadow DOM support
    +.scrollbars-visible-always {
    +	::-webkit-scrollbar {
    +		width: 8px;
    +		height: 8px;
    +	}
    +
    +	::-webkit-scrollbar-track,
    +	::-webkit-scrollbar-corner {
    +		background: @scrollbar-background-color;
    +	}
    +
    +	::-webkit-scrollbar-thumb {
    +		background: @scrollbar-color;
    +		border-radius: 5px;
    +		box-shadow: 0 0 1px black inset;
    +	}
    +}
    +

    To style scrollbars even inside of the shadow DOM, each rule needs to be prefixed with /deep/. We use /deep/ instead of ::shadow because we don't care about the selector of the host element in this case. We just want our styling to apply everywhere.

    // With shadow DOM support using /deep/
    +.scrollbars-visible-always {
    +	/deep/ ::-webkit-scrollbar {
    +		width: 8px;
    +		height: 8px;
    +	}
    +
    +	/deep/ ::-webkit-scrollbar-track,
    +	/deep/ ::-webkit-scrollbar-corner {
    +		background: @scrollbar-background-color;
    +	}
    +
    +	/deep/ ::-webkit-scrollbar-thumb {
    +		background: @scrollbar-color;
    +		border-radius: 5px;
    +		box-shadow: 0 0 1px black inset;
    +	}
    +}
    +
    Context-Targeted Style Sheets

    The selector features discussed above allow you to target shadow DOM content with specific selectors, but Atom also allows you to target a specific shadow DOM context with an entire style sheet. The context into which a style sheet is loaded is based on the file name. If you want to load a style sheet into the editor, name it with the .atom-text-editor.less or .atom-text-editor.css extensions.

    my-ui-theme/
    +  styles/
    +    index.less                   # loaded globally
    +    index.atom-text-editor.less  # loaded in the text editor shadow DOM
    +
    `,8),he={href:"https://github.com/atom/decoration-example/blob/master/styles/decoration-example.atom-text-editor.less",target:"_blank",rel:"noopener noreferrer"},ke=t('

    Inside a context-targeted style sheet, there's no need to use the ::shadow or /deep/ expressions. If you want to refer to the element containing the shadow root, you can use the ::host pseudo-element.

    During the transition phase, style sheets targeting the atom-text-editor context will also be loaded globally. Make sure you update your selectors in a way that maintains compatibility with the shadow DOM being disabled. That means if you use a ::host pseudo element, you should also include the same style rule matches against atom-text-editor.

    Note

    Note: The Shadow DOM was removed in Atom 1.13. The :host selector described below won't work and should not be used anymore.

    Upgrading Your Syntax Theme

    ',4),ve={href:"https://www.html5rocks.com/en/tutorials/webcomponents/shadowdom",target:"_blank",rel:"noopener noreferrer"},be=e("p",null,[n("Syntax themes are specifically intended to style only text editor content, so they are automatically loaded directly into the text editor's shadow DOM when it is enabled. This happens automatically when the theme's "),e("code",null,"package.json"),n(" contains a "),e("code",null,'theme: "syntax"'),n(" declaration, so you don't need to change anything to target the appropriate context.")],-1),ge=e("em",null,"outside",-1),fe=e("code",null,".editor",-1),we=e("code",null,".editor-colors",-1),ye=e("code",null,":host",-1),xe=e("code",null,"atom-text-editor",-1),_e={href:"https://www.html5rocks.com/en/tutorials/webcomponents/shadowdom-201#toc-style-host",target:"_blank",rel:"noopener noreferrer"},Ve=e("code",null,":host",-1),De=t(`

    Here's an example from Atom's light syntax theme. Note that the atom-text-editor selector intended to target the editor from the outside has been retained to allow the theme to keep working during the transition phase when it is possible to disable the shadow DOM.

    atom-text-editor,
    +:host {
    +	/* :host added */
    +	background-color: @syntax-background-color;
    +	color: @syntax-text-color;
    +
    +	.invisible-character {
    +		color: @syntax-invisible-character-color;
    +	}
    +	/* more nested selectors... */
    +}
    +
    `,2);function Te(Oe,Ee){const a=o("ExternalLinkIcon"),c=o("RouterLink");return r(),d("div",null,[h,e("p",null,[n("We have upgraded all the core packages. Please see "),e("a",k,[n("this issue"),s(a)]),n(" for a link to all the upgrade PRs.")]),v,e("p",null,[n("All of the atom-specific methods available on the "),b,n(" have been moved to the "),g,n(", available via "),f,n(". See the "),e("a",w,[y,n(" docs"),s(a)]),n(" and "),e("a",x,[_,n(" docs"),s(a)]),n(" for more info.")]),V,e("ul",null,[e("li",null,[n("Check out "),e("a",D,[n("an example"),s(a)]),n(" from find-and-replace.")]),e("li",null,[n("See the "),e("a",T,[n("docs"),s(a)]),n(" for all the options.")])]),O,e("ul",null,[e("li",null,[n("And check out the "),e("a",E,[n("conversion of "),M,s(a)]),n(" as a real-world example.")]),e("li",null,[n("See the "),e("a",C,[S,n(" docs"),s(a)]),n(" for all options.")])]),j,e("ol",null,[e("li",null,[n("All model events are now exposed as event subscription methods that return "),e("a",A,[q,s(a)]),n(" objects")]),I,N]),P,e("p",null,[n("All events from the Atom API are now methods that return a "),e("a",W,[R,s(a)]),n(" object, on which you can call "),L,n(" to unsubscribe.")]),U,e("p",null,[n("You no longer need to require "),$,n(" to get an emitter. We now provide an "),Y,n(" class from "),F,n(". We have a specific pattern for use of the "),Q,n(". Rather than mixing it in, we instantiate a member variable, and create explicit subscription methods. For more information see the "),e("a",H,[z,n(" docs"),s(a)]),n(".")]),G,e("p",null,[B,n(" and "),J,n(" are no longer available. Now we use "),K,n(", and collect the results in a "),X,n(". See "),e("a",Z,[n("the docs"),s(a)]),n(" for more info.")]),ee,e("p",null,[n("Many selectors have changed, and we have introduced the "),e("a",ne,[n("Shadow DOM"),s(a)]),n(" to the editor. See the "),s(c,{to:"/upgrading-to-1-0-apis/sections/upgrading-your-ui-theme-or-package-selectors/"},{default:u(()=>[n("Upgrading Your UI Theme And Package Selectors guide")]),_:1}),n(" for more information in upgrading your package stylesheets.")]),se,e("p",null,[n("Text editor content is now rendered in the shadow DOM, which shields it from being styled by global style sheets to protect against accidental style pollution. For more background on the shadow DOM, check out the "),e("a",ae,[n("Shadow DOM 101"),s(a)]),n(" on HTML 5 Rocks. If you need to style text editor content in a UI theme, you'll need to circumvent this protection for any rules that target the text editor's content. Some examples of the kinds of UI theme styles needing to be updated:")]),te,oe,ie,e("p",null,[n("Chromium provides two tools for bypassing shadow boundaries, the "),ce,n(" pseudo-element and the "),pe,n(" combinator. For an in-depth explanation of styling the shadow DOM, see the "),e("a",le,[n("Shadow DOM 201"),s(a)]),n(" article on HTML 5 Rocks.")]),re,e("p",null,[n("Check out the "),e("a",de,[n("find-and-replace"),s(a)]),n(" package for another example of using "),ue,n(" to pierce the shadow DOM.")]),me,e("p",null,[n("Check out this "),e("a",he,[n("style sheet"),s(a)]),n(" from the decoration-example package for an example of context-targeting.")]),ke,e("p",null,[n("Text editor content is now rendered in the shadow DOM, which shields it from being styled by global style sheets to protect against accidental style pollution. For more background on the shadow DOM, check out the "),e("a",ve,[n("Shadow DOM 101"),s(a)]),n(" on HTML 5 Rocks.")]),be,e("p",null,[n("When theme style sheets are loaded into the text editor's shadow DOM, selectors intended to target the editor from the "),ge,n(" no longer make sense. Styles targeting the "),fe,n(" and "),we,n(" classes instead need to target the "),ye,n(" pseudo-element, which matches against the containing "),xe,n(" node. Check out the "),e("a",_e,[n("Shadow DOM 201"),s(a)]),n(" article for more information about the "),Ve,n(" pseudo-element.")]),De])}const je=l(m,[["render",Te],["__file","index.html.vue"]]);export{je as default}; diff --git a/assets/index.html.47c4aeae.js b/assets/index.html.47c4aeae.js new file mode 100644 index 0000000000..09fdbded70 --- /dev/null +++ b/assets/index.html.47c4aeae.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-6c7e3cf8","path":"/docs/atom-archive/hacking-atom/","title":"Chapter 3 : Hacking Atom","lang":"en-US","frontmatter":{"title":"Chapter 3 : Hacking Atom","sitemap":{"priority":0.1}},"excerpt":"","headers":[{"level":2,"title":"Hacking Atom","slug":"hacking-atom","link":"#hacking-atom","children":[{"level":3,"title":"Tools of the Trade","slug":"tools-of-the-trade","link":"#tools-of-the-trade","children":[]},{"level":3,"title":"The Init File","slug":"the-init-file","link":"#the-init-file","children":[]},{"level":3,"title":"Package: Word Count","slug":"package-word-count","link":"#package-word-count","children":[]},{"level":3,"title":"Package: Modifying Text","slug":"package-modifying-text","link":"#package-modifying-text","children":[]},{"level":3,"title":"Package: Active Editor Info","slug":"package-active-editor-info","link":"#package-active-editor-info","children":[]},{"level":3,"title":"Creating a Theme","slug":"creating-a-theme","link":"#creating-a-theme","children":[]},{"level":3,"title":"Creating a Grammar","slug":"creating-a-grammar","link":"#creating-a-grammar","children":[]},{"level":3,"title":"Creating a Legacy TextMate Grammar","slug":"creating-a-legacy-textmate-grammar","link":"#creating-a-legacy-textmate-grammar","children":[]},{"level":3,"title":"Publishing","slug":"publishing","link":"#publishing","children":[]},{"level":3,"title":"Iconography","slug":"iconography","link":"#iconography","children":[]},{"level":3,"title":"Debugging","slug":"debugging","link":"#debugging","children":[]},{"level":3,"title":"Writing Specs","slug":"writing-specs","link":"#writing-specs","children":[]},{"level":3,"title":"Handling URIs","slug":"handling-uris","link":"#handling-uris","children":[]},{"level":3,"title":"Core URIs","slug":"core-uris","link":"#core-uris","children":[]},{"level":3,"title":"Cross-Platform Compatibility","slug":"cross-platform-compatibility","link":"#cross-platform-compatibility","children":[]},{"level":3,"title":"Converting from TextMate","slug":"converting-from-textmate","link":"#converting-from-textmate","children":[]},{"level":3,"title":"Hacking on Atom Core","slug":"hacking-on-atom-core","link":"#hacking-on-atom-core","children":[]},{"level":3,"title":"Contributing to Official Atom Packages","slug":"contributing-to-official-atom-packages","link":"#contributing-to-official-atom-packages","children":[]}]},{"level":2,"title":"Creating a Fork of a Core Package in atom/atom","slug":"creating-a-fork-of-a-core-package-in-atom-atom","link":"#creating-a-fork-of-a-core-package-in-atom-atom","children":[{"level":3,"title":"Creating Your New Package","slug":"creating-your-new-package","link":"#creating-your-new-package","children":[]},{"level":3,"title":"Merging Upstream Changes into Your Package","slug":"merging-upstream-changes-into-your-package","link":"#merging-upstream-changes-into-your-package","children":[]},{"level":3,"title":"Maintaining a Fork of a Core Package in atom/atom","slug":"maintaining-a-fork-of-a-core-package-in-atom-atom","link":"#maintaining-a-fork-of-a-core-package-in-atom-atom","children":[]},{"level":3,"title":"Summary","slug":"summary-3","link":"#summary-3","children":[]}]}],"git":{"updatedTime":1669438016000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":4},{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1},{"name":"confused-Techie","email":"dev@lhbasics.com","commits":1}]},"readingTime":{"minutes":1.26,"words":378},"filePathRelative":"docs/atom-archive/hacking-atom/index.md"}');export{e as data}; diff --git a/assets/index.html.4a993420.js b/assets/index.html.4a993420.js new file mode 100644 index 0000000000..f6438ecdfa --- /dev/null +++ b/assets/index.html.4a993420.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-d804e652","path":"/slide/","title":"Slides","lang":"en-US","frontmatter":{"title":"Slides","blog":{"type":"type","key":"slide"},"layout":"Blog"},"excerpt":"","headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null}');export{e as data}; diff --git a/assets/index.html.4ae4defc.js b/assets/index.html.4ae4defc.js new file mode 100644 index 0000000000..68e6946f45 --- /dev/null +++ b/assets/index.html.4ae4defc.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-7d8d32d8","path":"/tag/rolling/","title":"rolling Tag","lang":"en-US","frontmatter":{"title":"rolling Tag","blog":{"type":"category","name":"rolling","key":"tag"},"layout":"Blog"},"excerpt":"","headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null}');export{e as data}; diff --git a/assets/index.html.4b5dd6bc.js b/assets/index.html.4b5dd6bc.js new file mode 100644 index 0000000000..c4e9e7272f --- /dev/null +++ b/assets/index.html.4b5dd6bc.js @@ -0,0 +1 @@ +import{_ as t,o as s,c as r,a as e,b as o,d as n,r as i}from"./app.0e1565ce.js";const d={},l=e("h2",{id:"notices",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#notices","aria-hidden":"true"},"#"),o(" Notices")],-1),c={class:"custom-container info"},h=e("p",{class:"custom-container-title"},"Welcome",-1),u=e("p",null,"Welcome to all new visitors!",-1),p=e("h3",{id:"downloads-and-releases",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#downloads-and-releases","aria-hidden":"true"},"#"),o(" Downloads and Releases")],-1),_={href:"https://pulsar-edit.dev/download.html",target:"_blank",rel:"noopener noreferrer"},f=e("h3",{id:"app-updates",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#app-updates","aria-hidden":"true"},"#"),o(" App Updates")],-1),g=e("p",null,"Currently Pulsar does not support automatic app updates. There are, however, automated notifications of new versions, which include links and instructions so you can easily get the new versions when they come out. (These notifications can be disabled if you wish.) Beyond that, new versions can be obtained via the Download links here on our website, or from our CI (continuous integration) pages as explained on our Download page.",-1),m=e("h3",{id:"blog",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#blog","aria-hidden":"true"},"#"),o(" Blog")],-1),b=e("em",null,"goings-on",-1),w={href:"https://pulsar-edit.dev/blog/",target:"_blank",rel:"noopener noreferrer"},k=e("h3",{id:"packages",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#packages","aria-hidden":"true"},"#"),o(" Packages")],-1),y=e("p",null,"One of our first and biggest tasks was to replace the closed source Atom.io package repository with our own so that users would still be able to download from the huge package ecosystem.",-1),x={href:"https://web.pulsar-edit.dev/",target:"_blank",rel:"noopener noreferrer"},v=e("h3",{id:"support-and-community",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#support-and-community","aria-hidden":"true"},"#"),o(" Support and Community")],-1),B={href:"https://pulsar-edit.dev/community.html",target:"_blank",rel:"noopener noreferrer"},C={href:"https://github.com/pulsar-edit/pulsar/issues/new?assignees=&labels=bug%2Ctriage&template=bug-report.yml",target:"_blank",rel:"noopener noreferrer"};function I(P,N){const a=i("ExternalLinkIcon");return s(),r("div",null,[l,e("div",c,[h,u,p,e("p",null,[o("We are constantly working on the editor and maintain both rolling and regular releases. You can find out more about the differences and download Pulsar on our "),e("a",_,[o("downloads page"),n(a)]),o(" and following the instructions.")]),f,g,m,e("p",null,[o("For updates on the "),b,o(" around here, please do check out our "),e("a",w,[o("blog"),n(a)]),o("!")]),k,y,e("p",null,[o("Searching and downloading from the "),e("a",x,[o("package repository"),n(a)]),o(" is now fully supported, as is publishing/updating/deleting packages. If you experience any issues, please feel free to report this to the Pulsar Team.")]),v,e("p",null,[o("If you have any problems when using Pulsar, then please do let us know in one of our "),e("a",B,[o("community areas"),n(a)]),o(" or as a "),e("a",C,[o("GitHub issue"),n(a)]),o(".")])])])}const D=t(d,[["render",I],["__file","index.html.vue"]]);export{D as default}; diff --git a/assets/index.html.4bb12063.js b/assets/index.html.4bb12063.js new file mode 100644 index 0000000000..a44645f38e --- /dev/null +++ b/assets/index.html.4bb12063.js @@ -0,0 +1 @@ +import{_ as s,o as r,c as l,a as e,b as t,d as n,w as h,r as i}from"./app.0e1565ce.js";const c={},d=e("h1",{id:"github",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#github","aria-hidden":"true"},"#"),t(" GitHub")],-1),u=e("p",null,[t("Welcome to the "),e("code",null,"github"),t(" wiki!")],-1),_={href:"https://github.com/atom/github/wiki",target:"_blank",rel:"noopener noreferrer"},p=e("div",{class:"custom-container warning"},[e("p",{class:"custom-container-title"},"Note"),e("p",null,"Please note that it's possible this is outdated, as its original version was published by @'Michelle Tilley' on Jun 7, 2017.")],-1),m=e("h2",{id:"roadmaps",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#roadmaps","aria-hidden":"true"},"#"),t(" Roadmaps")],-1),g={href:"https://github.com/atom/github/projects/8",target:"_blank",rel:"noopener noreferrer"},b=e("h2",{id:"monthly-planning",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#monthly-planning","aria-hidden":"true"},"#"),t(" Monthly Planning")],-1);function f(k,x){const o=i("ExternalLinkIcon"),a=i("RouterLink");return r(),l("div",null,[d,u,e("p",null,[t("This wiki contains a single file from the original "),e("a",_,[t("upstream Atom Wiki"),n(o)]),t(". Which while serves no purpose for the current mission or direction of Pulsar, just seemed like an interesting insight into how Atom originally began, and how progress originally happened.")]),p,m,e("p",null,[e("a",g,[t("Short Term Roadmap"),n(o)])]),b,e("ul",null,[e("li",null,[n(a,{to:"/docs/packages/core/github/june-2017.html"},{default:h(()=>[t("June 2017")]),_:1})])])])}const y=s(c,[["render",f],["__file","index.html.vue"]]);export{y as default}; diff --git a/assets/index.html.50f23593.js b/assets/index.html.50f23593.js new file mode 100644 index 0000000000..a7f804eb4f --- /dev/null +++ b/assets/index.html.50f23593.js @@ -0,0 +1,790 @@ +import{_ as h,a as m,b as g,c as k,d as v,e as b}from"./spec-suite.8f5e854d.js";import{_ as u}from"./dev-tools.1b7b2813.js";import{_ as f,a as y,b as w}from"./theme-side-by-side.33cf8d5d.js";import{_}from"./iconography.bf3cea92.js";import{_ as x,a as q,b as A,c as T,d as C,e as S,f as I,g as j,h as P,i as R}from"./cpu-profile-done.c24435bc.js";import{_ as N,o as E,c as U,a as e,b as n,d as a,w as o,e as M,f as t,r as d}from"./app.0e1565ce.js";const W={},F=t('

    STOP

    This is being kept for archival purposes only from the original Atom documentation. As this may no longer be relevant to Pulsar, you use this at your own risk. Current Pulsar documentation for this section is found at the documentation home.

    Hacking Atom

    ',2),D={href:"https://github.com/atom/tree-view",target:"_blank",rel:"noopener noreferrer"},L={href:"https://github.com/atom/command-palette",target:"_blank",rel:"noopener noreferrer"},O={href:"https://github.com/atom/find-and-replace",target:"_blank",rel:"noopener noreferrer"},z=t(`

    In this chapter, we're going to learn how to extend the functionality of Atom through writing packages. This will be everything from new user interfaces to new language grammars to new themes. We'll learn this by writing a series of increasingly complex packages together, introducing you to new APIs and tools and techniques as we need them.

    If you're looking for an example using a specific API or feature, you can skip to the end of the chapter where we've indexed all the examples that way.

    Tools of the Trade

    To begin, there are a few things we'll assume you know, at least to some degree. Since all of Atom is implemented using web technologies, we have to assume you know web technologies such as JavaScript and CSS. Specifically, we'll be using Less, which is a preprocessor for CSS.

    While much of Atom has been converted to JavaScript, a lot of older code has been left implemented in CoffeeScript because changing it would have been too risky. Additionally, Atom's default configuration language is CSON, which is based on CoffeeScript. If you don't know CoffeeScript, but you are familiar with JavaScript, you shouldn't have too much trouble. Here is an example of some simple CoffeeScript code:

    MyPackageView = require './my-package-view'
    +
    +module.exports =
    +  myPackageView: null
    +
    +  activate: (state) ->
    +    @myPackageView = new MyPackageView(state.myPackageViewState)
    +
    +  deactivate: ->
    +    @myPackageView.destroy()
    +
    +  serialize: ->
    +    myPackageViewState: @myPackageView.serialize()
    +
    `,6),Y={href:"http://coffeescript.org",target:"_blank",rel:"noopener noreferrer"},V={href:"http://lesscss.org/",target:"_blank",rel:"noopener noreferrer"},$=e("h3",{id:"the-init-file",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#the-init-file","aria-hidden":"true"},"#"),n(" The Init File")],-1),G=e("code",null,"init.coffee",-1),H=e("span",{class:"platform-mac platform-linux"},[e("code",null,"~/.atom")],-1),B=e("span",{class:"platform-windows"},[e("code",null,"%USERPROFILE%\\.atom")],-1),J={href:"https://atom.io/docs/api/latest",target:"_blank",rel:"noopener noreferrer"},K=e("a",{href:"./package-word-count"},"Package: Word Count",-1),X=t(`

    You can open the init.coffee file in an editor from the Atom > Init ScriptFile > Init ScriptEdit > Init Script menu. This file can also be named init.js and contain JavaScript code.

    For example, if you have the Audio Beep configuration setting enabled, you could add the following code to your init.coffee file to have Atom greet you with an audio beep every time it loads:

    atom.beep()
    +
    `,3),Z=e("code",null,"init.coffee",-1),Q={href:"https://atom.io/docs/api/latest/Selection",target:"_blank",rel:"noopener noreferrer"},ee={href:"https://atom.io/docs/api/latest/Clipboard",target:"_blank",rel:"noopener noreferrer"},ne=t(`
    atom.commands.add 'atom-text-editor', 'markdown:paste-as-link', ->
    +  return unless editor = atom.workspace.getActiveTextEditor()
    +
    +  selection = editor.getLastSelection()
    +  clipboardText = atom.clipboard.read()
    +
    +  selection.insertText("[#{selection.getText()}](#{clipboardText})")
    +
    `,1),ae=e("h3",{id:"package-word-count",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#package-word-count","aria-hidden":"true"},"#"),n(" Package: Word Count")],-1),se=e("p",null,"Let's get started by writing a very simple package and looking at some of the tools needed to develop one effectively. We'll start by writing a package that tells you how many words are in the current buffer and display it in a small modal window.",-1),te=e("h4",{id:"package-generator",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#package-generator","aria-hidden":"true"},"#"),n(" Package Generator")],-1),oe={href:"https://github.com/atom/package-generator",target:"_blank",rel:"noopener noreferrer"},ie=e("p",null,[n('You can run the generator by invoking the command palette and searching for "Generate Package". A dialog will appear asking you to name your new project. Name it '),e("code",null,"your-name-word-count"),n(". Atom will then create that directory and fill it out with a skeleton project and link it into your "),e("span",{class:"platform-mac platform-linux"},[e("code",null,"~/.atom/packages")]),e("span",{class:"platform-windows"},[e("code",null,"%USERPROFILE%\\.atom\\packages")]),n(" directory so it's loaded when you launch your editor next time.")],-1),le={class:"custom-container note"},ce=e("p",{class:"custom-container-title"},"Note",-1),re=e("strong",null,"Note:",-1),pe={href:"https://atom.io/packages",target:"_blank",rel:"noopener noreferrer"},de=e("code",null,"your-name-word-count",-1),ue=e("em",null,"should",-1),he=t('

    Basic generated Atom package

    You can see that Atom has created about a dozen files that make up the package. Let's take a look at each of them to get an idea of how a package is structured, then we can modify them to get our word count functionality.

    The basic package layout is as follows:

    my-package/
    +\u251C\u2500 grammars/
    +\u251C\u2500 keymaps/
    +\u251C\u2500 lib/
    +\u251C\u2500 menus/
    +\u251C\u2500 spec/
    +\u251C\u2500 snippets/
    +\u251C\u2500 styles/
    +\u251C\u2500 index.js
    +\u2514\u2500 package.json
    +

    Not every package will have (or need) all of these directories and the package generator doesn't create snippets or grammars. Let's see what some of these are so we can start messing with them.

    package.json
    `,6),me={href:"https://en.wikipedia.org/wiki/Npm_(software)",target:"_blank",rel:"noopener noreferrer"},ge=e("code",null,"package.json",-1),ke={href:"https://docs.npmjs.com/files/package.json",target:"_blank",rel:"noopener noreferrer"},ve=e("code",null,"package.json",-1),be=e("code",null,"package.json",-1),fe=t(`
    • main: the path to the JavaScript file that's the entry point to your package. If this is missing, Atom will default to looking for an index.coffee or index.js.
    • styles: an Array of Strings identifying the order of the style sheets your package needs to load. If not specified, style sheets in the styles directory are added alphabetically.
    • keymaps: an Array of Strings identifying the order of the key mappings your package needs to load. If not specified, mappings in the keymaps directory are added alphabetically.
    • menus: an Array of Strings identifying the order of the menu mappings your package needs to load. If not specified, mappings in the menus directory are added alphabetically.
    • snippets: an Array of Strings identifying the order of the snippets your package needs to load. If not specified, snippets in the snippets directory are added alphabetically.
    • activationCommands: an Object identifying commands that trigger your package's activation. The keys are CSS selectors, the values are Arrays of Strings identifying the command. The loading of your package is delayed until one of these events is triggered within the associated scope defined by the CSS selector. If not specified, the activate() method of your main export will be called when your package is loaded.
    • activationHooks: an Array of Strings identifying hooks that trigger your package's activation. The loading of your package is delayed until one of these hooks are triggered. Currently, there are three activation hooks:
      • core:loaded-shell-environment for when Atom has finished loading the shell environment variables
      • scope.name:root-scope-used for when a file is opened from the specified language (e.g. source.ruby:root-scope-used)
      • language-package-name:grammar-used for when a specific language package is used (e.g., my-special-language-javascript:grammar-used)
    • workspaceOpeners: An Array of Strings identifying URIs that trigger your package's activation. For example, say your package registers a custom opener for atom://my-custom-panel. By including that string in workspaceOpeners, your package will defer its activation until that URI is opened.

    The package.json in the package we've just generated looks like this currently:

    {
    +	"name": "your-name-word-count",
    +	"main": "./lib/your-name-word-count",
    +	"version": "0.0.0",
    +	"description": "A short description of your package",
    +	"activationCommands": {
    +		"atom-workspace": "your-name-word-count:toggle"
    +	},
    +	"repository": "https://github.com/atom/your-name-word-count",
    +	"license": "MIT",
    +	"engines": {
    +		"atom": ">=1.0.0 <2.0.0"
    +	},
    +	"dependencies": {}
    +}
    +

    If you wanted to use activationHooks, you might have:

    {
    +	"name": "your-name-word-count",
    +	"main": "./lib/your-name-word-count",
    +	"version": "0.0.0",
    +	"description": "A short description of your package",
    +	"activationHooks": [
    +		"language-javascript:grammar-used",
    +		"language-coffee-script:grammar-used"
    +	],
    +	"repository": "https://github.com/atom/your-name-word-count",
    +	"license": "MIT",
    +	"engines": {
    +		"atom": ">=1.0.0 <2.0.0"
    +	},
    +	"dependencies": {}
    +}
    +

    One of the first things you should do is ensure that this information is filled out. The name, description, repository URL the project will be at, and the license can all be filled out immediately. The other information we'll get into more detail on as we go.

    WARNING

    Warning: Do not forget to update the repository URL. The one generated for you is invalid by design and will prevent you from publishing your package until updated.

    Source Code

    If you want to extend Atom's behavior, your package should contain a single top-level module, which you export from whichever file is indicated by the main key in your package.json file. In the package we just generated, the main package file is lib/your-name-word-count.js. The remainder of your code should be placed in the lib directory, and required from your top-level file. If the main key is not in your package.json file, it will look for index.js or index.coffee as the main entry point.

    Your package's top-level module is a singleton object that manages the lifecycle of your extensions to Atom. Even if your package creates ten different views and appends them to different parts of the DOM, it's all managed from your top-level object.

    Your package's top-level module can implement the following basic methods:

    `,11),ye=e("li",null,[e("code",null,"activate(state)"),n(": This "),e("strong",null,"optional"),n(" method is called when your package is activated. It is passed the state data from the last time the window was serialized if your module implements the "),e("code",null,"serialize()"),n(" method. Use this to do initialization work when your package is started (like setting up DOM elements or binding events). If this method returns a promise the package will be considered loading until the promise resolves (or rejects).")],-1),we=e("code",null,"initialize(state)",-1),_e=e("strong",null,"optional",-1),xe=e("code",null,"activate()",-1),qe=e("code",null,"initialize()",-1),Ae=e("code",null,"activate()",-1),Te=e("code",null,"initialize()",-1),Ce=e("li",null,[e("code",null,"serialize()"),n(": This "),e("strong",null,"optional"),n(" method is called when the window is shutting down, allowing you to return JSON to represent the state of your component. When the window is later restored, the data you returned is passed to your module's "),e("code",null,"activate"),n(" method so you can restore your view to where the user left off.")],-1),Se=e("li",null,[e("code",null,"deactivate()"),n(": This "),e("strong",null,"optional"),n(" method is called when the window is shutting down and when the package is disabled. If your package is watching any files or holding external resources in any other way, release them here. You should also dispose of all subscriptions you're holding on to.")],-1),Ie=e("h5",{id:"style-sheets",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#style-sheets","aria-hidden":"true"},"#"),n(" Style Sheets")],-1),je=e("code",null,"styles",-1),Pe={href:"http://lesscss.org",target:"_blank",rel:"noopener noreferrer"},Re=e("p",null,[n("Ideally, you won't need much in the way of styling. Atom provides a standard set of components which define both the colors and UI elements for any package that fits into Atom seamlessly. You can view all of Atom's UI components by opening the styleguide: open the command palette "),e("kbd",{class:"platform-mac"},"Cmd+Shift+P"),e("kbd",{class:"platform-windows platform-linux"},"Ctrl+Shift+P"),n(" and search for "),e("code",null,"styleguide"),n(", or type "),e("kbd",{class:"platform-mac"},"Cmd+Ctrl+Shift+G"),e("kbd",{class:"platform-windows platform-linux"},"Ctrl+Shift+G"),n(".")],-1),Ne=e("em",null,"do",-1),Ee=e("em",null,"must",-1),Ue={href:"https://github.com/atom/atom-dark-ui/blob/master/styles/ui-variables.less",target:"_blank",rel:"noopener noreferrer"},Me=t(`

    An optional styleSheets array in your package.json can list the style sheets by name to specify a loading order; otherwise, style sheets are loaded alphabetically.

    Keymaps

    You can provide key bindings for commonly used actions for your extension, especially if you're also adding a new command. In our new package, we have a keymap filled in for us already in the keymaps/your-name-word-count.json file:

    {
    +  "atom-workspace": {
    +    "ctrl-alt-o": "your-name-word-count:toggle"
    +  }
    +}
    +

    This means that if you press Alt+Ctrl+O, our package will run the your-name-word-count:toggle command. We'll look at that code next, but if you want to change the default key mapping, you can do that in this file.

    Keymaps are placed in the keymaps subdirectory. By default, all keymaps are loaded in alphabetical order. An optional keymaps array in your package.json can specify which keymaps to load and in what order.

    Keybindings are executed by determining which element the keypress occurred on. In the example above, the your-name-word-count:toggle command is executed when pressing Alt+Ctrl+O on the atom-workspace element. Because the atom-workspace element is the parent of the entire Atom UI, this means the key combination will work anywhere in the application.

    `,7),We=t(`

    Menus are placed in the menus subdirectory. This defines menu elements like what pops up when you right click a context-menu or would go in the application menu to trigger functionality in your plugin.

    By default, all menus are loaded in alphabetical order. An optional menus array in your package.json can specify which menus to load and in what order.

    Application Menu

    It's recommended that you create an application menu item under the Packages menu for common actions with your package that aren't tied to a specific element. If we look in the menus/your-name-word-count.json file that was generated for us, we'll see a section that looks like this:

    
    +"menu": [
    +  {
    +    "label": "Packages",
    +    "submenu": [
    +      {
    +        "label": "Word Count",
    +        "submenu": [
    +          {
    +            "label": "Toggle",
    +            "command": "your-name-word-count:toggle"
    +          }
    +        ]
    +      }
    +    ]
    +  }
    +]
    +
    +

    This section puts a "Toggle" menu item under a menu group named "Your Name Word Count" in the "Packages" menu.

    Application Menu Item

    When you select that menu item, it will run the your-name-word-count:toggle command, which we'll look at in a bit.

    The menu templates you specify are merged with all other templates provided by other packages in the order which they were loaded.

    Context Menu

    It's recommended to specify a context menu item for commands that are linked to specific parts of the interface. In our menus/your-name-word-count.json file, we can see an auto-generated section that looks like this:

    "context-menu": {
    +    "atom-text-editor": [
    +      {
    +        "label": "Toggle your-name-word-count",
    +        "command": "your-name-word-count:toggle"
    +      }
    +    ]
    +  }
    +

    This adds a "Toggle Word Count" menu option to the menu that pops up when you right-click in an Atom text editor pane.

    Context Menu Entry

    When you click that it will again run the your-name-word-count:toggle method in your code.

    Context menus are created by determining which element was selected and then adding all of the menu items whose selectors match that element (in the order which they were loaded). The process is then repeated for the elements until reaching the top of the DOM tree.

    You can also add separators and submenus to your context menus. To add a submenu, provide a submenu key instead of a command. To add a separator, add an item with a single type: 'separator' key/value pair. For instance, you could do something like this:

    {
    +  "context-menu": {
    +    "atom-workspace": [
    +      {
    +        "label": "Text",
    +        "submenu": [
    +          {
    +            "label": "Inspect Element",
    +            "command": "core:inspect"
    +          },
    +          {
    +            "type": "separator"
    +          },
    +          {
    +            "label": "Selector All",
    +            "command": "core:select-all"
    +          },
    +          {
    +            "type": "separator"
    +          },
    +          {
    +            "label": "Deleted Selected Text",
    +            "command": "core:delete"
    +          }
    +        ]
    +      }
    +    ]
    +  }
    +}
    +

    Developing Our Package

    Currently with the generated package we have, if we run that your-name-word-count:toggle command through the menu or the command palette, we'll get a dialog that says "The YourNameWordCount package is Alive! It's ALIVE!".

    Wordcount Package is Alive Dialog

    Understanding the Generated Code

    Let's take a look at the code in our lib directory and see what is happening.

    There are two files in our lib directory. One is the main file (lib/your-name-word-count.js), which is pointed to in the package.json file as the main file to execute for this package. This file handles the logic of the whole plugin.

    The second file is a View class, lib/your-name-word-count-view.js, which handles the UI elements of the package. Let's look at this file first, since it's pretty simple.

    export default class YourNameWordCountView {
    +	constructor(serializedState) {
    +		// Create root element
    +		this.element = document.createElement("div");
    +		this.element.classList.add("your-name-word-count");
    +
    +		// Create message element
    +		const message = document.createElement("div");
    +		message.textContent = "The YourNameWordCount package is Alive! It's ALIVE!";
    +		message.classList.add("message");
    +		this.element.appendChild(message);
    +	}
    +
    +	// Returns an object that can be retrieved when package is activated
    +	serialize() {}
    +
    +	// Tear down any state and detach
    +	destroy() {
    +		this.element.remove();
    +	}
    +
    +	getElement() {
    +		return this.element;
    +	}
    +}
    +

    Basically the only thing happening here is that when the View class is created, it creates a simple div element and adds the your-name-word-count class to it (so we can find or style it later) and then adds the "Your Name Word Count package is Alive!" text to it. There is also a getElement method which returns that div. The serialize and destroy methods don't do anything and we won't have to worry about that until another example.

    Notice that we're simply using the basic browser DOM methods: createElement() and appendChild().

    The second file we have is the main entry point to the package. Again, because it's referenced in the package.json file. Let's take a look at that file.

    import YourNameWordCountView from "./your-name-word-count-view";
    +import { CompositeDisposable } from "atom";
    +
    +export default {
    +	yourNameWordCountView: null,
    +	modalPanel: null,
    +	subscriptions: null,
    +
    +	activate(state) {
    +		this.yourNameWordCountView = new YourNameWordCountView(
    +			state.yourNameWordCountViewState
    +		);
    +		this.modalPanel = atom.workspace.addModalPanel({
    +			item: this.yourNameWordCountView.getElement(),
    +			visible: false,
    +		});
    +
    +		// Events subscribed to in atom's system can be easily cleaned up with a CompositeDisposable
    +		this.subscriptions = new CompositeDisposable();
    +
    +		// Register command that toggles this view
    +		this.subscriptions.add(
    +			atom.commands.add("atom-workspace", {
    +				"your-name-word-count:toggle": () => this.toggle(),
    +			})
    +		);
    +	},
    +
    +	deactivate() {
    +		this.modalPanel.destroy();
    +		this.subscriptions.dispose();
    +		this.yourNameWordCountView.destroy();
    +	},
    +
    +	serialize() {
    +		return {
    +			yourNameWordCountViewState: this.yourNameWordCountView.serialize(),
    +		};
    +	},
    +
    +	toggle() {
    +		console.log("YourNameWordCount was toggled!");
    +		return this.modalPanel.isVisible()
    +			? this.modalPanel.hide()
    +			: this.modalPanel.show();
    +	},
    +};
    +

    There is a bit more going on here. First of all we can see that we are defining four methods. The only required one is activate. The deactivate and serialize methods are expected by Atom but optional. The toggle method is one Atom is not looking for, so we'll have to invoke it somewhere for it to be called, which you may recall we do both in the activationCommands section of the package.json file and in the action we have in the menu file.

    The deactivate method simply destroys the various class instances we've created and the serialize method simply passes on the serialization to the View class. Nothing too exciting here.

    The activate command does a number of things. For one, it is not called automatically when Atom starts up, it is first called when one of the activationCommands as defined in the package.json file are called. In this case, activate is only called the first time the toggle command is called. If nobody ever invokes the menu item or hotkey, this code is never called.

    This method does two things. The first is that it creates an instance of the View class we have and adds the element that it creates to a hidden modal panel in the Atom workspace.

    this.yourNameWordCountView = new YourNameWordCountView(
    +	state.yourNameWordCountViewState
    +);
    +this.modalPanel = atom.workspace.addModalPanel({
    +	item: this.yourNameWordCountView.getElement(),
    +	visible: false,
    +});
    +

    We'll ignore the state stuff for now, since it's not important for this simple plugin. The rest should be fairly straightforward.

    The next thing this method does is create an instance of the CompositeDisposable class so it can register all the commands that can be called from the plugin so other plugins could subscribe to these events.

    // Events subscribed to in atom's system can be easily cleaned up with a CompositeDisposable
    +this.subscriptions = new CompositeDisposable();
    +
    +// Register command that toggles this view
    +this.subscriptions.add(
    +	atom.commands.add("atom-workspace", {
    +		"your-name-word-count:toggle": () => this.toggle(),
    +	})
    +);
    +

    Next we have the toggle method. This method simply toggles the visibility of the modal panel that we created in the activate method.

    toggle() {
    +  console.log('YourNameWordCount was toggled!');
    +  return (
    +    this.modalPanel.isVisible() ?
    +    this.modalPanel.hide() :
    +    this.modalPanel.show()
    +  );
    +}
    +

    This should be fairly simple to understand. We're looking to see if the modal element is visible and hiding or showing it depending on its current state.

    The Flow

    So, let's review the actual flow in this package.

    1. Atom starts up
    2. Atom starts loading packages
    3. Atom reads your package.json
    4. Atom loads keymaps, menus, styles and the main module
    5. Atom finishes loading packages
    6. At some point, the user executes your package command your-name-word-count:toggle
    7. Atom executes the activate method in your main module which sets up the UI by creating the hidden modal view
    8. Atom executes the package command your-name-word-count:toggle which reveals the hidden modal view
    9. At some point, the user executes the your-name-word-count:toggle command again
    10. Atom executes the command which hides the modal view
    11. Eventually, Atom is shut down which can trigger any serializations that your package has defined

    Tip

    Tip: Keep in mind that the flow will be slightly different if you choose not to use activationCommands in your package.

    Counting the Words

    So now that we understand what is happening, let's modify the code so that our little modal box shows us the current word count instead of static text.

    We'll do this in a very simple way. When the dialog is toggled, we'll count the words right before displaying the modal. So let's do this in the toggle command. If we add some code to count the words and ask the view to update itself, we'll have something like this:

    toggle() {
    +  if (this.modalPanel.isVisible()) {
    +    this.modalPanel.hide();
    +  } else {
    +    const editor = atom.workspace.getActiveTextEditor();
    +    const words = editor.getText().split(/\\s+/).length;
    +    this.yourNameWordCountView.setCount(words);
    +    this.modalPanel.show();
    +  }
    +}
    +
    `,50),Fe={href:"https://atom.io/docs/api/latest/Workspace#instance-getActiveTextEditor",target:"_blank",rel:"noopener noreferrer"},De=e("code",null,"atom.workspace.getActiveTextEditor()",-1),Le={href:"https://atom.io/docs/api/latest/TextEditor#instance-getText",target:"_blank",rel:"noopener noreferrer"},Oe=e("code",null,"getText()",-1),ze=t(`

    Finally, we tell our view to update the word count it displays by calling the setCount() method on our view and then showing the modal again. Since that method doesn't yet exist, let's create it now.

    We can add this code to the end of our your-name-word-count-view.js file:

    setCount(count) {
    +  const displayText = \`There are \${count} words.\`;
    +  this.element.children[0].textContent = displayText;
    +}
    +

    Pretty simple! We take the count number that was passed in and place it into a string that we then stick into the element that our view is controlling.

    Note

    Note: To see your changes, you'll need to reload the code. You can do this by reloading the window (The window:reload command in the Command Palette). A common practice is to have two Atom windows, one for developing your package, and one for testing and reloading.

    Word Count Working

    Basic Debugging

    You'll notice a few console.log statements in the code. One of the cool things about Atom being built on Chromium is that you can use some of the same debugging tools available to you that you have when doing web development.

    To open up the Developer Console, press Alt+Cmd+ICtrl+Shift+I, or choose the menu option View > Developer > Toggle Developer Tools.

    Developer Tools Debugging

    From here you can inspect objects, run code and view console output just as though you were debugging a web site.

    Testing

    Your package should have tests, and if they're placed in the spec directory, they can be run by Atom.

    ',13),Ye={href:"https://jasmine.github.io/archives/1.3/introduction",target:"_blank",rel:"noopener noreferrer"},Ve=t('
    Running Tests

    Once you've got your test suite written, you can run it by pressing Alt+Cmd+Ctrl+PAlt+Ctrl+P or via the View > Developer > Run Package Specs menu. Our generated package comes with an example test suite, so you can run this right now to see what happens.

    Spec Suite Results

    You can also use the atom --test spec command to run them from the command line. It prints the test output and results to the console and returns the proper status code depending on whether the tests passed or failed.

    Summary

    ',5),$e=e("h3",{id:"package-modifying-text",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#package-modifying-text","aria-hidden":"true"},"#"),n(" Package: Modifying Text")],-1),Ge={href:"https://en.wikipedia.org/wiki/ASCII_art",target:"_blank",rel:"noopener noreferrer"},He=t(`
                                         o888
    +    ooooooo     ooooooo     ooooooo   888
    +  888     888 888     888 888     888 888
    +  888         888     888 888     888 888
    +    88ooo888    88ooo88     88ooo88  o888o
    +
    +

    This should demonstrate how to do basic text manipulation in the current text buffer and how to deal with selections.

    The final package can be viewed at https://github.com/atom/ascii-art.

    Basic Text Insertion

    `,4),Be=e("kbd",{class:"platform-mac"},"Cmd+Shift+P",-1),Je=e("kbd",{class:"platform-windows platform-linux"},"Ctrl+Shift+P",-1),Ke={href:"https://github.com/atom/command-palette",target:"_blank",rel:"noopener noreferrer"},Xe=e("code",null,"ascii-art",-1),Ze=t(`

    Now let's edit the package files to make our ASCII Art package do something interesting. Since this package doesn't need any UI, we can remove all view-related code so go ahead and delete lib/ascii-art-view.js, spec/ascii-art-view-spec.js, and styles/.

    Next, open up lib/ascii-art.js and remove all view code, so it looks like this:

    const { CompositeDisposable } = require("atom");
    +
    +module.exports = {
    +	subscriptions: null,
    +
    +	activate() {
    +		this.subscriptions = new CompositeDisposable();
    +		this.subscriptions.add(
    +			atom.commands.add("atom-workspace", {
    +				"ascii-art:convert": () => this.convert(),
    +			})
    +		);
    +	},
    +
    +	deactivate() {
    +		this.subscriptions.dispose();
    +	},
    +
    +	convert() {
    +		console.log("Convert text!");
    +	},
    +};
    +
    Create a Command

    Now let's add a command. You should namespace your commands with the package name followed by a : and then the name of the command. As you can see in the code, we called our command ascii-art:convert and we will define it to call the convert() method when it's executed.

    So far, that will simply log to the console. Let's start by making it insert something into the text buffer.

    convert() {
    +  const editor = atom.workspace.getActiveTextEditor()
    +  if (editor) {
    +    editor.insertText('Hello, World!')
    +  }
    +}
    +
    `,7),Qe=e("code",null,"atom.workspace.getActiveTextEditor()",-1),en=e("code",null,"convert()",-1),nn={href:"https://atom.io/docs/api/latest/TextEditor#instance-insertText",target:"_blank",rel:"noopener noreferrer"},an=e("code",null,"insertText()",-1),sn=t(`
    Reload the Package

    Before we can trigger ascii-art:convert, we need to load the latest code for our package by reloading the window. Run the command "Window: Reload" from the Command Palette or by pressing Alt+Cmd+Ctrl+LCtrl+Shift+F5.

    Trigger the Command

    Now open the Command Palette and search for the "Ascii Art: Convert" command. But it's not there! To fix this, open package.json and find the property called activationCommands. Activation commands make Atom launch faster by allowing Atom to delay a package's activation until it's needed. So remove the existing command and use ascii-art:convert in activationCommands:

    "activationCommands": {
    +  "atom-workspace": "ascii-art:convert"
    +}
    +

    First, reload the window by running the command "Window: Reload" from the command palette. Now when you run the "Ascii Art: Convert" command it will insert "Hello, World!" into the active editor, if any.

    Add a Key Binding

    Now let's add a key binding to trigger the ascii-art:convert command. Open keymaps/ascii-art.json and add a key binding linking Alt+Ctrl+A to the ascii-art:convert command. You can delete the pre-existing key binding since you won't need it anymore.

    When finished, the file should look like this:

    {
    +  "atom-text-editor": {
    +    "ctrl-alt-a": "ascii-art:convert"
    +  }
    +}
    +
    +

    Now reload the window and verify that the key binding works.

    WARNING

    Warning: The Atom keymap system is case-sensitive. This means that there is a distinction between a and A when creating keybindings. a means that you want to trigger the keybinding when you press A. But A means that you want to trigger the keybinding when you press Shift+A. You can also write shift-a when you want to trigger the keybinding when you press Shift+A.

    We strongly recommend always using lowercase and explicitly spelling out when you want to include Shift in your keybindings.

    Add the ASCII Art

    `,13),tn={href:"https://npmjs.org/package/figlet",target:"_blank",rel:"noopener noreferrer"},on={href:"https://npmjs.org/",target:"_blank",rel:"noopener noreferrer"},ln=e("code",null,"package.json",-1),cn=t(`
    "dependencies": {
    +  "figlet": "1.0.8"
    +}
    +

    After saving the file, run the command "Update Package Dependencies: Update" from the Command Palette. This will install the package's node module dependencies, only figlet in this case. You will need to run "Update Package Dependencies: Update" whenever you update the dependencies field in your package.json file.

    If for some reason this doesn't work, you'll see a message saying "Failed to update package dependencies" and you will find a new npm-debug.log file in your directory. That file should give you some idea as to what went wrong.

    Now require the figlet node module in lib/ascii-art.js and instead of inserting "Hello, World!", convert the selected text to ASCII art.

    convert () {
    +  const editor = atom.workspace.getActiveTextEditor()
    +  if (editor) {
    +    const selection = editor.getSelectedText()
    +
    +    const figlet = require('figlet')
    +    const font = 'o8'
    +    figlet(selection, {font}, function (error, art) {
    +      if (error) {
    +        console.error(error)
    +      } else {
    +        editor.insertText(\`\\n\${art}\\n\`)
    +      }
    +    })
    +  }
    +}
    +

    Now reload the editor, select some text in an editor window and press Alt+Ctrl+A. It should be replaced with a ridiculous ASCII art version instead.

    `,6),rn={href:"https://atom.io/docs/api/latest/TextEditor#instance-getSelectedText",target:"_blank",rel:"noopener noreferrer"},pn=e("code",null,"editor.getSelectedText()",-1),dn={href:"https://atom.io/docs/api/latest/TextEditor#instance-insertText",target:"_blank",rel:"noopener noreferrer"},un=e("code",null,"editor.insertText()",-1),hn=e("h4",{id:"summary-1",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#summary-1","aria-hidden":"true"},"#"),n(" Summary")],-1),mn=e("p",null,"In this section, we've made a UI-less package that takes selected text and replaces it with a processed version. This could be helpful in creating linters or checkers for your code.",-1),gn=e("h3",{id:"package-active-editor-info",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#package-active-editor-info","aria-hidden":"true"},"#"),n(" Package: Active Editor Info")],-1),kn={href:"https://nuclide.io",target:"_blank",rel:"noopener noreferrer"},vn=e("p",null,"For this package, we'll define a workspace item that tells us some information about our active text editor. The final package can be viewed at https://github.com/atom/active-editor-info.",-1),bn=e("h4",{id:"create-the-package",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#create-the-package","aria-hidden":"true"},"#"),n(" Create the Package")],-1),fn=e("kbd",{class:"platform-mac"},"Cmd+Shift+P",-1),yn=e("kbd",{class:"platform-windows platform-linux"},"Ctrl+Shift+P",-1),wn={href:"https://github.com/atom/command-palette",target:"_blank",rel:"noopener noreferrer"},_n=e("code",null,"active-editor-info",-1),xn=t(`

    Add an Opener

    Now let's edit the package files to show our view in a workspace item instead of a modal panel. The way we do this is by registering an opener with Atom. Openers are just functions that accept a URI and return a view (if it's a URI that the opener knows about). When you call atom.workspace.open(), Atom will go through all of its openers until it finds one that can handle the URI you passed.

    Let's open lib/active-editor-info.js and edit our activate() method to register an opener:

    "use babel";
    +
    +import ActiveEditorInfoView from "./active-editor-info-view";
    +import { CompositeDisposable, Disposable } from "atom";
    +
    +export default {
    +	subscriptions: null,
    +
    +	activate(state) {
    +		this.subscriptions = new CompositeDisposable(
    +			// Add an opener for our view.
    +			atom.workspace.addOpener((uri) => {
    +				if (uri === "atom://active-editor-info") {
    +					return new ActiveEditorInfoView();
    +				}
    +			}),
    +
    +			// Register command that toggles this view
    +			atom.commands.add("atom-workspace", {
    +				"active-editor-info:toggle": () => this.toggle(),
    +			}),
    +
    +			// Destroy any ActiveEditorInfoViews when the package is deactivated.
    +			new Disposable(() => {
    +				atom.workspace.getPaneItems().forEach((item) => {
    +					if (item instanceof ActiveEditorInfoView) {
    +						item.destroy();
    +					}
    +				});
    +			})
    +		);
    +	},
    +
    +	deactivate() {
    +		this.subscriptions.dispose();
    +	},
    +
    +	toggle() {
    +		console.log("Toggle it!");
    +	},
    +};
    +

    You'll notice we also removed the activeEditorInfoView property and the serialize() method. That's because, with workspace items, it's possible to have more than one instance of a given view. Since each instance can have its own state, each should do its own serialization instead of relying on a package-level serialize() method. We'll come back to that later.

    You probably also noticed that our toggle() implementation just logs the text "Toggle it!" to the console. Let's make it actually toggle our view:

      toggle() {
    +    atom.workspace.toggle('atom://active-editor-info');
    +  }
    +

    Updating the View

    Atom uses the same view abstractions everywhere, so we can almost use the generated ActiveEditorInfoView class as-is. We just need to add two small methods:

      getTitle() {
    +    // Used by Atom for tab text
    +    return 'Active Editor Info';
    +  }
    +
    +  getURI() {
    +    // Used by Atom to identify the view when toggling.
    +    return 'atom://active-editor-info';
    +  }
    +

    Now reload the window and run the "Active Editor Info: Toggle" command from the command palette! Our view will appear in a new tab in the center of the workspace. If you want, you can drag it into one of the docks. Toggling it again will then hide that dock. If you close the tab and run the toggle command again, it will appear in the last place you had it.

    Note

    We've repeated the same URI three times now. That's okay, but it's probably a good idea to define the URL in one place and then import it from that module wherever you need it.

    Constraining Our Item's Locations

    The purpose of our view is to show information about the active text editor, so it doesn't really make sense to show our item in the center of the workspace (where the text editor will be). Let's add some methods to our view class to influence where its opened:

      getDefaultLocation() {
    +    // This location will be used if the user hasn't overridden it by dragging the item elsewhere.
    +    // Valid values are "left", "right", "bottom", and "center" (the default).
    +    return 'right';
    +  }
    +
    +  getAllowedLocations() {
    +    // The locations into which the item can be moved.
    +    return ['left', 'right', 'bottom'];
    +  }
    +

    Now our item will appear in the right dock initially and users will only be able to drag it to one of the other docks.

    Show Active Editor Info

    Now that we have our view all wired up, let's update it to show some information about the active text editor. Add this to the constructor:

    this.subscriptions = atom.workspace
    +	.getCenter()
    +	.observeActivePaneItem((item) => {
    +		if (!atom.workspace.isTextEditor(item)) {
    +			message.innerText = "Open a file to see important information about it.";
    +			return;
    +		}
    +		message.innerHTML = \`
    +    <h2>\${item.getFileName() || "untitled"}</h2>
    +    <ul>
    +      <li><b>Soft Wrap:</b> \${item.softWrapped}</li>
    +      <li><b>Tab Length:</b> \${item.getTabLength()}</li>
    +      <li><b>Encoding:</b> \${item.getEncoding()}</li>
    +      <li><b>Line Count:</b> \${item.getLineCount()}</li>
    +    </ul>
    +  \`;
    +	});
    +

    Now whenever you open a text editor in the center, the view will update with some information about it.

    WARNING

    We use a template string here because it's simple and we have a lot of control over what's going into it, but this could easily result in the insertion of unwanted HTML if you're not careful. Sanitize your input and use the DOM API or a templating system when doing this for real.

    Also, don't forget to clean up the subscription in the destroy() method:

    destroy() {
    +  this.element.remove();
    +  this.subscriptions.dispose();
    +}
    +

    Serialization

    If you were to reload Atom now, you'd see that our item had disappeared. That's because we haven't told Atom how to serialize it yet. Let's do that now.

    The first step is to implement a serialize() method on our ActiveEditorInfoView class. Atom will call the serialize() method on every item in the workspace periodically to save its state.

      serialize() {
    +    return {
    +      // This is used to look up the deserializer function. It can be any string, but it needs to be
    +      // unique across all packages!
    +      deserializer: 'active-editor-info/ActiveEditorInfoView'
    +    };
    +  }
    +

    Note

    All of our view's state is derived from the active text editor so we only need the deserializer field. If we had other state that we wanted to preserve across reloads, we would just add things to the object we're returning. Just make sure that they're JSON serializable!

    Next we need to register a deserializer function that Atom can use to recreate the real object when it starts up. The best way to do that is to add a "deserializers" object to our package.json file:

    {
    +  "name": "active-editor-info",
    +  ...
    +  "deserializers": {
    +    "active-editor-info/ActiveEditorInfoView": "deserializeActiveEditorInfoView"
    +  }
    +}
    +

    Notice that the key ("active-editor-info/ActiveEditorInfoView") matches the string we used in our serialize() method above. The value ("deserializeActiveEditorInfoView") refers to a function in our main module, which we still need to add. Go back to active-editor-info.js and do that now:

      deserializeActiveEditorInfoView(serialized) {
    +    return new ActiveEditorInfoView();
    +  }
    +

    The value returned from our serialize() method will be passed to this function. Since our serialized object didn't include any state, we can just return a new ActiveEditorInfoView instance.

    Reload Atom and toggle the view with the "Active Editor Info: Toggle" command. Then reload Atom again. Your view should be just where you left it!

    Summary

    In this section, we've made a toggleable workspace item whose placement can be controlled by the user. This could be helpful when creating all sorts of visual tools for working with code!

    Creating a Theme

    `,37),qn={href:"http://lesscss.org/",target:"_blank",rel:"noopener noreferrer"},An=t('

    Atom supports two types of themes: UI and Syntax. UI themes style elements such as the tree view, the tabs, drop-down lists, and the status bar. Syntax themes style the code, gutter and other elements inside the editor view.

    Theme boundary

    Themes can be installed and changed from the Settings View which you can open by selecting the Atom > PreferencesFile > PreferencesEdit > Preferences menu, and clicking the "Install" or "Themes" tab on the left hand navigation.

    Getting Started

    Themes are pretty straightforward but it's still helpful to be familiar with a few things before starting:

    ',5),Tn={href:"https://speakerdeck.com/danmatthews/less-css",target:"_blank",rel:"noopener noreferrer"},Cn=e("code",null,"package.json",-1),Sn=e("code",null,"package.json",-1),In=e("li",null,[n("Your theme's "),e("code",null,"package.json"),n(" must contain a "),e("code",null,"theme"),n(" key with a value of "),e("code",null,"ui"),n(" or "),e("code",null,"syntax"),n(" for Atom to recognize and load it as a theme.")],-1),jn={href:"https://atom.io/themes",target:"_blank",rel:"noopener noreferrer"},Pn=t('

    Creating a Syntax Theme

    Let's create your first theme.

    To get started, press Cmd+Shift+PCtrl+Shift+P and start typing "Generate Syntax Theme" to generate a new theme package. Select "Generate Syntax Theme," and you'll be asked for the path where your theme will be created. Let's call ours motif-syntax.

    Tip

    Tip: Syntax themes should end with -syntax and UI themes should end with -ui.

    Atom will display a new window, showing the motif-syntax theme, with a default set of folders and files created for us. If you open the Settings View with Cmd+,Ctrl+, and click the "Themes" tab on the left, you'll see the "Motif" theme listed in the "Syntax Theme" drop-down. Select it from the menu to activate it, now when you open an editor you should see your new motif-syntax theme in action.

    Open up styles/colors.less to change the various color variables which have already been defined. For example, turn @red into #f4c2c1.

    Then open styles/base.less and modify the various selectors that have already been defined. These selectors style different parts of code in the editor such as comments, strings and the line numbers in the gutter.

    As an example, let's make the .gutter background-color into @red.

    Reload Atom by pressing Alt+Cmd+Ctrl+LAlt+Ctrl+R to see the changes you made reflected in your Atom window. Pretty neat!

    Tip

    Tip: You can avoid reloading to see changes you make by opening an Atom window in Dev Mode. To open a Dev Mode Atom window run atom --dev . in the terminal, or use the View > Developer > Open in Dev Mode menu. When you edit your theme, changes will instantly be reflected!

    Note

    Note: It's advised to not specify a font-family in your syntax theme because it will override the Font Family field in Atom's settings. If you still like to recommend a font that goes well with your theme, we suggest you do so in your README.

    Creating a UI Theme

    To create a UI theme, do the following:

    ',13),Rn={href:"https://github.com/atom-community/ui-theme-template",target:"_blank",rel:"noopener noreferrer"},Nn=t('
  • Clone the forked repository to the local filesystem
  • Open a terminal in the forked theme's directory
  • Open your new theme in a Dev Mode Atom window run atom --dev . in the terminal or use the View > Developer > Open in Dev Mode menu
  • Change the name of the theme in the theme's package.json file
  • Name your theme end with a -ui, for example super-white-ui
  • Run apm link --dev to symlink your repository to ~/.atom/dev/packages
  • Reload Atom using Alt+Cmd+Ctrl+LAlt+Ctrl+R
  • Enable the theme via the "UI Theme" drop-down in the "Themes" tab of the Settings View
  • Make changes! Since you opened the theme in a Dev Mode window, changes will be instantly reflected in the editor without having to reload.
  • ',9),En=t('

    Tip

    Tip: Because we used apm link --dev in the above instructions, if you break anything you can always close Atom and launch Atom normally to force Atom to the default theme. This allows you to continue working on your theme even if something goes catastrophically wrong.

    Theme Variables

    UI themes must provide a ui-variables.less and Syntax themes a syntax-variables.less file. It contains predefined variables that packages use to make sure the look and feel matches.

    Here the variables with the default values:

    ',4),Un={href:"https://github.com/atom/atom/blob/master/static/variables/ui-variables.less",target:"_blank",rel:"noopener noreferrer"},Mn={href:"https://github.com/atom/atom/blob/master/static/variables/syntax-variables.less",target:"_blank",rel:"noopener noreferrer"},Wn=e("p",null,"These default values will be used as a fallback in case a theme doesn't define its own variables.",-1),Fn=e("h5",{id:"use-in-packages",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#use-in-packages","aria-hidden":"true"},"#"),n(" Use in Packages")],-1),Dn=e("p",null,[n("In any of your package's "),e("code",null,".less"),n(" files, you can access the theme variables by importing the "),e("code",null,"ui-variables"),n(" or "),e("code",null,"syntax-variables"),n(" file from Atom.")],-1),Ln={href:"https://github.com/atom/styleguide",target:"_blank",rel:"noopener noreferrer"},On=t(`

    Here's an example .less file that a package can define using theme variables:

    @import "ui-variables";
    +
    +.my-selector {
    +	background-color: @base-background-color;
    +	padding: @component-padding;
    +}
    +
    @import "syntax-variables";
    +
    +.my-selector {
    +	background-color: @syntax-background-color;
    +}
    +

    Development workflow

    There are a few tools to help make theme development faster and easier.

    Live Reload
    `,6),zn=e("kbd",{class:"platform-mac"},"Alt+Cmd+Ctrl+L",-1),Yn=e("kbd",{class:"platform-windows platform-linux"},"Alt+Ctrl+R",-1),Vn={href:"https://github.com/atom/dev-live-reload",target:"_blank",rel:"noopener noreferrer"},$n=t('

    To launch a Dev Mode window:

    • Open your theme directory in a dev window by selecting the View > Developer > Open in Dev Mode menu item
    • Or launch Atom from the terminal with atom --dev

    If you'd like to reload all the styles at any time, you can use the shortcut Alt+Cmd+Ctrl+LAlt+Ctrl+R.

    Developer Tools

    Atom is based on the Chrome browser, and supports Chrome's Developer Tools. You can open them by selecting the View > Developer > Toggle Developer Tools menu, or by using the Alt+Cmd+ICtrl+Shift+I shortcut.

    The dev tools allow you to inspect elements and take a look at their CSS properties.

    Developer Tools

    ',7),Gn={href:"https://developer.chrome.com/devtools/docs/dom-and-styles",target:"_blank",rel:"noopener noreferrer"},Hn=e("h5",{id:"atom-styleguide",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#atom-styleguide","aria-hidden":"true"},"#"),n(" Atom Styleguide")],-1),Bn={href:"https://github.com/atom/styleguide",target:"_blank",rel:"noopener noreferrer"},Jn=t('

    To open the Styleguide, open the command palette with Cmd+Shift+PCtrl+Shift+P and search for "styleguide", or use the shortcut Cmd+Ctrl+Shift+GCtrl+Shift+G.

    Style Guide

    Side by side

    Sometimes when creating a theme (or package) things can go wrong and the editor becomes un-usable. E.g. if the text and background have the same color or something gets pushed out of sight. To avoid having to open Atom in "normal" mode to fix the issue, it's advised to open two Atom windows. One for making changes and one in Dev Mode to see the changes getting applied.

    Side by side screenshot

    Make changes on the left, see the changes getting applied in "Dev Mode" on the right.

    Now if you mess up something, only the window in "Dev Mode" will be affected and you can easily correct the mistake in your "normal" window.

    Publish your theme

    Once you're happy with your theme and would like to share it with other Atom users, it's time to publish it. \u{1F389}

    ',9),Kn=e("h3",{id:"creating-a-grammar",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#creating-a-grammar","aria-hidden":"true"},"#"),n(" Creating a Grammar")],-1),Xn={href:"http://tree-sitter.github.io/tree-sitter",target:"_blank",rel:"noopener noreferrer"},Zn={href:"https://en.wikipedia.org/wiki/Abstract_syntax_tree",target:"_blank",rel:"noopener noreferrer"},Qn=e("em",null,"syntax trees",-1),ea=t('

    This syntax tree gives Atom a comprehensive understanding of the structure of your code, which has several benefits:

    1. Syntax highlighting will not break because of formatting changes.
    2. Code folding will work regardless of how your code is indented.
    3. Editor features can operate on the syntax tree. For instance, the Select Larger Syntax Node and Select Smaller Syntax Node allow you to select conceptually larger and smaller chunks of your code.
    4. Community packages can use the syntax tree to manipulate code intelligently.

    Tree-sitter grammars are relatively new. Many languages in Atom are still supported by TextMate grammars, though we intend to phase these out over time.

    If you're adding support for a new language, you're in the right place!

    Getting Started

    There are two components required to use Tree-sitter in Atom: a parser and a grammar file.

    The Parser

    ',7),na={href:"https://en.wikipedia.org/wiki/Context-free_grammar",target:"_blank",rel:"noopener noreferrer"},aa={href:"http://tree-sitter.github.io/tree-sitter/creating-parsers",target:"_blank",rel:"noopener noreferrer"},sa={href:"https://github.com/tree-sitter",target:"_blank",rel:"noopener noreferrer"},ta={href:"https://npmjs.com",target:"_blank",rel:"noopener noreferrer"},oa=e("code",null,"name",-1),ia=e("code",null,"version",-1),la=e("code",null,"package.json",-1),ca=t(`
    {
    +  "name": "tree-sitter-mylanguage",
    +  "version": "0.0.1",
    +  // ...
    +}
    +

    then run the command npm publish.

    The Package

    Once you have a Tree-sitter parser that is available on npm, you can use it in your Atom package. Packages with grammars are, by convention, always named starting with language. You'll need a folder with a package.json, a grammars subdirectory, and a single json or cson file in the grammars directory, which can be named anything.

    language-mylanguage
    +\u251C\u2500\u2500 LICENSE
    +\u251C\u2500\u2500 README.md
    +\u251C\u2500\u2500 grammars
    +\u2502   \u2514\u2500\u2500 mylanguage.cson
    +\u2514\u2500\u2500 package.json
    +

    The Grammar File

    The mylanguage.cson file specifies how Atom should use the parser you created.

    Basic Fields

    It starts with some required fields:

    name: 'My Language'
    +scopeName: 'mylanguage'
    +type: 'tree-sitter'
    +parser: 'tree-sitter-mylanguage'
    +
    `,10),ra=e("li",null,[e("code",null,"scopeName"),n(" - A unique, stable identifier for the language. Atom users will use this in configuration files if they want to specify custom configuration based on the language.")],-1),pa=e("li",null,[e("code",null,"name"),n(" - A human readable name for the language.")],-1),da=e("code",null,"parser",-1),ua={href:"https://nodejs.org/api/modules.html#modules_require",target:"_blank",rel:"noopener noreferrer"},ha=e("code",null,"require()",-1),ma=e("li",null,[e("code",null,"type"),n(" - This should have the value "),e("code",null,"tree-sitter"),n(" to indicate to Atom that this is a Tree-sitter grammar and not a "),e("a",{href:"./creating-a-legacy-textmate-grammar"},"TextMate grammar"),n(".")],-1),ga=t(`

    Language Recognition

    Next, the file should contain some fields that indicate to Atom when this language should be used. These fields are all optional.

    • fileTypes - An array of filename suffixes. The grammar will be used for files whose names end with one of these suffixes. Note that the suffix may be an entire filename.
    • firstLineRegex - A regex pattern that will be tested against the first line of the file. The grammar will be used if this regex matches.
    • contentRegex - A regex pattern that will be tested against the contents of the file in order to break ties in cases where multiple grammars matched the file using the above two criteria. If the contentRegex matches, this grammar will be preferred over another grammar with no contentRegex. If the contentRegex does not match, a grammar with no contentRegex will be preferred over this one.

    Syntax Highlighting

    The HTML classes that Atom uses for syntax highlighting do not correspond directly to nodes in the syntax tree. Instead, Tree-sitter grammar files specify scope mappings that specify which classes should be applied to which syntax nodes. The scopes object controls these scope mappings. Its keys are CSS selectors that select nodes in the syntax tree. Its values can be of several different types.

    Here is a simple example:

    scopes:
    +  'call_expression > identifier': 'entity.name.function'
    +

    This entry means that, in the syntax tree, any identifier node whose parent is a call_expression should be highlighted using three classes: syntax--entity, syntax--name, and syntax--function.

    `,8),ka={href:"https://developer.mozilla.org/en-US/docs/Web/CSS/Child_selectors",target:"_blank",rel:"noopener noreferrer"},va=e("code",null,">",-1),ba=e("code",null,"'call_expression identifier'",-1),fa=e("code",null,"identifier",-1),ya=e("code",null,"call_expression",-1),wa=t(`
    Advanced Selectors

    The keys of the scopes object can also contain multiple CSS selectors, separated by commas, similar to CSS files. The triple-quote syntax in CSON makes it convenient to write keys like this on multiple lines:

    scopes:
    +  '''
    +  function_declaration > identifier,
    +  call_expression > identifier,
    +  call_expression > field_expression > field_identifier
    +  ''': 'entity.name.function'
    +
    `,3),_a={href:"https://developer.mozilla.org/en-US/docs/Web/CSS/:nth-child",target:"_blank",rel:"noopener noreferrer"},xa=e("code",null,":nth-child",-1),qa=e("code",null,"identifier",-1),Aa=e("code",null,"singleton_method",-1),Ta=t(`
    scopes:
    +  'singleton_method > identifier:nth-child(3)': 'entity.name.function'
    +
    `,1),Ca=e("em",null,"anonymous",-1),Sa=e("code",null,"(",-1),Ia=e("code",null,":",-1),ja={href:"http://tree-sitter.github.io/tree-sitter/using-parsers#named-vs-anonymous-nodes",target:"_blank",rel:"noopener noreferrer"},Pa=t('
    scopes:\n  '''\n    "*",\n    "/",\n    "+",\n    "-"\n  ''': 'keyword.operator'\n
    Text-based Mappings

    You can also apply different classes to a syntax node based on its text. Here are some examples:

    scopes:\n\n  # Apply the classes `syntax--builtin` and `syntax--variable` to all\n  # `identifier` nodes whose text is `require`.\n  'identifier': {exact: 'require', scopes: 'builtin.variable'},\n\n  # Apply the classes `syntax--type` and `syntax--integer` to all\n  # `primitive_type` nodes whose text starts with `int` or `uint`.\n  'primitive_type': {match: /^u?int/, scopes: 'type.integer'},\n\n  # Apply the classes `syntax--builtin`, `syntax--class`, and\n  # `syntax--name` to `constant` nodes with the text `Array`,\n  # `Hash` and `String`. For all other `constant` nodes, just\n  # apply the classes `syntax--class` and `syntax--name`.\n  'constant': [\n    {match: '^(Array|Hash|String)$', scopes: 'builtin.class.name'},\n    'class.name'\n  ]\n

    In total there are four types of values that can be associated with selectors in scopes:

    • Strings - Each class name in the dot-separated string will be prefixed with syntax-- and applied to the selected node.
    • Objects with the keys exact and scopes - If the node's text equals the exact string, the scopes string will be used as described above.
    • Objects with the keys match and scopes - If the node's text matches the match regex pattern, the scopes string will be used as described above.
    • Arrays - The elements of the array will be processed from beginning to end. The first element that matches the selected node will be used as describe above.
    Specificity
    ',7),Ra=e("code",null,"scopes",-1),Na={href:"https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity",target:"_blank",rel:"noopener noreferrer"},Ea=e("code",null,"exact",-1),Ua=e("code",null,"match",-1),Ma=e("em",null,"not",-1),Wa=e("code",null,"exact",-1),Fa=e("code",null,"match",-1),Da=t(`
    scopes:
    +  'call_expression > identifier': 'entity.name.function'
    +
    +  # If we did not include the second selector here, then this rule
    +  # would not apply to identifiers inside of call_expressions,
    +  # because the selector \`call_expression > identifier\` is more
    +  # specific than the selector \`identifier\`.
    +  'identifier, call_expression > identifier': [
    +    {exact: 'require', scopes: 'builtin.variable'},
    +    {match: '^[A-Z]', scopes: 'constructor'},
    +  ]
    +

    Language Injection

    Sometimes, a source file can contain code written in several different languages. Tree-sitter grammars support this situation using a two-part process called language injection. First, an 'outer' language must define an injection point - a set of syntax nodes whose text can be parsed using a different language, along with some logic for guessing the name of the other language that should be used. Second, an 'inner' language must define an injectionRegex - a regex pattern that will be tested against the language name provided by the injection point.

    `,3),La={href:"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#Tagged_templates",target:"_blank",rel:"noopener noreferrer"},Oa=t(`
    // HTML in a template literal
    +const htmlContent = html\`<div>Hello \${name}</div>\`;
    +

    The tree-sitter-javascript parser parses this tagged template literal as a call_expression with two children: an identifier and a template_literal:

    (call_expression
    +  (identifier)
    +  (template_literal
    +    (interpolation
    +      (identifier))))
    +

    Here is an injection point that would allow syntax highlighting inside of template literals:

    atom.grammars.addInjectionPoint("source.js", {
    +	type: "call_expression",
    +
    +	language(callExpression) {
    +		const { firstChild } = callExpression;
    +		if (firstChild.type === "identifier") {
    +			return firstChild.text;
    +		}
    +	},
    +
    +	content(callExpression) {
    +		const { lastChild } = callExpression;
    +		if (lastChild.type === "template_string") {
    +			return lastChild;
    +		}
    +	},
    +});
    +

    The language callback would then be called with every call_expression node in the syntax tree. In the example above, it would retrieve the first child of the call_expression, which is an identifier with the name "html". The callback would then return the string "html".

    The content callback would then be called with the same call_expression node and return the template_string node within the call_expression node.

    In order to parse the HTML within the template string, the HTML grammar file would need to specify an injectionRegex:

    injectionRegex: 'html|HTML'
    +

    Code Folding

    The next field in the grammar file, folds, controls code folding. Its value is an array of fold pattern objects. Fold patterns are used to decide whether or not a syntax node can be folded, and if so, where the fold should start and end. Here are some example fold patterns:

    folds: [
    +
    +  # All \`comment\` nodes are foldable. By default, the fold starts at
    +  # the end of the node's first line, and ends at the beginning
    +  # of the node's last line.
    +  {
    +    type: 'comment'
    +  }
    +
    +  # \`if_statement\` nodes are foldable if they contain an anonymous
    +  # "then" token and either an \`elif_clause\` or \`else_clause\` node.
    +  # The fold starts at the end of the "then" token and ends at the
    +  # \`elif_clause\` or \`else_clause\`.
    +  {
    +    type: 'if_statement',
    +    start: {type: '"then"'}
    +    end: {type: ['elif_clause', 'else_clause']}
    +  }
    +
    +  # Any node that starts with an anonymous "(" token and ends with
    +  # an anonymous ")" token is foldable. The fold starts after the
    +  # "(" and ends before the ")".
    +  {
    +    start: {type: '"("', index: 0},
    +    end: {type: '")"', index: -1}
    +  }
    +]
    +

    Fold patterns can have one or more of the following fields:

    • type - A string or array of strings. In order to be foldable according to this pattern, a syntax node's type must match one of these strings.
    • start - An object that is used to identify a child node after which the fold should start. The object can have one or both of the following fields:
      • type - A string or array of strings. To start a fold, a child node's type must match one of these strings.
      • index - a number that's used to select a specific child according to its index. Negative values are interpreted as indices relative the last child, so that -1 means the last child.
    • end - An object that is used to identify a child node before which the fold should end. It has the same structure as the start object.

    Comments

    The last field in the grammar file, comments, controls the behavior of Atom's Editor: Toggle Line Comments command. Its value is an object with a start field and an optional end field. The start field is a string that should be prepended to or removed from lines in order to comment or un-comment them.

    In JavaScript, it looks like this:

    comments:
    +  start: '// '
    +

    The end field should be used for languages that only support block comments, not line comments. If present, it will be appended to or removed from the end of the last selected line in order to comment or un-comment the selection.

    In CSS, it would look like this:

    comments:
    +  start: '/* '
    +  end: ' */'
    +

    Example Packages

    More examples of all of these features can be found in the Tree-sitter grammars bundled with Atom:

    `,23),za={href:"https://github.com/atom/language-shellscript",target:"_blank",rel:"noopener noreferrer"},Ya={href:"https://github.com/atom/language-c",target:"_blank",rel:"noopener noreferrer"},Va={href:"https://github.com/atom/language-go",target:"_blank",rel:"noopener noreferrer"},$a={href:"https://github.com/atom/language-html",target:"_blank",rel:"noopener noreferrer"},Ga={href:"https://github.com/atom/language-javascript",target:"_blank",rel:"noopener noreferrer"},Ha={href:"https://github.com/atom/language-python",target:"_blank",rel:"noopener noreferrer"},Ba={href:"https://github.com/atom/language-ruby",target:"_blank",rel:"noopener noreferrer"},Ja={href:"https://github.com/atom/language-typescript",target:"_blank",rel:"noopener noreferrer"},Ka=t('

    Creating a Legacy TextMate Grammar

    Atom's syntax highlighting can be powered by two types of grammars. If you're adding support for a new language, the preferred way is to create a Tree-sitter grammar. Tree-sitter grammars have better performance and provide support for more editor features, such as the Select Larger Syntax Node command.

    This section describes the Atom's legacy support for TextMate grammars.

    TextMate grammars are supported by several popular text editors. They provide a set of regex (regular expression) patterns which are assigned scopes. These scopes are then turned into the CSS classes that you can target in syntax themes.

    Note

    Note: This tutorial is a work in progress.

    Getting Started

    TextMate Grammars depend heavily on regexes, and you should be comfortable with interpreting and writing regexes before continuing. Note that Atom uses the Oniguruma engine, which is very similar to the PCRE or Perl regex engines. Here are some resources to help you out:

    • https://www.regular-expressions.info/tutorial.html provides a comprehensive regex tutorial
    • https://www.rexegg.com/regex-quickstart.html contains a cheat sheet for various regex expressions
    • https://regex101.com/ or https://regexr.com/ allows live prototyping
    • https://github.com/kkos/oniguruma/blob/master/doc/RE the docs for the Oniguruma regex engine
    ',8),Xa={href:"https://github.com/bevry/cson#what-is-cson",target:"_blank",rel:"noopener noreferrer"},Za={href:"https://www.json.org/",target:"_blank",rel:"noopener noreferrer"},Qa=t('

    Create the Package

    To get started, press Cmd+Shift+PCtrl+Shift+P and start typing "Generate Package" to generate a new grammar package. Select "Package Generator: Generate Package," and you'll be asked for the path where your package will be created. Let's call ours language-flight-manual.

    Tip

    Tip: Grammar packages should start with language-.

    ',3),es=e("code",null,"keymaps",-1),ns=e("code",null,"lib",-1),as=e("code",null,"menus",-1),ss=e("code",null,"styles",-1),ts=e("code",null,"package.json",-1),os=e("code",null,"activationCommands",-1),is=e("code",null,"grammars",-1),ls=e("code",null,"flight-manual.cson",-1),cs={href:"https://gist.github.com/DamnedScholar/622926bcd222eb1ddc483d12103fd315",target:"_blank",rel:"noopener noreferrer"},rs=t(`

    scopeName is the root scope of your package. This should generally describe what language your grammar package is highlighting; for example, language-javascript's scopeName is source.js and language-html's is text.html.basic. Name it source.flight-manual for now.

    name is the user-friendly name that is displayed in places like the status bar or the grammar selector. Again, this name should describe what the grammar package is highlighting. Rename it to Flight Manual.

    fileTypes is an array of filetypes that language-flight-manual should highlight. We're interested in highlighting the Flight Manual's Markdown files, so add the md extension to the list and remove the others.

    patterns contains the array of regex patterns that will determine how the file is tokenized.

    Adding Patterns

    To start, let's add a basic pattern to tokenize the words Flight Manual whenever they show up. Your regex should look like \\bFlight Manual\\b. Here's what your patterns block should look like:

    'patterns': [
    +  {
    +    'match': '\\\\bFlight Manual\\\\b'
    +    'name': 'entity.other.flight-manual'
    +  }
    +]
    +
    `,7),ps=e("code",null,"match",-1),ds=e("code",null,"name",-1),us={href:"https://manual.macromates.com/en/language_grammars",target:"_blank",rel:"noopener noreferrer"},hs=t(`

    Tip

    Tip: All scopes should end with the portion of the root scopeName after the leading source or text. In our case, all scopes should end with flight-manual.

    Note

    Note: Astute readers may have noticed that the \\b was changed to \\\\b with two backslashes and not one. This is because CSON processes the regex string before handing it to Oniguruma, so all backslashes need to be escaped twice.

    But what if we wanted to apply different scopes to Flight and Manual? This is possible by adding capture groups to the regex and then referencing those capture groups in a new capture property. For example:

    'match': '\\\\b(Flight) (Manual)\\\\b'
    +'name': 'entity.other.flight-manual'
    +'captures':
    +  '1':
    +    'name': 'keyword.other.flight.flight-manual'
    +  '2':
    +    'name': 'keyword.other.manual.flight-manual'
    +

    This will assign the scope keyword.other.flight.flight-manual to Flight, keyword.other.manual.flight-manual to Manual, and entity.other.flight-manual to the overarching Flight Manual.

    Begin/End Patterns

    Now let's say we want to tokenize the ::: note Note blocks that occur in Flight Manual files. Our previous two examples used match, but one limit of match is that it can only match single lines. ::: note Note blocks, on the other hand, can span multiple lines. For these cases, you can use the begin/end keys. Once the regex in the begin key is matched, tokenization will continue until the end pattern is reached.

    'begin': '({{)(#note)(}})'
    +'beginCaptures':
    +  '0': # The 0 capture contains the entire match
    +    'name': 'meta.block.start.flight-manual'
    +  '1':
    +    'name': 'punctuation.definition.block.flight-manual'
    +  '2':
    +    'name': 'keyword.note.flight-manual'
    +  '3':
    +    'name': 'punctuation.definition.block.flight-manual'
    +'end': '({{)(/note)(}})'
    +'endCaptures':
    +  '0':
    +    'name': 'meta.block.end.flight-manual'
    +  '1':
    +    'name': 'punctuation.definition.block.flight-manual'
    +  '2':
    +    'name': 'keyword.note.flight-manual'
    +  '3':
    +    'name': 'punctuation.definition.block.flight-manual'
    +'name': 'meta.block.note.flight-manual'
    +

    Tip

    Tip: Get into the habit of providing punctuation scopes early on. It's much less effort than having to go back and rewriting all your patterns to support punctuation scopes when your grammar starts to get a bit longer!

    Awesome, we have our first multiline pattern! However, if you've been following along and playing around in your own .md file, you may have noticed that Flight Manual doesn't receive any scopes inside a note block. A begin/end block is essentially a subgrammar of its own: once it starts matching, it will only match its own subpatterns until the end pattern is reached. Since we haven't defined any subpatterns, then clearly nothing will be matched inside of a note block. Let's fix that!

    'begin': '({{)(#note)(}})'
    +'beginCaptures':
    +  '0': # The 0 capture contains the entire match
    +    'name': 'meta.block.start.flight-manual'
    +  '1':
    +    'name': 'punctuation.definition.block.flight-manual'
    +  '2':
    +    'name': 'keyword.note.flight-manual'
    +  '3':
    +    'name': 'punctuation.definition.block.flight-manual'
    +'end': '({{)(/note)(}})'
    +'endCaptures':
    +  '0':
    +    'name': 'meta.block.end.flight-manual'
    +  '1':
    +    'name': 'punctuation.definition.block.flight-manual'
    +  '2':
    +    'name': 'keyword.note.flight-manual'
    +  '3':
    +    'name': 'punctuation.definition.block.flight-manual'
    +'name': 'meta.block.note.flight-manual'
    +'patterns': [
    +  {
    +    'match': '\\\\b(Flight) (Manual)\\\\b'
    +    'name': 'entity.other.flight-manual'
    +    'captures':
    +      '1':
    +        'name': 'keyword.other.flight.flight-manual'
    +      '2':
    +        'name': 'keyword.other.manual.flight-manual'
    +  }
    +]
    +

    There. With the patterns block, Flight Manual should now receive the proper scopes.

    Repositories and the Include keyword, or how to avoid duplication

    At this point, note blocks are looking pretty nice, as is the Flight Manual keyword, but the rest of the file is noticeably lacking any form of Markdown syntax highlighting. Is there a way to include the GitHub-Flavored Markdown grammar without copying and pasting everything over? This is where the include keyword comes in. include allows you to include other patterns, even from other grammars! language-gfm's scopeName is source.gfm, so let's include that. Our patterns block should now look like the following:

    'patterns': [
    +  {
    +    'include': 'source.gfm'
    +  }
    +  {
    +    # Flight Manual pattern
    +  }
    +  {
    +    # Note begin/end pattern
    +  }
    +]
    +

    However, including source.gfm has led to another problem: note blocks still don't have any Markdown highlighting! The quick fix would be to add the include pattern to the note's pattern block as well, but now we're duplicating two patterns. You can imagine that as this grammar grows it'll quickly become inefficient to keep copying each new global pattern over to the note pattern as well. Therefore, include helpfully recognizes the special $self scope. $self automatically includes all the top-level patterns of the current grammar. The note block can then be simplified to the following:

    'begin': '({{)(#note)(}})'
    +# beginCaptures
    +'end': '({{)(/note)(}})'
    +# endCaptures
    +'name': 'meta.block.note.flight-manual'
    +'patterns': [
    +  {
    +    'include': '$self'
    +  }
    +]
    +

    Where to Go from Here

    There are several good resources out there that help when writing a grammar. The following is a list of some particularly useful ones (some have been linked to in the sections above as well).

    `,19),ms={href:"https://gist.github.com/DamnedScholar/622926bcd222eb1ddc483d12103fd315",target:"_blank",rel:"noopener noreferrer"},gs={href:"https://gist.github.com/Aerijo/b8c82d647db783187804e86fa0a604a1",target:"_blank",rel:"noopener noreferrer"},ks=e("li",null,"http://www.apeth.com/nonblog/stories/textmatebundle.html. A blog of a programmer's experience writing a grammar package for TextMate.",-1),vs={href:"https://github.com/kkos/oniguruma/blob/master/doc/RE",target:"_blank",rel:"noopener noreferrer"},bs={href:"http://manual.macromates.com/en/language_grammars.html",target:"_blank",rel:"noopener noreferrer"},fs={href:"https://github.com/atom/first-mate",target:"_blank",rel:"noopener noreferrer"},ys=e("code",null,"first-mate",-1),ws={href:"https://github.com/atom/language-python",target:"_blank",rel:"noopener noreferrer"},_s={href:"https://github.com/atom/language-javascript",target:"_blank",rel:"noopener noreferrer"},xs={href:"https://github.com/atom/language-html",target:"_blank",rel:"noopener noreferrer"},qs={href:"https://github.com/atom?utf8=%E2%9C%93&q=language&type=source&language=",target:"_blank",rel:"noopener noreferrer"},As=e("h3",{id:"publishing",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#publishing","aria-hidden":"true"},"#"),n(" Publishing")],-1),Ts=e("code",null,"apm",-1),Cs=e("code",null,"apm",-1),Ss=e("h4",{id:"prepare-your-package",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#prepare-your-package","aria-hidden":"true"},"#"),n(" Prepare Your Package")],-1),Is=e("p",null,"There are a few things you should double check before publishing:",-1),js=t("
  • Your package.json file has name, description, and repository fields.
  • Your package.json file has a version field with a value of "0.0.0".
  • Your package.json file has an engines field that contains an entry for Atom such as: "engines": {"atom": ">=1.0.0 <2.0.0"}.
  • Your package has a README.md file at the root.
  • Your repository URL in the package.json file is the same as the URL of your repository.
  • ",5),Ps={href:"https://github.com",target:"_blank",rel:"noopener noreferrer"},Rs={href:"https://help.github.com/articles/importing-a-git-repository-using-the-command-line/",target:"_blank",rel:"noopener noreferrer"},Ns=e("h4",{id:"publish-your-package",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#publish-your-package","aria-hidden":"true"},"#"),n(" Publish Your Package")],-1),Es={href:"https://atom.io/packages",target:"_blank",rel:"noopener noreferrer"},Us=e("code",null,"https://atom.io/packages/your-package-name",-1),Ms=e("p",null,[n("Now let's review what the "),e("code",null,"apm publish"),n(" command does:")],-1),Ws=e("li",null,"Registers the package name on atom.io if it is being published for the first time.",-1),Fs=e("li",null,[n("Updates the "),e("code",null,"version"),n(" field in the "),e("code",null,"package.json"),n(" file and commits it.")],-1),Ds={href:"https://git-scm.com/book/en/Git-Basics-Tagging",target:"_blank",rel:"noopener noreferrer"},Ls=e("li",null,"Pushes the tag and current branch up to GitHub.",-1),Os=e("li",null,"Updates atom.io with the new version being published.",-1),zs=t(`

    Now run the following commands to publish your package:

    $ cd path-to-your-package
    +$ apm publish minor
    +
    `,2),Ys=e("code",null,"apm publish",-1),Vs={href:"https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/",target:"_blank",rel:"noopener noreferrer"},$s={href:"https://en.wikipedia.org/wiki/Keychain_(Apple)",target:"_blank",rel:"noopener noreferrer"},Gs=t(`

    Your package is now published and available on atom.io. Head on over to https://atom.io/packages/your-package-name to see your package's page.

    With apm publish, you can bump the version and publish by using

    $ apm publish <em>version-type</em>
    +

    where version-type can be major, minor and patch.

    The major option to the publish command tells apm to increment the first number of the version before publishing so the published version will be 1.0.0 and the Git tag created will be v1.0.0.

    The minor option to the publish command tells apm to increment the second number of the version before publishing so the published version will be 0.1.0 and the Git tag created will be v0.1.0.

    The patch option to the publish command tells apm to increment the third number of the version before publishing so the published version will be 0.0.1 and the Git tag created will be v0.0.1.

    `,7),Hs=e("code",null,"major",-1),Bs=e("code",null,"minor",-1),Js=e("code",null,"patch",-1),Ks={href:"https://semver.org",target:"_blank",rel:"noopener noreferrer"},Xs=e("p",null,[n("You can also run "),e("code",null,"apm help publish"),n(" to see all the available options and "),e("code",null,"apm help"),n(" to see all the other available commands.")],-1),Zs=e("h3",{id:"iconography",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#iconography","aria-hidden":"true"},"#"),n(" Iconography")],-1),Qs={href:"https://github.com/github/octicons/tree/v4.4.0",target:"_blank",rel:"noopener noreferrer"},et=e("blockquote",null,[e("p",null,[n("NOTE: Some older icons from version "),e("code",null,"2.1.2"),n(" are still kept for backwards compatibility.")])],-1),nt=e("h4",{id:"overview",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#overview","aria-hidden":"true"},"#"),n(" Overview")],-1),at=t('

    Octicons in the Styleguide

    Usage

    Octicons can be added with simple CSS classes in your markup. Prefix the icon names with icon icon-.

    As an example, to add a monitor icon (device-desktop), use the icon icon-device-desktop classes:

    <span class="icon icon-device-desktop"></span>
    +

    Size

    Octicons look best with a font-size of 16px. It's already used as the default, so you don't need to worry about it. In case you prefer a different icon size, try to use multiples of 16 (32px, 48px etc.) for the sharpest result. Sizes in between are ok too, but might look a bit blurry for icons with straight lines.

    Usability

    `,8),st={href:"https://atom.io/docs/api/latest/TooltipManager",target:"_blank",rel:"noopener noreferrer"},tt=e("code",null,'title="label"',-1),ot=e("h3",{id:"debugging",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#debugging","aria-hidden":"true"},"#"),n(" Debugging")],-1),it={href:"https://github.com/atom/atom/blob/master/CONTRIBUTING.md#submitting-issues",target:"_blank",rel:"noopener noreferrer"},lt=t(`

    Update to the Latest Version

    You might be running into an issue which was already fixed in a more recent version of Atom than the one you're using.

    If you're using a released version, check which version of Atom you're using:

    $ atom --version
    +> Atom    : 1.8.0
    +> Electron: 0.36.8
    +> Chrome  : 47.0.2526.110
    +> Node    : 5.1.1
    +
    `,4),ct={href:"https://github.com/atom/atom/releases/latest",target:"_blank",rel:"noopener noreferrer"},rt={href:"https://atom.io",target:"_blank",rel:"noopener noreferrer"},pt={href:"https://github.com/atom/atom/releases/latest",target:"_blank",rel:"noopener noreferrer"},dt={href:"https://github.com/atom/about",target:"_blank",rel:"noopener noreferrer"},ut=e("em",null,"Atom > About",-1),ht={href:"https://github.com/atom/about",target:"_blank",rel:"noopener noreferrer"},mt=e("em",null,"Help > About",-1),gt={href:"https://github.com/atom/atom#building",target:"_blank",rel:"noopener noreferrer"},kt=t(`

    Using Safe Mode

    A large part of Atom's functionality comes from packages you can install. Atom will also execute the code in your init script on startup. In some cases, these packages and the code in the init script might be causing unexpected behavior, problems, or performance issues.

    To determine if that is happening, start Atom from the terminal in safe mode:

    $ atom --safe
    +

    This starts Atom, but does not load packages from ~/.atom/packages or ~/.atom/dev/packages and disables loading of your init script. If you can no longer reproduce the problem in safe mode, it's likely it was caused by one of the packages or the init script.

    If removing or commenting out all content from the init script and starting Atom normally still produces the error, then try figuring out which package is causing trouble. Start Atom normally again and open the Settings View with Cmd+,Ctrl+,. Since the Settings View allows you to disable each installed package, you can disable packages one by one until you can no longer reproduce the issue. Restart Atom or reload Atom with Alt+Cmd+Ctrl+LCtrl+Shift+F5 after you disable each package to make sure it's completely gone.

    When you find the problematic package, you can disable or uninstall the package. We strongly recommend creating an issue on the package's GitHub repository.

    Clearing Saved State

    Atom saves a number of things about your environment when you exit in order to restore Atom to the same configuration when you next launch the program. In some cases the state that gets saved can be something undesirable that prevents Atom from working properly. In these cases, you may want to clear the state that Atom has saved.

    DANGER

    :rotatinglight: Danger: Clearing the saved state permanently destroys any state that Atom has saved _across all projects. This includes unsaved changes to files you may have been editing in all projects. This is a destructive action.

    Clearing the saved state can be done by opening a terminal and executing:

    $ atom --clear-window-state
    +

    Reset to Factory Defaults

    In some cases, you may want to reset Atom to "factory defaults", in other words clear all of your configuration and remove all packages. This can easily be done by opening a terminal and executing:

    `,14),vt=e("div",{class:"language-command-line ext-command-line line-numbers-mode"},[e("pre",{class:"language-command-line"},[e("code",null,`$ mv ~/.atom ~/.atom-backup +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"})])],-1),bt=e("div",{class:"language-command-line ext-command-line line-numbers-mode"},[e("pre",{class:"language-command-line"},[e("code",null,`$ mv ~/.atom ~/.atom-backup +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"})])],-1),ft=e("div",{class:"language-command-line ext-command-line line-numbers-mode"},[e("pre",{class:"language-command-line"},[e("code",null,`$ rename %USERPROFILE%\\.atom .atom-backup +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"})])],-1),yt=t(`

    Once that is complete, you can launch Atom as normal. Everything will be just as if you first installed Atom.

    Tip

    Tip: The command given above doesn't delete the old configuration, just puts it somewhere that Atom can't find it. If there are pieces of the old configuration you want to retrieve, you can find them in the ~/.atom-backup%USERPROFILE%\\.atom-backup directory.

    Check for Linked Packages

    If you develop or contribute to Atom packages, there may be left-over packages linked to your ~/.atom/packages or ~/.atom/dev/packages directories. You can use the apm links command to list all linked packages:

    $ apm links
    +> /Users/octocat/.atom/dev/packages (0)
    +> \u2514\u2500\u2500 (no links)
    +> /Users/octocat/.atom/packages (1)
    +> \u2514\u2500\u2500 color-picker -> /Users/octocat/github/color-picker
    +

    You can remove links using the apm unlink command:

    $ apm unlink color-picker
    +> Unlinking /Users/octocat/.atom/packages/color-picker \u2713
    +

    See apm links --help and apm unlink --help for more information on these commands.

    Tip

    Tip: You can also use apm unlink --all to easily unlink all packages and themes.

    Check for Incompatible Packages

    `,10),wt={href:"https://github.com/atom/incompatible-packages",target:"_blank",rel:"noopener noreferrer"},_t=e("p",null,[e("img",{src:x,alt:"Incompatible Packages Status Bar Indicator",title:"Incompatible Packages Status Bar Indicator"})],-1),xt=e("p",null,"If you see this indicator, click it and follow the instructions.",-1),qt=e("h4",{id:"check-atom-and-package-settings",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#check-atom-and-package-settings","aria-hidden":"true"},"#"),n(" Check Atom and Package Settings")],-1),At=e("p",null,"In some cases, unexpected behavior might be caused by settings in Atom or in one of the packages.",-1),Tt={href:"https://github.com/atom/settings-view",target:"_blank",rel:"noopener noreferrer"},Ct=e("kbd",{class:"platform-mac"},"Cmd+,",-1),St=e("kbd",{class:"platform-windows platform-linux"},"Ctrl+,",-1),It=e("span",{class:"platform-mac"},[e("em",null,"Atom > Preferences")],-1),jt=e("span",{class:"platform-windows"},[e("em",null,"File > Preferences")],-1),Pt=e("span",{class:"platform-linux"},[e("em",null,"Edit > Preferences")],-1),Rt={href:"https://github.com/atom/command-palette",target:"_blank",rel:"noopener noreferrer"},Nt=e("p",null,[e("img",{src:q,alt:"Settings View"})],-1),Et=e("p",null,"Some of these options are also available on a per-language basis which means that they may be different for specific languages, for example JavaScript or Python. To check the per-language settings, open the settings for the language package under the Packages tab in the Settings View, for example the language-javascript or language-python package.",-1),Ut={href:"https://atom.io/packages/wrap-guide",target:"_blank",rel:"noopener noreferrer"},Mt={href:"https://atom.io/packages/whitespace",target:"_blank",rel:"noopener noreferrer"},Wt=e("p",null,[e("img",{src:A,alt:"Package Settings"})],-1),Ft=e("h4",{id:"check-your-configuration",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#check-your-configuration","aria-hidden":"true"},"#"),n(" Check Your Configuration")],-1),Dt=e("h4",{id:"check-your-keybindings",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#check-your-keybindings","aria-hidden":"true"},"#"),n(" Check Your Keybindings")],-1),Lt={href:"https://atom.io/packages/keybinding-resolver",target:"_blank",rel:"noopener noreferrer"},Ot=t('

    Show the keybinding resolver with Cmd+.Ctrl+. or with "Keybinding Resolver: Show" from the Command palette. With the Keybinding Resolver shown, press a key combination:

    Keybinding Resolver

    The Keybinding Resolver shows you a list of keybindings that exist for the key combination, where each item in the list has the following:

    • the command for the keybinding
    • the CSS selector used to define the context in which the keybinding is valid
    • the file in which the keybinding is defined

    The keybindings are listed in two colors. All the keybindings that are matched but not executed are shown in gray. The one that is executed, if any, is shown in green. If the command you wanted to trigger isn't listed, then a keybinding for that command hasn't been loaded.

    ',5),zt=e("ul",null,[e("li",null,[e("p",null,"The key combination was not used in the context defined by the keybinding's selector"),e("p",null,[n("For example, you can't trigger the keybinding for the "),e("code",null,"tree-view:add-file"),n(" command if the Tree View is not focused.")])]),e("li",null,[e("p",null,"There is another keybinding that took precedence"),e("p",null,"This often happens when you install a package which defines keybindings that conflict with existing keybindings. If the package's keybindings have selectors with higher specificity or were loaded later, they'll have priority over existing ones.")])],-1),Yt=e("code",null,"keymap.cson",-1),Vt={href:"https://github.com/atom/atom/discussions",target:"_blank",rel:"noopener noreferrer"},$t=e("h4",{id:"check-font-rendering-issues",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#check-font-rendering-issues","aria-hidden":"true"},"#"),n(" Check Font Rendering Issues")],-1),Gt=e("kbd",{class:"platform-mac"},"Alt+Cmd+I",-1),Ht=e("kbd",{class:"platform-windows platform-linux"},"Ctrl+Shift+I",-1),Bt={href:"https://developers.google.com/web/tools/chrome-devtools/inspect-styles/",target:"_blank",rel:"noopener noreferrer"},Jt=t('

    Fonts In Use

    Check for Errors in the Developer Tools

    When an unexpected error occurs in Atom, you will normally see a red notification which provides details about the error and allows you to create an issue on the right repository:

    Exception Notification

    Not all errors are logged with a notification so if you suspect you're experiencing an error but there's no notification, you can also look for errors in the developer tools Console tab. To access the Console tab, press Alt-Cmd-ICtrl-Shift-I to open developer tools and then click the Console tab:

    DevTools Error

    If there are multiple errors, you can scroll down to the bottom of the panel to see the most recent error. Or while reproducing an error, you can right click in the Console tab panel, select Clear console to remove all Console output, and then reproduce the error to see what errors are logged to the Console tab.

    Note

    Note: When running in Dev Mode, the developer tools are automatically shown with the error logged in the Console tab.

    Find Crash Logs

    ',9),Kt=e("p",null,"When Atom crashes, it should write a core dump if system settings permit. In order to find whether the core dump is written and to where, consult the documentation for your distribution of Linux. Once you have the core dump, you can save it to send in later if it is needed for debugging.",-1),Xt=e("p",null,[n("When Atom crashes, you will find a crash dump in Console.app. You can launch Console.app using Spotlight or you can find it in "),e("code",null,"/Applications/Utilities/Console.app"),n(". Once you have launched the program, you can find the latest crash dump by following these instructions:")],-1),Zt=e("ol",null,[e("li",null,'Click "User Reports" in the left-most column'),e("li",null,[n("Find the latest entry in the middle column that starts with "),e("code",null,"Atom"),n(" and ends with "),e("code",null,".crash")])],-1),Qt=e("p",null,"Once you have the crash dump, you can save it to send in later if it is needed for debugging.",-1),eo=e("p",null,[n("When Atom crashes, you will find a crash dump inside your "),e("code",null,"%TEMP%\\Atom Crashes"),n(" directory. It will be the newest file with the "),e("code",null,".dmp"),n(" extension. Once you have the crash dump, you can save it to send in later if it is needed for debugging.")],-1),no=e("h4",{id:"diagnose-startup-performance",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#diagnose-startup-performance","aria-hidden":"true"},"#"),n(" Diagnose Startup Performance")],-1),ao={href:"https://github.com/atom/timecop",target:"_blank",rel:"noopener noreferrer"},so=e("p",null,[e("img",{src:T,alt:"Timecop"})],-1),to=e("p",null,"Timecop displays the following information:",-1),oo=e("ul",null,[e("li",null,"Atom startup times"),e("li",null,"File compilation times"),e("li",null,"Package loading and activation times"),e("li",null,"Theme loading and activation times")],-1),io=e("p",null,"If a specific package has high load or activation times, you might consider reporting an Issue to the maintainers. You can also disable the package to potentially improve future startup times.",-1),lo=e("h4",{id:"diagnose-runtime-performance",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#diagnose-runtime-performance","aria-hidden":"true"},"#"),n(" Diagnose Runtime Performance")],-1),co={href:"https://github.com/atom/atom/blob/master/CONTRIBUTING.md#submitting-issues",target:"_blank",rel:"noopener noreferrer"},ro=t('

    To run a profile, open the Developer Tools with Alt+Cmd+ICtrl+Shift+I. From there:

    1. Click the Profiles tab
    2. Select "Collect JavaScript CPU Profile"
    3. Click "Start"

    DevTools Profiler

    Once that is done, then perform the slow action to capture a recording. When finished, click "Stop". Switch to the "Chart" view, and a graph of the recorded actions will appear. You can save and post the profile data by clicking "Save" next to the profile's name in the left panel.

    DevTools Profiler

    ',5),po={href:"https://developer.chrome.com/devtools/docs/cpu-profiling",target:"_blank",rel:"noopener noreferrer"},uo=t(`

    Profiling Startup Performance

    If the time for loading the window looks high, you can create a CPU profile for that period using the --profile-startup command line flag when starting Atom:

    $ atom --profile-startup .
    +

    This will automatically capture a CPU profile as Atom is loading and open the Developer Tools once Atom loads. From there:

    1. Click the Profiles tab in the Developer Tools
    2. Select the "startup" profile
    3. Click the "Save" link for the startup profile

    You can then include the startup profile in any Issue you report.

    Check Your Build Tools

    If you are having issues installing a package using apm install, this could be because the package has dependencies on libraries that contain native code. This means you will need to have a C++ compiler and Python installed to be able to install it. You can run apm install --check to see if the Atom package manager can build native code on your machine.

    `,8),ho={href:"https://github.com/atom/atom/tree/master/docs/build-instructions",target:"_blank",rel:"noopener noreferrer"},mo=t(`

    Check if your GPU is causing the problem

    If you encounter flickering or other rendering issues, you can stop Atom from using your Graphics Processing Unit (GPU) with the --disable-gpu Chromium flag to see if the fault lies with your GPU:

    $ atom --disable-gpu
    +

    Chromium (and thus Atom) normally uses the GPU to accelerate drawing parts of the interface. --disable-gpu tells Atom to not even attempt to do this, and just use the CPU for rendering everything. This means that the parts of the interface that would normally be accelerated using the GPU will instead take slightly longer and render on the CPU. This likely won't make a noticeable difference, but does slightly increase the battery usage as the CPU has to work harder to do the things the GPU is optimized for.

    Two other Chromium flags that are useful for debugging are --enable-gpu-rasterization and --force-gpu-rasterization:

    $ atom --enable-gpu-rasterization --force-gpu-rasterization
    +

    --enable-gpu-rasterization allows other commands to determine how a layer tile (graphics) should be drawn and --force-gpu-rasterization determines that the Skia GPU backend should be used for drawing layer tiles (only valid with GPU accelerated compositing).

    Be sure to use Chromium flags at the end of the terminal call if you want to use other Atom flags as they will not be executed after the Chromium flags e.g.:

    $ atom --safe --enable-gpu-rasterization --force-gpu-rasterization
    +

    Writing Specs

    We've looked at and written a few specs through the examples already. Now it's time to take a closer look at the spec framework itself. How exactly do you write tests in Atom?

    `,11),go={href:"https://jasmine.github.io/archives/1.3/introduction",target:"_blank",rel:"noopener noreferrer"},ko=e("h4",{id:"create-a-new-spec",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#create-a-new-spec","aria-hidden":"true"},"#"),n(" Create a New Spec")],-1),vo={href:"https://github.com/atom/atom/tree/master/spec",target:"_blank",rel:"noopener noreferrer"},bo={href:"https://github.com/atom/markdown-preview/tree/master/spec",target:"_blank",rel:"noopener noreferrer"},fo=e("code",null,"spec",-1),yo=t(`
    Create a Spec File

    Spec files must end with -spec so add sample-spec.coffee to the spec directory.

    Add One or More describe Methods

    The describe method takes two arguments, a description and a function. If the description explains a behavior it typically begins with when; if it is more like a unit test it begins with the method name.

    describe("when a test is written", function () {
    +	// contents
    +});
    +

    or

    describe("Editor::moveUp", function () {
    +	// contents
    +});
    +
    Add One or More it Methods

    The it method also takes two arguments, a description and a function. Try and make the description flow with the it method. For example, a description of "this should work" doesn't read well as "it this should work". But a description of "should work" sounds great as "it should work".

    describe("when a test is written", function () {
    +	it("has some expectations that should pass", function () {
    +		// Expectations
    +	});
    +});
    +
    Add One or More Expectations
    `,11),wo={href:"https://jasmine.github.io/archives/1.3/introduction#section-Expectations",target:"_blank",rel:"noopener noreferrer"},_o=t(`
    describe("when a test is written", function () {
    +	it("has some expectations that should pass", function () {
    +		expect("apples").toEqual("apples");
    +		expect("oranges").not.toEqual("apples");
    +	});
    +});
    +
    Custom Matchers

    In addition to the Jasmine's built-in matchers, Atom includes the following:

    `,3),xo={href:"https://github.com/velesin/jasmine-jquery",target:"_blank",rel:"noopener noreferrer"},qo=t("
  • The toBeInstanceOf matcher is for the instanceof operator
  • The toHaveLength matcher compares against the .length property
  • The toExistOnDisk matcher checks if the file exists in the filesystem
  • The toHaveFocus matcher checks if the element currently has focus
  • The toShow matcher tests if the element is visible in the dom
  • ",5),Ao={href:"https://github.com/atom/atom/blob/master/spec/spec-helper.coffee",target:"_blank",rel:"noopener noreferrer"},To=t(`

    Asynchronous Specs

    Writing Asynchronous specs can be tricky at first. Some examples.

    Promises

    Working with promises is rather easy in Atom. You can use our waitsForPromise function.

    describe("when we open a file", function () {
    +	it("should be opened in an editor", function () {
    +		waitsForPromise(function () {
    +			atom.workspace
    +				.open("c.coffee")
    +				.then((editor) => expect(editor.getPath()).toContain("c.coffee"));
    +		});
    +	});
    +});
    +

    This method can be used in the describe, it, beforeEach and afterEach functions.

    describe("when we open a file", function () {
    +	beforeEach(function () {
    +		waitsForPromise(() => atom.workspace.open("c.coffee"));
    +	});
    +
    +	it("should be opened in an editor", function () {
    +		expect(atom.workspace.getActiveTextEditor().getPath()).toContain(
    +			"c.coffee"
    +		);
    +	});
    +});
    +

    If you need to wait for multiple promises use a new waitsForPromise function for each promise. (Caution: Without beforeEach this example will fail!)

    describe("waiting for the packages to load", function () {
    +	beforeEach(function () {
    +		waitsForPromise(() => atom.workspace.open("sample.js"));
    +
    +		waitsForPromise(() => atom.packages.activatePackage("tabs"));
    +
    +		waitsForPromise(() => atom.packages.activatePackage("tree-view"));
    +	});
    +
    +	it("should have waited long enough", function () {
    +		expect(atom.packages.isPackageActive("tabs")).toBe(true);
    +		expect(atom.packages.isPackageActive("tree-view")).toBe(true);
    +	});
    +});
    +

    waitsForPromise can take an additional object argument before the function. The object can have the following properties:

    • shouldReject Whether the promise should reject or resolve (default: false)
    • timeout The amount of time (in ms) to wait for the promise to be resolved or rejected (default: process.env.CI ? 60000 : 5000)
    • label The label to display if promise times out (default: 'promise to be resolved or rejected')
    describe("when we open a file", function () {
    +	it("should be opened in an editor", function () {
    +		waitsForPromise(
    +			{
    +				shouldReject: false,
    +				timeout: 5000,
    +				label: "promise to be resolved or rejected",
    +			},
    +			() =>
    +				atom.workspace
    +					.open("c.coffee")
    +					.then((editor) => expect(editor.getPath()).toContain("c.coffee"))
    +		);
    +	});
    +});
    +
    Asynchronous Functions with Callbacks

    Specs for asynchronous functions can be done using the waitsFor and runs functions. A simple example.

    describe("fs.readdir(path, cb)", function () {
    +	it("is async", function () {
    +		const spy = jasmine.createSpy("fs.readdirSpy");
    +		fs.readdir("/tmp/example", spy);
    +
    +		waitsFor(() => spy.callCount > 0);
    +
    +		runs(function () {
    +			const exp = [null, ["example.coffee"]];
    +
    +			expect(spy.mostRecentCall.args).toEqual(exp);
    +			expect(spy).toHaveBeenCalledWith(null, ["example.coffee"]);
    +		});
    +	});
    +});
    +
    `,15),Co={href:"https://jasmine.github.io/archives/1.3/introduction#section-Asynchronous_Support",target:"_blank",rel:"noopener noreferrer"},So=t(`

    Running Specs

    Most of the time you'll want to run specs by triggering the window:run-package-specs command. This command is not only to run package specs, it can also be used to run Atom core specs when working on Atom itself. This will run all the specs in the current project's spec directory.

    To run a limited subset of specs use the fdescribe or fit methods. You can use those to focus a single spec or several specs. Modified from the example above, focusing an individual spec looks like this:

    describe("when a test is written", function () {
    +	fit("has some expectations that should pass", function () {
    +		expect("apples").toEqual("apples");
    +		expect("oranges").not.toEqual("apples");
    +	});
    +});
    +
    Running on CI
    `,5),Io={href:"https://blog.atom.io/2014/04/25/ci-for-your-packages.html",target:"_blank",rel:"noopener noreferrer"},jo={href:"http://blog.atom.io/2014/07/28/windows-ci-for-your-packages.html",target:"_blank",rel:"noopener noreferrer"},Po=t(`
    Running via the Command Line

    To run tests on the command line, run Atom with the --test flag followed by one or more paths to test files or directories. You can also specify a --timeout option, which will force-terminate your tests after a certain number of seconds have passed.

    atom --test --timeout 60 ./test/test-1.js ./test/test-2.js
    +

    Customizing your test runner

    WARNING

    Warning: This API is available as of 1.2.0-beta0, and it is experimental and subject to change. Test runner authors should be prepared to test their code against future beta releases until it stabilizes.

    By default, package tests are run with Jasmine 1.3, which is outdated but can't be changed for compatibility reasons. You can specify your own custom test runner by including an atomTestRunner field in your package.json. Atom will require whatever module you specify in this field, so you can use a relative path or the name of a module in your package's dependencies.

    Your test runner module must export a single function, which Atom will call within a new window to run your package's tests. Your function will be called with the following parameters:

    • testPaths An array of paths to tests to run. Could be paths to files or directories.
    • buildAtomEnvironment A function that can be called to construct an instance of the atom global. No atom global will be explicitly assigned, but you can assign one in your runner if desired. This function should be called with the following parameters:
      • applicationDelegate An object responsible for Atom's interaction with the browser process and host OS. Use buildDefaultApplicationDelegate for a default instance. You can override specific methods on this object to prevent or test these interactions.
      • window A window global.
      • document A document global.
      • configDirPath A path to the configuration directory (usually ~/.atom).
      • enablePersistence A boolean indicating whether the Atom environment should save or load state from the file system. You probably want this to be false.
    • buildDefaultApplicationDelegate A function that builds a default instance of the application delegate, suitable to be passed as the applicationDelegate parameter to buildAtomEnvironment.
    • logFile An optional path to a log file to which test output should be logged.
    • headless A boolean indicating whether or not the tests are being run from the command line via atom --test.
    • legacyTestRunner This function can be invoked to run the legacy Jasmine runner, giving your package a chance to transition to a new test runner while maintaining a subset of its tests in the old environment.

    Your function should return a promise that resolves to an exit code when your tests are finish running. This exit code will be returned when running your tests via the command line.

    Handling URIs

    Beginning in Atom 1.23, packages have the ability to handle special URIs triggered from the system; for example, a package named my-package can register itself to handle any URI starting with atom://my-package/.

    WARNING

    Warning: Handling URIs triggered from other applications, like a web browser, is a powerful tool, but also one that can be jarring. You should shape your package's user experience to handle this well. In general, you should avoid taking direct action on behalf of a user. For example, a URI handler that immediately installs a package is too invasive, but a URI handler that shows the package's pane in the settings view is useful. A URI handler that begins to clone a repo is overly aggressive, but a URI handler that prompts the user to clone a repo is okay.

    Any package with a URI handler that we feel violates this guideline is subject to removal from the Atom package registry at our discretion.

    Modifying your package.json

    The first step to handling URIs from your package is to modify its package.json file. You should add a new key called uriHandler, and its value should be an object.

    The uriHandler object must contain a key called method with a string value that tells Atom which method in your package to call when a URI needs to be handled. The object can optionally include a key called deferActivation which can be set to the boolean false to prevent Atom from deferring activation of your package \u2014\xA0see more below.

    For example, if we want our package my-package to handle URIs with a method on our package's main module called handleURI, we could add the following to our package.json:

    "uriHandler": {
    +  "method": "handleURI"
    +}
    +

    Modifying your Main Module

    `,18),Ro=e("code",null,"atom://my-package/",-1),No=e("code",null,"handleURI",-1),Eo={href:"https://nodejs.org/api/url.html#url_url_parse_urlstring_parsequerystring_slashesdenotehost",target:"_blank",rel:"noopener noreferrer"},Uo=e("code",null,"url.parse(uri, true)",-1),Mo=t(`

    Here's a sample package, written in JavaScript, that handles URIs with the package.json configuration we saw above.

    export default {
    +	activate() {
    +		// normal activation code here
    +	},
    +
    +	handleURI(parsedUri) {
    +		console.log(parsedUri);
    +	},
    +};
    +

    When Atom handles, for example, the URI atom://my-package/my/test/url?value=42&other=false, the package would log out something like the following:

    {
    +  protocol: 'atom:',
    +  slashes: true,
    +  auth: null,
    +  host: 'my-package',
    +  port: null,
    +  hostname: 'my-package',
    +  hash: null,
    +  search: '?value=true&other=false',
    +  query: { value: '42', other: 'false' },
    +  pathname: '/my/test/url',
    +  path: '/my/test/url?value=true&other=false',
    +  href: 'atom://my-package/my/test/url?value=true&other=false'
    +}
    +

    Notice that the query string arguments are available in the query property, but are strings \u2014\xA0you'll have to convert to other native types yourself.

    Controlling Activation Deferral

    For performance reasons, adding a uriHandler entry to your package's package.json will enable deferred activation. This means that Atom will not activate your package until it has a URI for it to handle \u2014\xA0it will then activate your package and then immediately call the URI handler method. If you want to disable the deferred activation, ensuring your package is activated upon startup, you can add "deferActivation": false to the URI handler config. For example,

    "uriHandler": {
    +  "method": "handleURI",
    +  "deferActivation": false
    +}
    +

    Before doing this, make sure your package actually needs to be activated immediately \u2014\xA0disabling deferred activation means Atom takes longer to start since it has to activate all packages without deferred activation.

    Linux Support

    Because URI handling is different across operating systems and distributions, there is no built-in URI handler support for Atom on Linux. If you want to configure URI handling on your system yourself, then you should configure atom: protocol URI's to trigger atom with the --uri-handler flag; for example, the URI atom://test/uri should launch Atom via atom --uri-handler atom://test/uri.

    Core URIs

    Atom provides a core URI to handle opening files with the syntax atom://core/open/file?filename=<filepath>&line=<line>&column=<col>

    Cross-Platform Compatibility

    Atom runs on a number of platforms and while Electron and Node take care of many of the details there are still some considerations to ensure your package works on other operating systems.

    File symlinks can be used on Windows by non-Administrators by specifying 'junction' as the type (this argument is ignored on macOS & Linux).

    Also consider:

    • Symlinks committed to Git will not checkout correctly on Windows - dynamically create what you need with fs.symlink instead
    • Symlinked directories are only available to Administrators on Windows - avoid a dependency on them

    Filenames

    • Reserved filenames on Windows are com1-com9, lpt1-lpt9, con, nul, aux and prn (regardless of extension, e.g. prn.txt is disallowed)
    • Reserved characters on Windows are ? \\ / < > ? % | : " so avoid where possible
    • Names with spaces when passed to the command line;
      • Windows requires you surround the path with double quotes e.g. "c:\\my test"
      • macOS and Linux require a backslash before each space e.g. /my\\ test

    File paths

    • Windows uses \\ although some tools and PowerShell allow / too
    • macOS and Linux use /

    You can dynamically find out what your platform uses with path.sep or better yet use the node path library functions such as join and normalize which automatically take care of this.

    Windows supports up to 250 characters for a path - avoid deeply nested directory structures

    Paths are not URLs

    URL parsing routines should not be used on file paths. While they initially look like a relative path it will fail in a number of scenarios on all platforms.

    • Various characters are misinterpreted, e.g. ? as query string, # as a fragment identifier
    • Windows drive specifiers are incorrectly parsed as a protocol

    If you need to use a path for a URL use the file: protocol with an absolute path instead to ensure drive letters and slashes are appropriately addressed, e.g. file:///c|/test/pic.png

    fs.stat on directories

    The fs.stat function does not return the size of the contents of a directory but rather the allocation size of the directory itself. This returns 0 on Windows and 1024 on macOS and so should not be relied upon.

    path.relative can't traverse drives

    • On a macOS or Linux system path.relative can be used to calculate a relative path to traverse between any two given paths.
    • On Windows this is not always possible as it can contain multiple absolute roots, e.g. c:\\ and d:\\

    Rapid file operations

    `,34),Wo={href:"https://www.npmjs.com/package/rimraf",target:"_blank",rel:"noopener noreferrer"},Fo=t(`

    Line endings

    • Windows uses CRLF
    • macOS and Linux use LF
    • Git on Windows often has autocrlf set which automatically converts between the two

    If you are writing specs that use text file fixtures consider that this will interfere with file lengths, hash codes and direct text comparisons. It will also change the Atom selection length by 1 character per line.

    If you have spec fixtures that are text files you may want to tell Git to force LF, CRLF or not convert them by specifying the paths in .gitattributes e.g.

    spec/fixtures/always-crlf.txt eol=crlf
    +spec/fixtures/always-lf.txt eol=lf
    +spec/fixtures/leave-as-is.txt -text
    +

    Converting from TextMate

    `,6),Do={href:"https://macromates.com",target:"_blank",rel:"noopener noreferrer"},Lo=e("h4",{id:"converting-a-textmate-grammar-bundle",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#converting-a-textmate-grammar-bundle","aria-hidden":"true"},"#"),n(" Converting a TextMate Grammar Bundle")],-1),Oo=e("p",null,"Converting a TextMate bundle will allow you to use its editor preferences, snippets, and colorization inside Atom.",-1),zo={href:"https://en.wikipedia.org/wiki/R_(programming_language)",target:"_blank",rel:"noopener noreferrer"},Yo={href:"https://github.com/textmate",target:"_blank",rel:"noopener noreferrer"},Vo=t(`

    You can convert the R bundle with the following command:

    $ apm init --package language-r --convert https://github.com/textmate/r.tmbundle
    +

    You can now change directory into language-r to see the converted bundle. Once you link your package with the apm link command, your new package is ready to use. Launch Atom and open a .r file in the editor to see it in action!

    Converting a TextMate Syntax Theme

    `,4),$o={href:"https://macromates.com",target:"_blank",rel:"noopener noreferrer"},Go=e("h5",{id:"differences",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#differences","aria-hidden":"true"},"#"),n(" Differences")],-1),Ho={href:"https://en.wikipedia.org/wiki/Property_list",target:"_blank",rel:"noopener noreferrer"},Bo={href:"https://en.wikipedia.org/wiki/Cascading_Style_Sheets",target:"_blank",rel:"noopener noreferrer"},Jo={href:"http://lesscss.org",target:"_blank",rel:"noopener noreferrer"},Ko=e("p",null,"The utility that converts the theme first parses the theme's plist file and then creates comparable CSS rules and properties that will style Atom similarly.",-1),Xo=e("h5",{id:"convert-the-theme",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#convert-the-theme","aria-hidden":"true"},"#"),n(" Convert the Theme")],-1),Zo={href:"http://wiki.macromates.com/Themes/UserSubmittedThemes",target:"_blank",rel:"noopener noreferrer"},Qo=t(`

    Now, let's say you've downloaded the theme to ~/Downloads/MyTheme.tmTheme, you can convert the theme with the following command:

    $ apm init --theme my-theme --convert ~/Downloads/MyTheme.tmTheme
    +

    You can then change directory to my-theme to see the converted theme.

    Activate the Theme

    Once your theme is installed you can enable it by launching Atom and opening the Settings View with the Atom > PreferencesFile > PreferencesEdit > Preferences menu item. Then select the "Themes" tab on the left side navigation. Finally, choose "My Theme" from the "Syntax Theme" dropdown menu to enable your new theme.

    Your theme is now enabled, open an editor to see it in action!

    Hacking on Atom Core

    If you're hitting a bug in Atom or just want to experiment with adding a feature to the core of the system, you'll want to run Atom in Dev Mode with access to a local copy of the Atom source.

    Fork the atom/atom repository

    `,9),ei={href:"https://help.github.com/articles/fork-a-repo/",target:"_blank",rel:"noopener noreferrer"},ni=t(`

    Cloning and bootstrapping

    Once you've set up your fork of the atom/atom repository, you can clone it to your local machine:

    $ git clone git@github.com:<em>your-username</em>/atom.git
    +

    From there, you can navigate into the directory where you've cloned the Atom source code and run the bootstrap script to install all the required dependencies:

    `,4),ai=e("div",{class:"language-command-line ext-command-line line-numbers-mode"},[e("pre",{class:"language-command-line"},[e("code",null,`$ cd where-you-cloned-atom +$ script/bootstrap +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),si=e("div",{class:"language-command-line ext-command-line line-numbers-mode"},[e("pre",{class:"language-command-line"},[e("code",null,`$ cd where-you-cloned-atom +$ script/bootstrap +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),ti=e("div",{class:"language-command-line ext-command-line line-numbers-mode"},[e("pre",{class:"language-command-line"},[e("code",null,`$ cd where-you-cloned-atom +$ script\\bootstrap +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),oi=t(`

    Running in Development Mode

    Once you have a local copy of Atom cloned and bootstrapped, you can then run Atom in Development Mode. But first, if you cloned Atom to somewhere other than ~/github/atom%USERPROFILE%\\github\\atom you will need to set the ATOM_DEV_RESOURCE_PATH environment variable to point to the folder in which you cloned Atom. To run Atom in Dev Mode, use the --dev parameter from the terminal:

    $ atom --dev <em>path-to-open</em>
    +

    Note

    Note: If the atom command does not respond in the terminal, then try atom-dev or atom-beta. The suffix depends upon the particular source code that was cloned.

    There are a couple benefits of running Atom in Dev Mode:

    `,5),ii=t('
  • When the ATOM_DEV_RESOURCE_PATH environment variable is set correctly, Atom is run using the source code from your local atom/atom repository. This means that you don't have to run script/buildscript\\build every time you change code. Just restart Atom \u{1F44D}
  • Packages that exist in ~/.atom/dev/packages%USERPROFILE%\\.atom\\dev\\packages are loaded instead of packages of the same name normally loaded from other locations. This means that you can have development versions of packages you use loaded but easily go back to the stable versions by launching without Dev Mode.
  • ',2),li={href:"https://github.com/atom/dev-live-reload",target:"_blank",rel:"noopener noreferrer"},ci=e("code",null,"window:reload",-1),ri=t(`

    Running Atom Core Tests Locally

    In order to run Atom Core tests from the terminal, first be certain to set the ATOM_DEV_RESOURCE_PATH environment variable as mentioned above and then:

    $ cd <em>path-to-your-local-atom-repo</em>
    +$ atom --test spec
    +

    Building

    In order to build Atom from source, you need to have a number of other requirements and take additional steps.

    `,5),pi=e("p",null,"Ubuntu LTS 16.04 64-bit is the recommended platform.",-1),di=e("h5",{id:"requirements",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#requirements","aria-hidden":"true"},"#"),n(" Requirements")],-1),ui=e("li",null,"OS with 64-bit or 32-bit architecture",-1),hi=e("li",null,"C++11 toolchain",-1),mi=e("li",null,"Git",-1),gi={href:"https://github.com/creationix/nvm",target:"_blank",rel:"noopener noreferrer"},ki=e("li",null,[n("npm 6.12 or later (run "),e("code",null,"npm install -g npm"),n(")")],-1),vi=e("li",null,"Python 2.6.x, 2.7.x or 3.5+",-1),bi={href:"https://wiki.gnome.org/Projects/Libsecret",target:"_blank",rel:"noopener noreferrer"},fi=e("p",null,"For more details, scroll down to find how to setup a specific Linux distro.",-1),yi=e("h6",{id:"ubuntu-debian",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#ubuntu-debian","aria-hidden":"true"},"#"),n(" Ubuntu / Debian")],-1),wi=e("ul",null,[e("li",null,[e("p",null,"Install GNOME headers and other basic prerequisites:"),e("div",{class:"language-command-line ext-command-line line-numbers-mode"},[e("pre",{class:"language-command-line"},[e("code",null,`$ sudo apt-get install build-essential git libsecret-1-dev fakeroot rpm libx11-dev libxkbfile-dev +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"})])])]),e("li",null,[e("p",null,[n("If "),e("code",null,"script/build"),n(" exits with an error, you may need to install a newer C++ compiler with C++11:")]),e("div",{class:"language-command-line ext-command-line line-numbers-mode"},[e("pre",{class:"language-command-line"},[e("code",null,`$ sudo add-apt-repository ppa:ubuntu-toolchain-r/test +$ sudo apt-get update +$ sudo apt-get install gcc-5 g++-5 +$ sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-5 80 --slave /usr/bin/g++ g++ /usr/bin/g++-5 +$ sudo update-alternatives --config gcc # choose gcc-5 from the list +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"})])])])],-1),_i=e("h6",{id:"fedora-22",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#fedora-22","aria-hidden":"true"},"#"),n(" Fedora 22+")],-1),xi=e("div",{class:"language-command-line ext-command-line line-numbers-mode"},[e("pre",{class:"language-command-line"},[e("code",null,`$ sudo dnf --assumeyes install make gcc gcc-c++ glibc-devel git-core libsecret-devel rpmdevtools libX11-devel libxkbfile-devel +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"})])],-1),qi=e("h6",{id:"fedora-21-centos-rhel",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#fedora-21-centos-rhel","aria-hidden":"true"},"#"),n(" Fedora 21 / CentOS / RHEL")],-1),Ai=e("div",{class:"language-command-line ext-command-line line-numbers-mode"},[e("pre",{class:"language-command-line"},[e("code",null,`$ sudo yum install -y make gcc gcc-c++ glibc-devel git-core libsecret-devel rpmdevtools +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"})])],-1),Ti=e("h6",{id:"arch",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#arch","aria-hidden":"true"},"#"),n(" Arch")],-1),Ci=e("div",{class:"language-command-line ext-command-line line-numbers-mode"},[e("pre",{class:"language-command-line"},[e("code",null,`sudo pacman -S --needed gconf base-devel git nodejs npm libsecret python libx11 libxkbfile +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"})])],-1),Si=e("h6",{id:"slackware",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#slackware","aria-hidden":"true"},"#"),n(" Slackware")],-1),Ii=e("div",{class:"language-command-line ext-command-line line-numbers-mode"},[e("pre",{class:"language-command-line"},[e("code",null,`$ sbopkg -k -i node -i atom +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"})])],-1),ji=e("h6",{id:"opensuse",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#opensuse","aria-hidden":"true"},"#"),n(" openSUSE")],-1),Pi=e("div",{class:"language-command-line ext-command-line line-numbers-mode"},[e("pre",{class:"language-command-line"},[e("code",null,`$ sudo zypper install nodejs nodejs-devel make gcc gcc-c++ glibc-devel git-core libsecret-devel rpmdevtools libX11-devel libxkbfile-devel +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"})])],-1),Ri=e("h5",{id:"requirements-1",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#requirements-1","aria-hidden":"true"},"#"),n(" Requirements")],-1),Ni=e("li",null,"macOS 10.9 or later",-1),Ei={href:"https://github.com/creationix/nvm",target:"_blank",rel:"noopener noreferrer"},Ui=e("li",null,[n("npm 6.12 or later (run "),e("code",null,"npm install -g npm"),n(")")],-1),Mi=e("li",null,"Python v2.6.x, v2.7.x or v3.5+",-1),Wi={href:"https://developer.apple.com/xcode/downloads/",target:"_blank",rel:"noopener noreferrer"},Fi=e("code",null,"xcode-select --install",-1),Di=e("li",null,[e("p",null,"Node.js 10.12 or later (the architecture of node available to the build system will determine whether you build 32-bit or 64-bit Atom)")],-1),Li=e("li",null,[e("p",null,[n("npm 6.12 or later (run "),e("code",null,"npm install -g npm"),n(")")])],-1),Oi=e("p",null,"Python v2.6.x, v2.7.x, or v3.5+",-1),zi={href:"https://www.microsoft.com/en-us/search/shop/apps?q=python+software+foundation&devicetype=pc&category=Developer+tools%5cDevelopment+kits&Price=0&MaturityRating=ESRB%3aE",target:"_blank",rel:"noopener noreferrer"},Yi=e("li",null,[n("Download Python from https://www.python.org/downloads/. "),e("ul",null,[e("li",null,'For Python 2, be sure to install in the default location, or check "Add Python 2.x to PATH" before installing.'),e("li",null,[n('For Python 3, check "Add Python 3.x to PATH", or change the install path to '),e("code",null,"[Your_Drive_Letter]:\\Python37"),n(" e.g. "),e("code",null,"C:\\Python37"),n(", (even if your version of Python 3 isn't 3.7, that's one place where the scripts will look.)")]),e("li",null,[n("If python isn't found by the bootstrap script, create a symbolic link to the directory containing "),e("code",null,"python.exe"),n(" using e.g.: "),e("code",null,"mklink /d %SystemDrive%\\Python27 D:\\elsewhere\\Python27"),n("(Links should be set at either "),e("code",null,"%SystemDrive%\\Python27"),n(" or "),e("code",null,"%SystemDrive%\\Python37"),n(", regardless of what version of Python you actually have.)")])])],-1),Vi=e("p",null,"C++ build tools:",-1),$i=e("strong",null,"Option 1:",-1),Gi={href:"https://www.npmjs.com/package/windows-build-tools",target:"_blank",rel:"noopener noreferrer"},Hi=e("code",null,"npm install --global windows-build-tools@4",-1),Bi=e("strong",null,"Option 2:",-1),Ji={href:"https://visualstudio.microsoft.com/visual-cpp-build-tools/",target:"_blank",rel:"noopener noreferrer"},Ki=e("strong",null,"Option 3:",-1),Xi={href:"https://www.visualstudio.com/downloads/",target:"_blank",rel:"noopener noreferrer"},Zi=e("p",null,"Also ensure that:",-1),Qi=e("ul",null,[e("li",null,"The default installation folder is chosen so the build tools can find it"),e("li",null,"If using Visual Studio make sure Visual C++ support is selected/installed"),e("li",null,"If using Visual C++ Build Tools make sure a Windows SDK (Windows 8 SDK or Windows 10 SDK) is selected/installed"),e("li",null,[n("A "),e("code",null,"git"),n(" command is in your path")]),e("li",null,[n("Set the "),e("code",null,"GYP_MSVS_VERSION"),n(" environment variable to the Visual Studio/Build Tools version ("),e("code",null,"2015"),n(" or "),e("code",null,"2017"),n(".) e.g. "),e("code",null,'[Environment]::SetEnvironmentVariable("GYP_MSVS_VERSION", "2015", "User")'),n(" in PowerShell (or set it in Windows advanced system settings).")])],-1),el=e("h5",{id:"instructions",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#instructions","aria-hidden":"true"},"#"),n(" Instructions")],-1),nl=e("div",{class:"language-command-line ext-command-line line-numbers-mode"},[e("pre",{class:"language-command-line"},[e("code",null,`$ script/build +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"})])],-1),al=e("p",null,[n("To also install the newly built application, use the "),e("code",null,"--create-debian-package"),n(" or "),e("code",null,"--create-rpm-package"),n(" option and then install the generated package via the system package manager.")],-1),sl=e("div",{class:"language-command-line ext-command-line line-numbers-mode"},[e("pre",{class:"language-command-line"},[e("code",null,`$ script/build +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"})])],-1),tl=e("p",null,[n("To also install the newly built application, use "),e("code",null,"script/build --install"),n(".")],-1),ol=e("div",{class:"language-command-line ext-command-line line-numbers-mode"},[e("pre",{class:"language-command-line"},[e("code",null,`$ script\\build +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"})])],-1),il=e("p",null,[n("To also install the newly built application, use "),e("code",null,"script\\build --create-windows-installer"),n(" and launch one of the generated installers.")],-1),ll=e("h5",{id:"script-build-options",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#script-build-options","aria-hidden":"true"},"#"),n(),e("code",null,"script/build"),n(" Options")],-1),cl=e("ul",null,[e("li",null,[e("code",null,"--compress-artifacts"),n(": zips the generated application as "),e("code",null,"out/atom-{arch}.tar.gz"),n(".")]),e("li",null,[e("code",null,"--create-debian-package"),n(": creates a .deb package as "),e("code",null,"out/atom-{arch}.deb")]),e("li",null,[e("code",null,"--create-rpm-package"),n(": creates a .rpm package as "),e("code",null,"out/atom-{arch}.rpm")]),e("li",null,[e("code",null,"--install[=dir]"),n(": installs the application in "),e("code",null,"${dir}"),n("; "),e("code",null,"${dir}"),n(" defaults to "),e("code",null,"/usr/local"),n(".")])],-1),rl=e("ul",null,[e("li",null,[e("code",null,"--code-sign"),n(": signs the application with the GitHub certificate specified in "),e("code",null,"$ATOM_MAC_CODE_SIGNING_CERT_DOWNLOAD_URL"),n(".")]),e("li",null,[e("code",null,"--compress-artifacts"),n(": zips the generated application as "),e("code",null,"out/atom-mac.zip"),n(".")]),e("li",null,[e("code",null,"--install[=dir]"),n(": installs the application at "),e("code",null,"${dir}/Atom.app"),n(" for dev and stable versions or at "),e("code",null,"${dir}/Atom-Beta.app"),n(" for beta versions; "),e("code",null,"${dir}"),n(" defaults to "),e("code",null,"/Applications"),n(".")])],-1),pl=e("ul",null,[e("li",null,[e("code",null,"--code-sign"),n(": signs the application with the GitHub certificate specified in "),e("code",null,"$WIN_P12KEY_URL"),n(".")]),e("li",null,[e("code",null,"--compress-artifacts"),n(": zips the generated application as "),e("code",null,"out\\atom-windows.zip"),n(".")]),e("li",null,[e("code",null,"--create-windows-installer"),n(": creates an "),e("code",null,".exe"),n(" and two "),e("code",null,".nupkg"),n(" packages in the "),e("code",null,"out"),n(" directory.")]),e("li",null,[e("code",null,"--install[=dir]"),n(": installs the application in "),e("code",null,"${dir}\\Atom\\app-dev"),n("; "),e("code",null,"${dir}"),n(" defaults to "),e("code",null,"%LOCALAPPDATA%"),n(".")])],-1),dl=e("h5",{id:"troubleshooting",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#troubleshooting","aria-hidden":"true"},"#"),n(" Troubleshooting")],-1),ul=e("h6",{id:"typeerror-unable-to-watch-path",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#typeerror-unable-to-watch-path","aria-hidden":"true"},"#"),n(" TypeError: Unable to watch path")],-1),hl=e("p",null,"If you get following error with a big traceback right after Atom starts:",-1),ml=e("div",{class:"language-text ext-text line-numbers-mode"},[e("pre",{class:"language-text"},[e("code",null,`TypeError: Unable to watch path +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"})])],-1),gl=e("p",null,"you have to increase number of watched files by inotify. For testing if this is the reason for this error you can execute:",-1),kl=e("div",{class:"language-command-line ext-command-line line-numbers-mode"},[e("pre",{class:"language-command-line"},[e("code",null,`$ sudo sysctl fs.inotify.max_user_watches=32768 +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"})])],-1),vl=e("p",null,"then restart Atom. If Atom now works fine, you can make this setting permanent:",-1),bl=e("div",{class:"language-command-line ext-command-line line-numbers-mode"},[e("pre",{class:"language-command-line"},[e("code",null,`$ echo 32768 | sudo tee -a /proc/sys/fs/inotify/max_user_watches +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"})])],-1),fl={href:"https://github.com/atom/atom/issues/2082",target:"_blank",rel:"noopener noreferrer"},yl=e("h6",{id:"usr-bin-env-node-no-such-file-or-directory",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#usr-bin-env-node-no-such-file-or-directory","aria-hidden":"true"},"#"),n(" /usr/bin/env: node: No such file or directory")],-1),wl=e("p",null,[n("If you get this notice when attempting to run any script, you either do not have Node.js installed, or node isn't identified as Node.js on your machine. If it's the latter, this might be caused by installing Node.js via the distro package manager and not nvm, so entering "),e("code",null,"sudo ln -s /usr/bin/nodejs /usr/bin/node"),n(" into your terminal may fix the issue. On some variants (mostly Debian based distros) you can use "),e("code",null,"update-alternatives"),n(" too:")],-1),_l=e("div",{class:"language-command-line ext-command-line line-numbers-mode"},[e("pre",{class:"language-command-line"},[e("code",null,`$ sudo update-alternatives --install /usr/bin/node node /usr/bin/nodejs 1 --slave /usr/bin/js js /usr/bin/nodejs +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"})])],-1),xl=e("h6",{id:"attributeerror-module-object-has-no-attribute-script-main",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#attributeerror-module-object-has-no-attribute-script-main","aria-hidden":"true"},"#"),n(" AttributeError: 'module' object has no attribute 'script_main'")],-1),ql=e("p",null,"If you get following error with a big traceback while building Atom:",-1),Al=e("div",{class:"language-text ext-text line-numbers-mode"},[e("pre",{class:"language-text"},[e("code",null,`sys.exit(gyp.script_main()) AttributeError: 'module' object has no attribute 'script_main' gyp ERR! +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"})])],-1),Tl=e("p",null,"you need to uninstall the system version of gyp.",-1),Cl=e("p",null,"On Fedora you would do the following:",-1),Sl=e("div",{class:"language-command-line ext-command-line line-numbers-mode"},[e("pre",{class:"language-command-line"},[e("code",null,`$ sudo yum remove gyp +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"})])],-1),Il=e("h6",{id:"linux-build-error-reports",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#linux-build-error-reports","aria-hidden":"true"},"#"),n(" Linux build error reports")],-1),jl={href:"https://github.com/atom/atom/search?q=label%3Abuild-error+label%3Alinux&type=Issues",target:"_blank",rel:"noopener noreferrer"},Pl={href:"https://github.com/atom/atom/search?q=label%3Abuild-error+label%3Amac&type=Issues",target:"_blank",rel:"noopener noreferrer"},Rl=e("h6",{id:"common-errors",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#common-errors","aria-hidden":"true"},"#"),n(" Common Errors")],-1),Nl=e("li",null,[e("p",null,[e("code",null,"node is not recognized")]),e("ul",null,[e("li",null,[n("If you just installed Node.js, you'll need to restart Command Prompt before the "),e("code",null,"node"),n(" command is available on your path.")])])],-1),El=e("li",null,[e("p",null,[e("code",null,"msbuild.exe failed with exit code: 1")]),e("ul",null,[e("li",null,[n("If using "),e("strong",null,"Visual Studio"),n(", ensure you have the "),e("strong",null,"Visual C++"),n(" component installed. Go into Add/Remove Programs, select Visual Studio, press Modify, and then check the Visual C++ box.")]),e("li",null,[n("If using "),e("strong",null,"Visual C++ Build Tools"),n(", ensure you have the "),e("strong",null,"Windows 8 SDK"),n(" or "),e("strong",null,"Windows 10 SDK"),n(' component installed. Go into Add/Remove Programs, select Visual C++ Build Tools, press Modify and then check the "Windows 8 SDK" or "Windows 10 SDK" box.')])])],-1),Ul=e("p",null,[e("code",null,"script\\build"),n(" stops with no error or warning shortly after displaying the versions of node, npm and Python")],-1),Ml=e("li",null,[n("Make sure that the path where you have checked out Atom does not include a space. For example, use "),e("code",null,"C:\\atom"),n(" instead of "),e("code",null,"C:\\my stuff\\atom"),n(".")],-1),Wl=e("code",null,"C:\\atom",-1),Fl={href:"https://github.com/atom/atom/issues/2200",target:"_blank",rel:"noopener noreferrer"},Dl=e("p",null,[e("code",null,"error MSB4025: The project file could not be loaded. Invalid character in the given encoding.")],-1),Ll=e("code",null,"%USERPROFILE%",-1),Ol={href:"https://bugs.chromium.org/p/gyp/issues/list",target:"_blank",rel:"noopener noreferrer"},zl=e("ul",null,[e("li",null,"https://github.com/TooTallNate/node-gyp/issues/297"),e("li",null,"https://bugs.chromium.org/p/gyp/issues/detail?id=393")],-1),Yl=e("li",null,[e("p",null,[e("code",null,"'node_modules\\.bin\\npm' is not recognized as an internal or external command, operable program or batch file.")]),e("ul",null,[e("li",null,[n("This occurs if the previous build left things in a bad state. Run "),e("code",null,"script\\clean"),n(" and then "),e("code",null,"script\\build"),n(" again.")])])],-1),Vl=e("li",null,[e("p",null,[e("code",null,"script\\build"),n(" stops at installing runas with "),e("code",null,"Failed at the runas@x.y.z install script.")]),e("ul",null,[e("li",null,"See the next item.")])],-1),$l=e("li",null,[e("p",null,[e("code",null,"error MSB8020: The build tools for Visual Studio 201? (Platform Toolset = 'v1?0') cannot be found.")]),e("ul",null,[e("li",null,[n("Try setting the "),e("code",null,"GYP_MSVS_VERSION"),n(" environment variable to "),e("strong",null,"2015"),n(" or "),e("strong",null,"2017"),n(" depending on what version of Visual Studio/Build Tools is installed and then "),e("code",null,"script\\clean"),n(" followed by "),e("code",null,"script\\build"),n(" (re-open the Command Prompt if you set the variable using the GUI).")])])],-1),Gl=e("li",null,[e("p",null,[e("code",null,"'node-gyp' is not recognized as an internal or external command, operable program or batch file.")]),e("ul",null,[e("li",null,[n("Try running "),e("code",null,"npm install -g node-gyp"),n(", and run "),e("code",null,"script\\build"),n(" again.")])])],-1),Hl=e("li",null,[e("p",null,[n("Other "),e("code",null,"node-gyp"),n(" errors on first build attempt, even though the right Node.js and Python versions are installed.")]),e("ul",null,[e("li",null,"Do try the build command one more time as experience shows it often works on second try in many cases.")])],-1),Bl=e("h6",{id:"windows-build-error-reports",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#windows-build-error-reports","aria-hidden":"true"},"#"),n(" Windows build error reports")],-1),Jl={href:"https://github.com/atom/atom/search?q=label%3Abuild-error+label%3Awindows&type=Issues",target:"_blank",rel:"noopener noreferrer"},Kl=e("li",null,"If it hasn't, please open a new issue with your Windows version, architecture (x86 or x64), and a text dump of your build output, including the Node.js and Python versions.",-1),Xl=e("h3",{id:"contributing-to-official-atom-packages",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#contributing-to-official-atom-packages","aria-hidden":"true"},"#"),n(" Contributing to Official Atom Packages")],-1),Zl={href:"https://github.com/atom/atom",target:"_blank",rel:"noopener noreferrer"},Ql=e("h4",{id:"hacking-on-packages",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#hacking-on-packages","aria-hidden":"true"},"#"),n(" Hacking on Packages")],-1),ec=e("h5",{id:"cloning",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#cloning","aria-hidden":"true"},"#"),n(" Cloning")],-1),nc=e("code",null,"apm install",-1),ac=t(`

    For example, if you want to make changes to the tree-view package, fork the repo on your github account, then clone it:

    $ git clone git@github.com:<em>your-username</em>/tree-view.git
    +

    Next install all the dependencies:

    $ cd tree-view
    +$ apm install
    +> Installing modules \u2713
    +

    Now you can link it to development mode so when you run an Atom window with atom --dev, you will use your fork instead of the built in package:

    $ apm link -d
    +
    Running in Development Mode

    Editing a package in Atom is a bit of a circular experience: you're using Atom to modify itself. What happens if you temporarily break something? You don't want the version of Atom you're using to edit to become useless in the process. For this reason, you'll only want to load packages in development mode while you are working on them. You'll perform your editing in stable mode, only switching to development mode to test your changes.

    To open a development mode window, use the "Application: Open Dev" command. You can also run dev mode from the command line with atom --dev.

    To load your package in development mode, create a symlink to it in ~/.atom/dev/packages. This occurs automatically when you clone the package with apm develop. You can also run apm link --dev and apm unlink --dev from the package directory to create and remove dev-mode symlinks.

    Installing Dependencies

    You'll want to keep dependencies up to date by running apm update after pulling any upstream changes.

    Creating a Fork of a Core Package in atom/atom

    `,13),sc={href:"https://github.com/atom/atom/tree/master/packages",target:"_blank",rel:"noopener noreferrer"},tc=e("code",null,"packages",-1),oc={class:"custom-container tip"},ic=e("p",{class:"custom-container-title"},"Tips",-1),lc=e("strong",null,"Tip:",-1),cc=e("h3",{id:"creating-your-new-package",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#creating-your-new-package","aria-hidden":"true"},"#"),n(" Creating Your New Package")],-1),rc={href:"https://github.com/atom/atom/tree/master/packages/one-light-ui",target:"_blank",rel:"noopener noreferrer"},pc={href:"https://github.com/atom/atom/archive/master.zip",target:"_blank",rel:"noopener noreferrer"},dc=t(`
  • Unzip the file to a temporary location (for example /tmp/atomC:\\TEMP\\atom)

  • Copy the contents of the desired package into a working directory for your fork

    $ <span class='platform-mac platform-linux'>cp -R /tmp/atom/packages/one-light-ui ~/src/one-light-ui-plus</span><span class='platform-windows'>xcopy C:\\TEMP\\atom\\packages\\one-light-ui C:\\src\\one-light-ui-plus /E /H /K</span>
    +
  • Create a local repository and commit the initial contents

    $ cd ~/src/one-light-ui-plus
    +$ git init
    +$ git commit -am "Import core Atom package"
    +
  • Update the name property in package.json to give your package a unique name

  • Make the other customizations that you have in mind

  • Commit your changes

    $ git commit -am "Apply initial customizations"
    +
  • `,6),uc={href:"https://help.github.com/articles/create-a-repo/",target:"_blank",rel:"noopener noreferrer"},hc=e("li",null,[e("p",null,"Follow the instructions in the github.com UI to push your code to your new online repository")],-1),mc=e("h3",{id:"merging-upstream-changes-into-your-package",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#merging-upstream-changes-into-your-package","aria-hidden":"true"},"#"),n(" Merging Upstream Changes into Your Package")],-1),gc=e("h3",{id:"maintaining-a-fork-of-a-core-package-in-atom-atom",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#maintaining-a-fork-of-a-core-package-in-atom-atom","aria-hidden":"true"},"#"),n(" Maintaining a Fork of a Core Package in atom/atom")],-1),kc={href:"https://github.com/atom/atom/blob/master/docs/rfcs/003-consolidate-core-packages.md",target:"_blank",rel:"noopener noreferrer"},vc={href:"https://github.com/atom/atom",target:"_blank",rel:"noopener noreferrer"},bc={href:"https://github.com/atom/one-light-ui",target:"_blank",rel:"noopener noreferrer"},fc={href:"https://github.com/atom/atom/tree/master/packages/one-light-ui",target:"_blank",rel:"noopener noreferrer"},yc=e("code",null,"packages/one-light-ui",-1),wc=e("p",null,"If you forked one of the core packages before it was moved into the atom/atom repository, and you want to continue merging upstream changes into your fork, please follow the steps below.",-1),_c=e("h4",{id:"step-by-step-guide",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#step-by-step-guide","aria-hidden":"true"},"#"),n(" Step-by-step guide")],-1),xc={href:"https://github.com/atom/one-light-ui",target:"_blank",rel:"noopener noreferrer"},qc=e("code",null,"one-light-ui-plus",-1),Ac=t(`
    Add atom/atom as a Remote

    Navigate to your local clone of your fork:

    $ cd path/to/your/fork
    +
    `,3),Tc={href:"https://github.com/atom/atom",target:"_blank",rel:"noopener noreferrer"},Cc=t(`
    $ git remote add upstream https://github.com/atom/atom.git
    +
    Get the Latest Changes for the Core Package

    Tip

    Tip: Follow these steps each time you want to merge upstream changes into your fork.

    Fetch the latest changes from the atom/atom repository:

    $ git fetch upstream
    +

    Identify recent changes to the core package. For example, if you're maintaining a fork of the one-light-ui package, then you'll want to identify recent changes in the packages/one-light-ui directory:

    $ git log upstream/master -- packages/one-light-ui
    +8ac9919a0 Bump up border size (Hugh Baht, 17 minutes ago)
    +3bf4d226e Remove obsolete build status link in one-light-ui README (Jason Rudolph, 3 days ago)
    +3edf64ad0 Merge pull request #42 from atom/sm-select-list (simurai, 2 weeks ago)
    +...
    +

    Look through the log and identify the commits that you want to merge into your fork.

    Merge Upstream Changes into Your Fork
    `,9),Sc={href:"https://git-scm.com/docs/git-format-patch",target:"_blank",rel:"noopener noreferrer"},Ic=e("code",null,"git format-patch",-1),jc={href:"https://git-scm.com/docs/git-am",target:"_blank",rel:"noopener noreferrer"},Pc=e("code",null,"git am",-1),Rc=e("code",null,"8ac9919a0",-1),Nc=t(`
    $ git format-patch -1 --stdout 8ac9919a0 | git am -p3
    +

    Repeat this step for each commit that you want to merge into your fork.

    Summary

    If you finished this chapter, you should be an Atom-hacking master. We've discussed how you should work with CoffeeScript, and how to put it to good use in creating packages. You should also be able to do this in your own created theme now.

    Even when something goes wrong, you should be able to debug this easily. But also fewer things should go wrong, because you are capable of writing great specs for Atom.

    In the next chapter, we\u2019ll go into more of a deep dive on individual internal APIs and systems of Atom, even looking at some Atom source to see how things are really getting done.

    `,6);function Ec(Uc,Mc){const s=d("ExternalLinkIcon"),i=d("RouterLink"),p=d("Tabs");return E(),U("div",null,[F,e("p",null,[n(`Now it's time to come to the "Hackable" part of the Hackable Editor. As we've seen throughout the second section, a huge part of Atom is made up of bundled packages. If you wish to add some functionality to Atom, you have access to the same APIs and tools that the core features of Atom has. From the `),e("a",D,[n("tree-view"),a(s)]),n(" to the "),e("a",L,[n("command-palette"),a(s)]),n(" to "),e("a",O,[n("find-and-replace"),a(s)]),n(" functionality, even the most core features of Atom are implemented as packages.")]),z,e("p",null,[n("We'll go over examples like this in a bit, but this is what the language looks like. Just about everything you can do with CoffeeScript in Atom is also doable in JavaScript. You can brush up on CoffeeScript at "),e("a",Y,[n("coffeescript.org"),a(s)]),n(".")]),e("p",null,[n("Less is an even simpler transition from CSS. It adds a number of useful things like variables and functions to CSS. You can learn about Less at "),e("a",V,[n("lesscss.org"),a(s)]),n(". Our usage of Less won't get too complex in this book however, so as long as you know basic CSS you should be fine.")]),$,e("p",null,[n("When Atom finishes loading, it will evaluate "),G,n(" in your "),H,B,n(" directory, giving you a chance to run CoffeeScript code to make customizations. Code in this file has full access to "),e("a",J,[n("Atom's API"),a(s)]),n(". If customizations become extensive, consider creating a package, which we will cover in "),K,n(".")]),X,e("p",null,[n("Because "),Z,n(" provides access to Atom's API, you can use it to implement useful commands without creating a new package or extending an existing one. Here's a command which uses the "),e("a",Q,[n("Selection API"),a(s)]),n(" and "),e("a",ee,[n("Clipboard API"),a(s)]),n(" to construct a Markdown link from the selected text and the clipboard contents as the URL:")]),ne,e("p",null,[n("Now, reload Atom and use the "),a(i,{to:"/getting-started/sections/atom-basics/#command-palette"},{default:o(()=>[n("Command Palette")]),_:1}),n(` to execute the new command, "Markdown: Paste As Link", by name. And if you'd like to trigger the command via a keyboard shortcut, you can define a `),a(i,{to:"/using-atom/sections/basic-customization/#customizing-keybindings"},{default:o(()=>[n("keybinding for the command")]),_:1}),n(".")]),ae,se,te,e("p",null,[n("The simplest way to start a package is to use the built-in package generator that ships with Atom. As you might expect by now, this generator is itself a separate package implemented in "),e("a",oe,[n("package-generator"),a(s)]),n(".")]),ie,e("div",le,[ce,e("p",null,[re,n(" You may encounter a situation where your package is not loaded. That is because a new package using the same name as an actual package hosted on "),e("a",pe,[n("atom.io"),a(s)]),n(' (e.g. "wordcount" and "word-count") is not being loaded as you expected. If you follow our suggestion above of using the '),de,n(" package name, you "),ue,n(" be safe \u{1F600}")])]),he,e("p",null,[n("Similar to "),e("a",me,[n("Node modules"),a(s)]),n(", Atom packages contain a "),ge,n(' file in their top-level directory. This file contains metadata about the package, such as the path to its "main" module, library dependencies, and manifests specifying the order in which its resources should be loaded.')]),e("p",null,[n("In addition to some of the regular "),e("a",ke,[n("Node "),ve,n(" keys"),a(s)]),n(" available, Atom "),be,n(" files have their own additions.")]),fe,e("ul",null,[ye,e("li",null,[we,n(": (Available in Atom 1.14 and above) This "),_e,n(" method is similar to "),xe,n(" but is called earlier. Whereas activation occurs after the workspace has been deserialized (and can therefore happen after "),a(i,{to:"/behind-atom/sections/serialization-in-atom/#serialization-methods"},{default:o(()=>[n("your package's deserializers")]),_:1}),n(" have been called), "),qe,n(" is guaranteed to be called before everything. Use "),Ae,n(" if you want to be sure that the workspace is ready; use "),Te,n(" if you need to do some setup prior to your deserializers or view providers being invoked.")]),Ce,Se]),Ie,e("p",null,[n("Style sheets for your package should be placed in the "),je,n(" directory. Any style sheets in this directory will be loaded and attached to the DOM when your package is activated. Style sheets can be written as CSS or "),e("a",Pe,[n("Less"),a(s)]),n(", but Less is recommended.")]),Re,e("p",null,[n("If you "),Ne,n(" need special styling, try to keep only structural styles in the package style sheets. If you "),Ee,n(" specify colors and sizing, these should be taken from the active theme's "),e("a",Ue,[n("ui-variables.less"),a(s)]),n(".")]),Me,e("p",null,[n("We'll cover more advanced keybinding stuff a bit later in "),a(i,{to:"/behind-atom/sections/keymaps-in-depth/"},{default:o(()=>[n("Keymaps in Depth")]),_:1}),n(".")]),We,e("p",null,[n("Let's look at the 3 lines we've added. First we get an instance of the current editor object (where our text to count is) by calling "),e("a",Fe,[De,a(s)]),n(".")]),e("p",null,[n("Next we get the number of words by calling "),e("a",Le,[Oe,a(s)]),n(" on our new editor object, then splitting that text on whitespace with a regular expression and then getting the length of that array.")]),ze,e("p",null,[n("Under the hood, "),e("a",Ye,[n("Jasmine v1.3"),a(s)]),n(" executes your tests, so you can assume that any DSL available there is also available to your package.")]),Ve,e("p",null,[n("We've now generated, customized and tested our first plugin for Atom. Congratulations! Now let's go ahead and "),a(i,{to:"/docs/atom-archive/hacking-atom/publishing/"},{default:o(()=>[n("publish")]),_:1}),n(" it so it's available to the world.")]),$e,e("p",null,[n("Now that we have our first package written, let's go through examples of other types of packages we can make. This section will guide you though creating a simple command that replaces the selected text with "),e("a",Ge,[n("ascii art"),a(s)]),n('. When you run our new command with the word "cool" selected, it will be replaced with:')]),He,e("p",null,[n("To begin, press "),Be,Je,n(" to bring up the "),e("a",Ke,[n("Command Palette"),a(s)]),n('. Type "generate package" and select the "Package Generator: Generate Package" command, just as we did in '),a(i,{to:"/hacking-atom/sections/package-word-count/#package-generator"},{default:o(()=>[n("the section on package generation")]),_:1}),n(". Enter "),Xe,n(" as the name of the package.")]),Ze,e("p",null,[n("As in "),a(i,{to:"/hacking-atom/sections/package-word-count/#counting-the-words"},{default:o(()=>[n("Counting Words")]),_:1}),n(", we're using "),Qe,n(" to get the object that represents the active text editor. If this "),en,n(" method is called when not focused on a text editor, nothing will happen.")]),e("p",null,[n("Next we insert a string into the current text editor with the "),e("a",nn,[an,a(s)]),n(' method. This will insert the text wherever the cursor currently is in the current editor. If there are selections, it will replace all selections with the "Hello, World!" text.')]),sn,e("p",null,[n("Now we need to convert the selected text to ASCII art. To do this we will use the "),e("a",tn,[n("figlet"),a(s)]),n(" Node module from "),e("a",on,[n("npm"),a(s)]),n(". Open "),ln,n(" and add the latest version of figlet to the dependencies:")]),cn,e("p",null,[n("There are a couple of new things in this example we should look at quickly. The first is the "),e("a",rn,[pn,a(s)]),n(" which, as you might guess, returns the text that is currently selected.")]),e("p",null,[n("We then call the Figlet code to convert that into something else and replace the current selection with it with the "),e("a",dn,[un,a(s)]),n(" call.")]),hn,mn,gn,e("p",null,[n("We saw in our "),a(i,{to:"/hacking-atom/sections/package-word-count/"},{default:o(()=>[n("Word Count")]),_:1}),n(" package how we could show information in a modal panel. However, panels aren't the only way to extend Atom's UI\u2014you can also add items to the workspace. These items can be dragged to new locations (for example, one of the docks on the edges of the window), and Atom will restore them the next time you open the project. This system is used by Atom's tree view, as well as by third party packages like "),e("a",kn,[n("Nuclide"),a(s)]),n(" for its console, debugger, outline view, and diagnostics (linter results).")]),vn,bn,e("p",null,[n("To begin, press "),fn,yn,n(" to bring up the "),e("a",wn,[n("Command Palette"),a(s)]),n('. Type "generate package" and select the "Package Generator: Generate Package" command, just as we did in '),a(i,{to:"/hacking-atom/sections/package-word-count/#package-generator"},{default:o(()=>[n("the section on package generation")]),_:1}),n(". Enter "),_n,n(" as the name of the package.")]),xn,e("p",null,[n("Atom's interface is rendered using HTML, and it's styled via "),e("a",qn,[n("Less"),a(s)]),n(" which is a superset of CSS. Don't worry if you haven't heard of Less before; it's just like CSS, but with a few handy extensions.")]),An,e("ul",null,[e("li",null,[n("Less is a superset of CSS, but it has some really handy features like variables. If you aren't familiar with its syntax, take a few minutes to "),e("a",Tn,[n("familiarize yourself"),a(s)]),n(".")]),e("li",null,[n("You may also want to review the concept of a "),Cn,n(" (as covered in "),a(i,{to:"/hacking-atom/sections/package-word-count/#packagejson"},{default:o(()=>[n("Atom "),Sn]),_:1}),n("). This file is used to help distribute your theme to Atom users.")]),In,e("li",null,[n("You can find existing themes to install or fork in "),e("a",jn,[n("the atom.io themes registry"),a(s)]),n(".")])]),Pn,e("ol",null,[e("li",null,[n("Fork the "),e("a",Rn,[n("ui-theme-template"),a(s)])]),Nn]),En,e("ul",null,[e("li",null,[e("a",Un,[n("ui-variables.less"),a(s)])]),e("li",null,[e("a",Mn,[n("syntax-variables.less"),a(s)])])]),Wn,Fn,Dn,e("p",null,[n("Your package should generally only specify structural styling, and these should come from "),e("a",Ln,[n("the style guide"),a(s)]),n(". Your package shouldn't specify colors, padding sizes, or anything in absolute pixels. You should instead use the theme variables. If you follow this guideline, your package will look good out of the box with any theme!")]),On,e("p",null,[n("Reloading by pressing "),zn,Yn,n(" after you make changes to your theme is less than ideal. Atom supports "),e("a",Vn,[n("live updating"),a(s)]),n(" of styles on Atom windows in Dev Mode.")]),$n,e("p",null,[n("Check out Google's "),e("a",Gn,[n("extensive tutorial"),a(s)]),n(" for a short introduction.")]),Hn,e("p",null,[n("If you are creating an UI theme, you'll want a way to see how your theme changes affect all the components in the system. The "),e("a",Bn,[n("Styleguide"),a(s)]),n(" is a page that renders every component Atom supports.")]),Jn,e("p",null,[n("Follow the steps on the "),a(i,{to:"/docs/atom-archive/hacking-atom/publishing/"},{default:o(()=>[n("Publishing")]),_:1}),n(" page. The example used is for the Word Count package, but publishing a theme works exactly the same.")]),Kn,e("p",null,[n("Atom's syntax highlighting and code folding system is powered by "),e("a",Xn,[n("Tree-sitter"),a(s)]),n(". Tree-sitter parsers create and maintain full "),e("a",Zn,[Qn,a(s)]),n(" representing your code.")]),ea,e("p",null,[n("Tree-sitter generates parsers based on "),e("a",na,[n("context-free grammars"),a(s)]),n(" that are typically written in JavaScript. The generated parsers are C libraries that can be used in other applications as well as Atom.")]),e("p",null,[n("They can also be developed and tested at the command line, separately from Atom. Tree-sitter has "),e("a",aa,[n("its own documentation page"),a(s)]),n(" on how to create these parsers. The "),e("a",sa,[n("Tree-sitter GitHub organization"),a(s)]),n(" also contains a lot of example parsers that you can learn from, each in its own repository.")]),e("p",null,[n("Once you have created a parser, you need to publish it to "),e("a",ta,[n("the NPM registry"),a(s)]),n(" to use it in Atom. To do this, make sure you have a "),oa,n(" and "),ia,n(" in your parser's "),la,n(":")]),ca,e("ul",null,[ra,pa,e("li",null,[da,n(" - The name of the parser node module that will be used for parsing. This string will be passed directly to "),e("a",ua,[ha,a(s)]),n(" in order to load the parser.")]),ma]),ga,e("p",null,[n("Note that in this selector, we're using the "),e("a",ka,[n("immediate child combinator"),a(s)]),n(" ("),va,n("). Arbitrary descendant selectors without this combinator (for example "),ba,n(", which would match any "),fa,n(" occurring anywhere within a "),ya,n(") are currently not supported.")]),wa,e("p",null,[n("You can use the "),e("a",_a,[xa,n(" pseudo-class"),a(s)]),n(" to select nodes based on their order within their parent. For example, this example selects "),qa,n(" nodes which are the fourth (zero-indexed) child of a "),Aa,n(" node.")]),Ta,e("p",null,[n("Finally, you can use double-quoted strings in the selectors to select "),Ca,n(" tokens in the syntax tree, like "),Sa,n(" and "),Ia,n(". See "),e("a",ja,[n("the Tree-sitter documentation"),a(s)]),n(" for more information about named vs anonymous tokens.")]),Pa,e("p",null,[n("If multiple selectors in the "),Ra,n(" object match a node, the node's classes will be decided based on the "),e("a",Na,[n("most specific"),a(s)]),n(" selector. Note that the "),Ea,n(" and "),Ua,n(" rules do "),Ma,n(" affect specificity, so you may need to supply the same "),Wa,n(" or "),Fa,n(" rules for multiple selectors to ensure that they take precedence over other selectors. You can use the same selector multiple times in a scope mapping, within different comma-separated keys:")]),Da,e("p",null,[n("For example, in JavaScript, "),e("a",La,[n("tagged template literals"),a(s)]),n(" sometimes contain code written in a different language, and the name of the language is often used in the 'tag' function, as shown in this example:")]),Oa,e("ul",null,[e("li",null,[e("a",za,[n("Bash"),a(s)])]),e("li",null,[e("a",Ya,[n("C"),a(s)])]),e("li",null,[e("a",Va,[n("Go"),a(s)])]),e("li",null,[e("a",$a,[n("HTML"),a(s)])]),e("li",null,[e("a",Ga,[n("JavaScript"),a(s)])]),e("li",null,[e("a",Ha,[n("Python"),a(s)])]),e("li",null,[e("a",Ba,[n("Ruby"),a(s)])]),e("li",null,[e("a",Ja,[n("TypeScript"),a(s)])])]),Ka,e("p",null,[n("Grammar files are written in the "),e("a",Xa,[n("CSON"),a(s)]),n(" or "),e("a",Za,[n("JSON"),a(s)]),n(" format. Whichever one you decide to use is up to you, but this tutorial will be written in CSON.")]),Qa,e("p",null,[n("The default package template creates a lot of folders that aren't needed for grammar packages. Go ahead and delete the "),es,n(", "),ns,n(", "),as,n(", and "),ss,n(" folders. Furthermore, in "),ts,n(", remove the "),os,n(" section. Now create a new folder called "),is,n(", and inside that a file called "),ls,n(". This is the main file that we will be working with - start by populating it with a "),e("a",cs,[n("boilerplate template"),a(s)]),n(". Now let's go over what each key means.")]),rs,e("p",null,[ps,n(" is where your regex is contained, and "),ds,n(" is the scope name that is to be applied to the entirety of the match. More information about scope names can be found in "),e("a",us,[n("Section 12.4 of the TextMate Manual"),a(s)]),n(".")]),hs,e("ul",null,[e("li",null,[e("a",ms,[n("DamnedScholar's Gist"),a(s)]),n(". Provides a template of most keys, each with a short comment explaining their function.")]),e("li",null,[e("a",gs,[n("Aerijo's Gist"),a(s)]),n(". [Work in Progress] Another guide that attempts to fully explain making a grammar package for users of all levels.")]),ks,e("li",null,[e("a",vs,[n("Oniguruma docs"),a(s)]),n(". The documentation for the regex engine Atom uses.")]),e("li",null,[e("a",bs,[n("TextMate Section 12"),a(s)]),n(". Atom uses the same principles as laid out here, including the list of acceptable scopes.")]),e("li",null,[e("a",fs,[ys,a(s)]),n(". Not necessary to write a grammar, but a good technical reference for what Atom is doing behind the scenes.")]),e("li",null,[n("Look at any existing packages, such as the ones for "),e("a",ws,[n("Python"),a(s)]),n(", "),e("a",_s,[n("JavaScript"),a(s)]),n(", "),e("a",xs,[n("HTML"),a(s)]),n(", "),e("a",qs,[n("and more"),a(s)]),n(".")])]),M(` +TODO: +* \`repository\` and including from repository patterns +* Wrap-up +* Implement Package Generator functionality to generate a grammar +* Intermediate + advanced grammar tutorials +`),As,e("p",null,[n("Atom bundles a command line utility called "),Ts,n(" which we first used back in "),a(i,{to:"/using-atom/sections/atom-packages/#command-line"},{default:o(()=>[n("Command Line")]),_:1}),n(" to search for and install packages via the command line. The "),Cs,n(" command can also be used to publish Atom packages to the public registry and update them.")]),Ss,Is,e("ul",null,[js,e("li",null,[n("Your package is in a Git repository that has been pushed to "),e("a",Ps,[n("GitHub"),a(s)]),n(". Follow "),e("a",Rs,[n("this guide"),a(s)]),n(" if your package isn't already on GitHub.")])]),Ns,e("p",null,[n("Before you publish a package it is a good idea to check ahead of time if a package with the same name has already been published to "),e("a",Es,[n("the atom.io package registry"),a(s)]),n(". You can do that by visiting "),Us,n(" to see if the package already exists. If it does, update your package's name to something that is available before proceeding.")]),Ms,e("ol",null,[Ws,Fs,e("li",null,[n("Creates a new "),e("a",Ds,[n("Git tag"),a(s)]),n(" for the version being published.")]),Ls,Os]),zs,e("p",null,[n("If this is the first package you are publishing, the "),Ys,n(" command may prompt you for your GitHub username and password. If you have two-factor authentication enabled, use a "),e("a",Vs,[n("personal access token"),a(s)]),n(" in lieu of a password. This is required to publish and you only need to enter this information the first time you publish. The credentials are stored securely in your "),e("a",$s,[n("keychain"),a(s)]),n(" once you login.")]),Gs,e("p",null,[n("Use "),Hs,n(" when you make a change that breaks backwards compatibility, like changing defaults or removing features. Use "),Bs,n(" when adding new functionality or options, but without breaking backwards compatibility. Use "),Js,n(" when you've changed the implementation of existing features, but without changing the behaviour or options of your package. Check out "),e("a",Ks,[n("semantic versioning"),a(s)]),n(" to learn more about best practices for versioning your package releases.")]),Xs,Zs,e("p",null,[n("Atom comes bundled with the "),e("a",Qs,[n("Octicons 4.4.0"),a(s)]),n(" icon set. Use them to add icons to your packages.")]),et,nt,e("p",null,[n("In the "),a(i,{to:"/hacking-atom/sections/creating-a-theme/#atom-styleguide"},{default:o(()=>[n("Styleguide")]),_:1}),n(` under the "Icons" section you'll find all the Octicons that are available.`)]),at,e("p",null,[n("Although icons can make your UI visually appealing, when used without a text label, it can be hard to guess its meaning. In cases where space for a text label is insufficient, consider adding a "),e("a",st,[n("tooltip"),a(s)]),n(" that appears on hover. Or a more subtle "),tt,n(" attribute would help as well.")]),ot,e("p",null,[n("Atom provides several tools to help you understand unexpected behavior and debug problems. This guide describes some of those tools and a few approaches to help you debug and provide more helpful information when "),e("a",it,[n("submitting issues"),a(s)]),n(":")]),lt,e("p",null,[n("Then check for the "),e("a",ct,[n("latest Stable version"),a(s)]),n(".")]),a(p,{id:"1675",data:[{title:"Linux"},{title:"Mac"},{title:"Windows"}],"tab-id":"Updating"},{tab0:o(({title:l,value:c,isActive:r})=>[e("p",null,[n("To update to the latest version, you can download it from "),e("a",rt,[n("the atom.io website"),a(s)]),n(" or "),e("a",pt,[n("the latest release on GitHub"),a(s)]),n(" and follow the "),a(i,{to:"/getting-started/sections/installing-atom/#installing-atom-on-linux"},{default:o(()=>[n("Installation instructions for Atom on Linux")]),_:1}),n(".")])]),tab1:o(({title:l,value:c,isActive:r})=>[e("p",null,[n("If there is a more recent release available, you can update to the most recent release with the auto-update functionality built in to Atom and the "),e("a",dt,[n("about package"),a(s)]),n(". You can open the About View by using the "),ut,n(' menu option to see whether Atom is up-to-date, downloading a new update or click the button to "Restart and Install Update".')])]),tab2:o(({title:l,value:c,isActive:r})=>[e("p",null,[n("If there is a more recent release available, you can update to the most recent release with the auto-update functionality built in to Atom and the "),e("a",ht,[n("about package"),a(s)]),n(". You can open the About View by using the "),mt,n(' menu option to see whether Atom is up-to-date, downloading a new update or click the button to "Restart and Install Update".')])]),_:1}),e("p",null,[n("If you're building Atom from source, pull down the latest version of master and "),e("a",gt,[n("re-build"),a(s)]),n(".")]),kt,a(p,{id:"1735",data:[{title:"Linux"},{title:"Mac"},{title:"Windows"}],"tab-id":"Debugging"},{tab0:o(({title:l,value:c,isActive:r})=>[vt]),tab1:o(({title:l,value:c,isActive:r})=>[bt]),tab2:o(({title:l,value:c,isActive:r})=>[ft]),_:1}),yt,e("p",null,[n("If you have packages installed that use native Node modules, when you upgrade to a new version of Atom, they might need to be rebuilt. Atom detects this and through the "),e("a",wt,[n("incompatible-packages package"),a(s)]),n(" displays an indicator in the status bar when this happens.")]),_t,xt,qt,At,e("p",null,[n("Open Atom's "),e("a",Tt,[n("Settings View"),a(s)]),n(" with "),Ct,St,n(", the "),It,jt,Pt,n(' menu option, or the "Settings View: Open" command from the '),e("a",Rt,[n("Command Palette"),a(s)]),n(".")]),Nt,e("p",null,[n("Check Atom's settings in the Settings View, there's a description of most configuration options in the "),a(i,{to:"/using-atom/sections/basic-customization/#configuration-key-reference"},{default:o(()=>[n("Basic Customization section")]),_:1}),n('. For example, if you want Atom to hide the invisible symbols representing whitespace characters, disable the "Show Invisibles" option.')]),Et,e("p",null,[n("Since Atom ships with a set of packages and you can also install additional packages yourself, check the list of packages and their settings. For instance, if you'd like to get rid of the vertical line in the middle of the editor, disable the "),e("a",Ut,[n("Wrap Guide package"),a(s)]),n(". And if you don't like it when Atom strips trailing whitespace or ensures that there's a single trailing newline in the file, you can configure that in the "),e("a",Mt,[n("whitespace package's"),a(s)]),n(" settings.")]),Wt,Ft,e("p",null,[n("You might have defined some custom styles, keymaps or snippets in "),a(i,{to:"/using-atom/sections/basic-customization/"},{default:o(()=>[n("one of your configuration files")]),_:1}),n(". In some situations, these personal hacks might be causing the unexpected behavior you're observing so try clearing those files and restarting Atom.")]),Dt,e("p",null,[n("If a command is not executing when you press a key combination or the wrong command is executing, there might be an issue with the keybinding for that combination. Atom ships with the "),e("a",Lt,[n("Keybinding Resolver"),a(s)]),n(", a neat package which helps you understand what key Atom saw you press and the command that was triggered because of it.")]),Ot,e("p",null,[n("If multiple keybindings are matched, Atom determines which keybinding will be executed based on the "),a(i,{to:"/behind-atom/sections/keymaps-in-depth/#specificity-and-cascade-order"},{default:o(()=>[n("specificity of the selectors and the order in which they were loaded")]),_:1}),n(". If the command you wanted to trigger is listed in the Keybinding Resolver, but wasn't the one that was executed, this is normally explained by one of two causes:")]),zt,e("p",null,[n("Atom loads core Atom keybindings and package keybindings first, and user-defined keybindings last. Since user-defined keybindings are loaded last, you can use your "),Yt,n(" file to tweak the keybindings and sort out problems like these. See the "),a(i,{to:"/behind-atom/sections/keymaps-in-depth/"},{default:o(()=>[n("Keymaps in Depth section")]),_:1}),n(" for more information.")]),e("p",null,[n("If you notice that a package's keybindings are taking precedence over core Atom keybindings, it might be a good idea to report the issue on that package's GitHub repository. You can contact atom maintainers on "),e("a",Vt,[n("Atom's github discussions"),a(s)])]),$t,e("p",null,[n("You can determine which fonts are being used to render a specific piece of text by using the Developer Tools. To open the Developer Tools press "),Gt,Ht,n('. Once the Developer Tools are open, click the "Elements" tab. Use the '),e("a",Bt,[n("standard tools for finding the element"),a(s)]),n(' containing the text you want to check. Once you have selected the element, you can click the "Computed" tab in the styles pane and scroll to the bottom. The list of fonts being used will be shown there:')]),Jt,a(p,{id:"1912",data:[{title:"Linux"},{title:"Mac"},{title:"Windows"}],"tab-id":"Debugging"},{tab0:o(({title:l,value:c,isActive:r})=>[Kt]),tab1:o(({title:l,value:c,isActive:r})=>[Xt,Zt,Qt]),tab2:o(({title:l,value:c,isActive:r})=>[eo]),_:1}),no,e("p",null,[n("If Atom is taking a long time to start, you can use the "),e("a",ao,[n("Timecop package"),a(s)]),n(" to get insight into where Atom spends time while loading.")]),so,to,oo,io,lo,e("p",null,[n("If you're experiencing performance problems in a particular situation, your "),e("a",co,[n("Issue reports"),a(s)]),n(" will be more valuable if you include a saved profile from Chrome's CPU profiler that gives some insight into what is slow.")]),ro,e("p",null,[n("To learn more, check out the "),e("a",po,[n("Chrome documentation on CPU profiling"),a(s)]),n(".")]),uo,e("p",null,[n("Check out the pre-requisites in the "),e("a",ho,[n("build instructions"),a(s)]),n(" for your platform for more details.")]),mo,e("p",null,[n("Atom uses "),e("a",go,[n("Jasmine"),a(s)]),n(" as its spec framework. Any new functionality should have specs to guard against regressions.")]),ko,e("p",null,[e("a",vo,[n("Atom specs"),a(s)]),n(" and "),e("a",bo,[n("package specs"),a(s)]),n(" are added to their respective "),fo,n(" directory. The example below creates a spec for Atom core.")]),yo,e("p",null,[n("The best way to learn about expectations is to read the "),e("a",wo,[n("Jasmine documentation"),a(s)]),n(" about them. Below is a simple example.")]),_o,e("ul",null,[e("li",null,[e("a",xo,[n("jasmine-jquery"),a(s)])]),qo]),e("p",null,[n("These are defined in "),e("a",Ao,[n("spec/spec-helper.coffee"),a(s)]),n(".")]),To,e("p",null,[n("For a more detailed documentation on asynchronous tests please visit the "),e("a",Co,[n("Jasmine documentation"),a(s)]),n(".")]),So,e("p",null,[n("It is now easy to run the specs in a CI environment like Travis and AppVeyor. See the "),e("a",Io,[n("Travis CI For Your Packages"),a(s)]),n(" and "),e("a",jo,[n("AppVeyor CI For Your Packages"),a(s)]),n(" posts for more details.")]),Po,e("p",null,[n("Now that we've told Atom that we want our package to handle URIs beginning with "),Ro,n(" via our "),No,n(" method, we need to actually write this method. Atom passes two arguments to your URI handler method; the first one is the fully-parsed URI plus query string, "),e("a",Eo,[n("parsed with Node's "),Uo,a(s)]),n(". The second argument is the raw, string URI; this is normally not needed since the first argument gives you structured information about the URI.")]),Mo,e("p",null,[n("Creation and deletion operations may take a few milliseconds to complete. If you need to remove many files and folders consider "),e("a",Wo,[n("RimRAF"),a(s)]),n(" which has built-in retry logic for this.")]),Fo,e("p",null,[n("It's possible that you have themes or grammars from "),e("a",Do,[n("TextMate"),a(s)]),n(" that you like and use and would like to convert to Atom. If so, you're in luck because there are tools to help with the conversion.")]),Lo,Oo,e("p",null,[n("Let's convert the TextMate bundle for the "),e("a",zo,[n("R"),a(s)]),n(" programming language. You can find other existing TextMate bundles "),e("a",Yo,[n("on GitHub"),a(s)]),n(".")]),Vo,e("p",null,[n("This section will go over how to convert a "),e("a",$o,[n("TextMate"),a(s)]),n(" theme to an Atom theme.")]),Go,e("p",null,[n("TextMate themes use "),e("a",Ho,[n("plist"),a(s)]),n(" files while Atom themes use "),e("a",Bo,[n("CSS"),a(s)]),n(" or "),e("a",Jo,[n("Less"),a(s)]),n(" to style the UI and syntax in the editor.")]),Ko,Xo,e("p",null,[n("Download the theme you wish to convert, you can browse existing TextMate themes on the "),e("a",Zo,[n("TextMate website"),a(s)]),n(".")]),Qo,e("p",null,[n("Follow the "),e("a",ei,[n("GitHub Help instructions on how to fork a repo"),a(s)]),n(".")]),ni,a(p,{id:"2629",data:[{title:"Linux"},{title:"Mac"},{title:"Windows"}],"tab-id":"hacking-atom"},{tab0:o(({title:l,value:c,isActive:r})=>[ai]),tab1:o(({title:l,value:c,isActive:r})=>[si]),tab2:o(({title:l,value:c,isActive:r})=>[ti]),_:1}),oi,e("ol",null,[ii,e("li",null,[n("Packages that contain stylesheets, such as syntax themes, will have those stylesheets automatically reloaded by the "),e("a",li,[n("dev-live-reload"),a(s)]),n(" package. This does not live reload JavaScript or CoffeeScript files \u2014 you'll need to reload the window ("),ci,n(") to see changes to those.")])]),ri,a(p,{id:"2685",data:[{title:"Linux"},{title:"Mac"},{title:"Windows"}],"tab-id":"hacking-atom"},{tab0:o(({title:l,value:c,isActive:r})=>[pi,di,e("ul",null,[ui,hi,mi,e("li",null,[n("Node.js 10.12 or later (we recommend installing it via "),e("a",gi,[n("nvm"),a(s)]),n(")")]),ki,vi,e("li",null,[n("Development headers for "),e("a",bi,[n("libsecret"),a(s)]),n(".")])]),fi,yi,wi,_i,xi,qi,Ai,Ti,Ci,Si,Ii,ji,Pi]),tab1:o(({title:l,value:c,isActive:r})=>[Ri,e("ul",null,[Ni,e("li",null,[n("Node.js 10.12 or later (we recommend installing it via "),e("a",Ei,[n("nvm"),a(s)]),n(")")]),Ui,Mi,e("li",null,[n("Command Line Tools for "),e("a",Wi,[n("Xcode"),a(s)]),n(" (run "),Fi,n(" to install)")])])]),tab2:o(({title:l,value:c,isActive:r})=>[e("ul",null,[Di,Li,e("li",null,[Oi,e("ul",null,[e("li",null,[e("a",zi,[n("Get Python from the Microsoft Store"),a(s)]),n(", or")]),Yi])]),e("li",null,[Vi,e("ul",null,[e("li",null,[$i,n(),e("a",Gi,[n("windows-build-tools"),a(s)]),n(' - From an elevated Powershell window (right click and "run as Administrator") do: '),Hi,n(" to install")]),e("li",null,[Bi,n(),e("a",Ji,[n("Visual C++ Build Tools 2015 or 2017"),a(s)])]),e("li",null,[Ki,n(),e("a",Xi,[n("Visual Studio 2015 or 2017"),a(s)]),n(" (Community Edition or better)")])]),Zi,Qi])])]),_:1}),el,a(p,{id:"2907",data:[{title:"Linux"},{title:"Mac"},{title:"Windows"}],"tab-id":"hacking-atom"},{tab0:o(({title:l,value:c,isActive:r})=>[nl,al]),tab1:o(({title:l,value:c,isActive:r})=>[sl,tl]),tab2:o(({title:l,value:c,isActive:r})=>[ol,il]),_:1}),ll,a(p,{id:"2930",data:[{title:"Linux"},{title:"Mac"},{title:"Windows"}],"tab-id":"hacking-atom"},{tab0:o(({title:l,value:c,isActive:r})=>[cl]),tab1:o(({title:l,value:c,isActive:r})=>[rl]),tab2:o(({title:l,value:c,isActive:r})=>[pl]),_:1}),dl,a(p,{id:"3002",data:[{title:"Linux"},{title:"Mac"},{title:"Windows"}],"tab-id":"hacking-atom"},{tab0:o(({title:l,value:c,isActive:r})=>[ul,hl,ml,gl,kl,vl,bl,e("p",null,[n("See also "),e("a",fl,[n("#2082"),a(s)]),n(".")]),yl,wl,_l,xl,ql,Al,Tl,Cl,Sl,Il,e("p",null,[n("Use "),e("a",jl,[n("this search"),a(s)]),n(" to get a list of reports about build errors on Linux.")])]),tab1:o(({title:l,value:c,isActive:r})=>[e("p",null,[n("Use "),e("a",Pl,[n("this search"),a(s)]),n(" to get a list of reports about build errors on macOS.")])]),tab2:o(({title:l,value:c,isActive:r})=>[Rl,e("ul",null,[Nl,El,e("li",null,[Ul,e("ul",null,[Ml,e("li",null,[n("Try moving the repository to "),Wl,n(". Most likely, the path is too long. See "),e("a",Fl,[n("issue #2200"),a(s)]),n(".")])])]),e("li",null,[Dl,e("ul",null,[e("li",null,[n("This can occur because your home directory ("),Ll,n(") has non-ASCII characters in it. This is a bug in "),e("a",Ol,[n("gyp"),a(s)]),n(" which is used to build native Node.js modules and there is no known workaround. "),zl])])]),Yl,Vl,$l,Gl,Hl]),Bl,e("ul",null,[e("li",null,[n("If all else fails, use "),e("a",Jl,[n("this search"),a(s)]),n(" to get a list of reports about build errors on Windows, and see if yours has already been reported.")]),Kl])]),_:1}),Xl,e("p",null,[n("If you think you know which package is causing the issue you are reporting, feel free to open up the issue in that specific repository instead. When in doubt just open the issue on the "),e("a",Zl,[n("atom/atom"),a(s)]),n(" repository but be aware that it may get closed and reopened in the proper package's repository.")]),Ql,ec,e("p",null,[n("The first step is creating your own clone. For some packages, you may also need to install the "),a(i,{to:"/hacking-atom/sections/hacking-on-atom-core/#building"},{default:o(()=>[n("requirements necessary for building Atom")]),_:1}),n(" in order to run "),nc,n(".")]),ac,e("p",null,[n("Several of Atom's core packages are maintained in the "),e("a",sc,[tc,n(" directory of the atom/atom repository"),a(s)]),n(". If you would like to use one of these packages as a starting point for your own package, please follow the steps below.")]),e("div",oc,[ic,e("p",null,[lc,n(" In most cases, we recommend "),a(i,{to:"/docs/atom-archive/hacking-atom/package-word-count/#package-generator"},{default:o(()=>[n("generating a brand new package")]),_:1}),n(" or a "),a(i,{to:"/docs/atom-archive/hacking-atom/creating-a-theme/#creating-a-syntax-theme"},{default:o(()=>[n("brand new theme")]),_:1}),n(" as the starting point for your creation. The guide below applies only to situations where you want to create a package that closely resembles a core Atom package.")])]),cc,e("p",null,[n("For the sake of this guide, let's assume that you want to start with the current code in the "),e("a",rc,[n("one-light-ui"),a(s)]),n(' package, make some customizations to it, and publish your new package under the name "one-light-ui-plus".')]),e("ol",null,[e("li",null,[e("p",null,[n("Download the "),e("a",pc,[n("current contents of the atom/atom repository as a zip file"),a(s)])])]),dc,e("li",null,[e("p",null,[e("a",uc,[n("Create a public repository on github.com"),a(s)]),n(" for your new package")])]),hc,e("li",null,[e("p",null,[n("Follow the steps in the "),a(i,{to:"/docs/atom-archive/hacking-atom/sections/publishing/"},{default:o(()=>[n("Publishing guide")]),_:1}),n(" to publish your new package")])])]),mc,e("p",null,[n("The code in the original package will continue to evolve over time, either to fix bugs or to add new enhancements. You may want to incorporate some or all of those updates into your package. To do so, you can follow "),a(i,{to:"/docs/atom-archive/hacking-atom/sections/maintaining-a-fork-of-a-core-package-in-atom-atom/#step-by-step-guide"},{default:o(()=>[n("these steps")]),_:1}),n(" for merging upstream changes into your package.")]),gc,e("p",null,[n("Originally, each of Atom's core packages resided in a separate repository. In 2018, in an effort to streamline the development of Atom by reducing overhead, the Atom team "),e("a",kc,[n("consolidated many core Atom packages"),a(s)]),n(" into the "),e("a",vc,[n("atom/atom repository"),a(s)]),n(". For example, the one-light-ui package was originally maintained in the "),e("a",bc,[n("atom/one-light-ui"),a(s)]),n(" repository, but it is now maintained in the "),e("a",fc,[yc,n(" directory in the atom/atom repository"),a(s)]),n(".")]),wc,_c,e("p",null,[n("For the sake of this guide, let's assume that you forked the "),e("a",xc,[n("atom/one-light-ui"),a(s)]),n(" repository, renamed your fork to "),qc,n(", and made some customizations.")]),Ac,e("p",null,[n("Add the "),e("a",Tc,[n("atom/atom repository"),a(s)]),n(" as a git remote:")]),Cc,e("p",null,[n("For each commit that you want to bring into your fork, use "),e("a",Sc,[Ic,a(s)]),n(" in conjunction with "),e("a",jc,[Pc,a(s)]),n(". For example, to merge commit "),Rc,n(" into your fork:")]),Nc])}const Yc=N(W,[["render",Ec],["__file","index.html.vue"]]);export{Yc as default}; diff --git a/assets/index.html.563e9153.js b/assets/index.html.563e9153.js new file mode 100644 index 0000000000..fe016d4581 --- /dev/null +++ b/assets/index.html.563e9153.js @@ -0,0 +1 @@ +import{_ as e,o as c,c as t}from"./app.0e1565ce.js";const n={};function _(o,r){return c(),t("div")}const a=e(n,[["render",_],["__file","index.html.vue"]]);export{a as default}; diff --git a/assets/index.html.576a4614.js b/assets/index.html.576a4614.js new file mode 100644 index 0000000000..4c7834efab --- /dev/null +++ b/assets/index.html.576a4614.js @@ -0,0 +1 @@ +import{_ as a,o as s,c as t,a as e,b as o}from"./app.0e1565ce.js";const r={},c=e("h1",{id:"glossary",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#glossary","aria-hidden":"true"},"#"),o(" Glossary")],-1),n=[c];function _(d,i){return s(),t("div",null,n)}const h=a(r,[["render",_],["__file","index.html.vue"]]);export{h as default}; diff --git a/assets/index.html.58a10652.js b/assets/index.html.58a10652.js new file mode 100644 index 0000000000..6fb4511a0c --- /dev/null +++ b/assets/index.html.58a10652.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-36dcd4ed","path":"/docs/atom-archive/upgrading-to-1-0-apis/","title":"Appendix D : Upgrading to 1.0 APIs","lang":"en-US","frontmatter":{"title":"Appendix D : Upgrading to 1.0 APIs","sitemap":{"priority":0.1}},"excerpt":"","headers":[{"level":2,"title":"Upgrading to 1.0 APIs","slug":"upgrading-to-1-0-apis","link":"#upgrading-to-1-0-apis","children":[{"level":3,"title":"Upgrading Your Package","slug":"upgrading-your-package","link":"#upgrading-your-package","children":[]},{"level":3,"title":"Upgrading Your UI Theme Or Package Selectors","slug":"upgrading-your-ui-theme-or-package-selectors","link":"#upgrading-your-ui-theme-or-package-selectors","children":[]},{"level":3,"title":"Upgrading Your Syntax Theme","slug":"upgrading-your-syntax-theme","link":"#upgrading-your-syntax-theme","children":[]}]}],"git":{"updatedTime":1669438016000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":2},{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":2},{"name":"confused-Techie","email":"dev@lhbasics.com","commits":1}]},"readingTime":{"minutes":0.56,"words":167},"filePathRelative":"docs/atom-archive/upgrading-to-1-0-apis/index.md"}');export{e as data}; diff --git a/assets/index.html.59090f4c.js b/assets/index.html.59090f4c.js new file mode 100644 index 0000000000..dd813ef89b --- /dev/null +++ b/assets/index.html.59090f4c.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-35eded6e","path":"/docs/launch-manual/sections/getting-started/","title":"Getting Started","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Why Pulsar?","slug":"why-pulsar","link":"#why-pulsar","children":[{"level":3,"title":"The Nucleus of Pulsar","slug":"the-nucleus-of-pulsar","link":"#the-nucleus-of-pulsar","children":[]},{"level":3,"title":"The Native Web","slug":"the-native-web","link":"#the-native-web","children":[]},{"level":3,"title":"JavaScript, Meet C++","slug":"javascript-meet-c","link":"#javascript-meet-c","children":[]},{"level":3,"title":"Web Tech: The Fun Parts","slug":"web-tech-the-fun-parts","link":"#web-tech-the-fun-parts","children":[]},{"level":3,"title":"An Open-Source Text Editor","slug":"an-open-source-text-editor","link":"#an-open-source-text-editor","children":[]}]},{"level":2,"title":"Installing Pulsar","slug":"installing-pulsar","link":"#installing-pulsar","children":[{"level":3,"title":"Universal releases","slug":"universal-releases","link":"#universal-releases","children":[]},{"level":3,"title":"Debian/Ubuntu based distributions","slug":"debian-ubuntu-based-distributions","link":"#debian-ubuntu-based-distributions","children":[]},{"level":3,"title":"Fedora/RHEL based distributions","slug":"fedora-rhel-based-distributions","link":"#fedora-rhel-based-distributions","children":[]},{"level":3,"title":"Updating Pulsar","slug":"updating-pulsar","link":"#updating-pulsar","children":[]},{"level":3,"title":"Portable Mode","slug":"portable-mode","link":"#portable-mode","children":[]},{"level":3,"title":"Building Pulsar from Source","slug":"building-pulsar-from-source","link":"#building-pulsar-from-source","children":[]},{"level":3,"title":"Proxy and Firewall Settings","slug":"proxy-and-firewall-settings","link":"#proxy-and-firewall-settings","children":[]}]},{"level":2,"title":"Pulsar Basics","slug":"pulsar-basics","link":"#pulsar-basics","children":[{"level":3,"title":"Terminology","slug":"terminology","link":"#terminology","children":[]},{"level":3,"title":"Command Palette","slug":"command-palette","link":"#command-palette","children":[]},{"level":3,"title":"Settings and Preferences","slug":"settings-and-preferences","link":"#settings-and-preferences","children":[]},{"level":3,"title":"Opening, Modifying, and Saving Files","slug":"opening-modifying-and-saving-files","link":"#opening-modifying-and-saving-files","children":[]},{"level":3,"title":"Opening Directories","slug":"opening-directories","link":"#opening-directories","children":[]}]},{"level":2,"title":"Summary","slug":"summary","link":"#summary","children":[]}],"git":{"updatedTime":1672104919000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":2},{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":2}]},"readingTime":{"minutes":0.26,"words":78},"filePathRelative":"docs/launch-manual/sections/getting-started/index.md"}');export{e as data}; diff --git a/assets/index.html.5925a712.js b/assets/index.html.5925a712.js new file mode 100644 index 0000000000..1aa4fc8e5c --- /dev/null +++ b/assets/index.html.5925a712.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-65ee6ad2","path":"/category/dev/","title":"dev Category","lang":"en-US","frontmatter":{"title":"dev Category","blog":{"type":"category","name":"dev","key":"category"},"layout":"Blog"},"excerpt":"","headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null}');export{e as data}; diff --git a/assets/index.html.5cd06018.js b/assets/index.html.5cd06018.js new file mode 100644 index 0000000000..cefb93701d --- /dev/null +++ b/assets/index.html.5cd06018.js @@ -0,0 +1 @@ +import{_ as o,o as r,c as l,a as t,b as e,d as n,r as i}from"./app.0e1565ce.js";const c={},s=t("h1",{id:"atom-languageclient",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#atom-languageclient","aria-hidden":"true"},"#"),e(" Atom-LanguageClient")],-1),h=t("p",null,[e("Welcome to the "),t("code",null,"atom-languageclient"),e(" wiki!")],-1),u={href:"https://github.com/atom/atom-languageclient/wiki",target:"_blank",rel:"noopener noreferrer"},m={href:"https://github.com/pulsar-edit/atom-languageclient",target:"_blank",rel:"noopener noreferrer"};function _(d,g){const a=i("ExternalLinkIcon");return r(),l("div",null,[s,h,t("p",null,[e("This wiki has been nearly directly taken from the "),t("a",u,[e("upstream Atom Wiki"),n(a)]),e(". That may mean that parts of it are out of date, but otherwise hopefully is a helpful resource relating to our "),t("a",m,[e("Atom-LanguageClient Repo"),n(a)]),e("!")])])}const f=o(c,[["render",_],["__file","index.html.vue"]]);export{f as default}; diff --git a/assets/index.html.5de84039.js b/assets/index.html.5de84039.js new file mode 100644 index 0000000000..d5d230b970 --- /dev/null +++ b/assets/index.html.5de84039.js @@ -0,0 +1,2 @@ +import{_ as n,o as r,c as s,e as i,a as e,b as t,d as o,r as d}from"./app.0e1565ce.js";const c={},l=e("h1",{id:"pulsar-tooling",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#pulsar-tooling","aria-hidden":"true"},"#"),t(" Pulsar tooling")],-1),h=e("p",null,"Here you will find a list of tools used by the Pulsar team and information about them.",-1),u=e("h2",{id:"continuous-integration",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#continuous-integration","aria-hidden":"true"},"#"),t(" Continuous Integration")],-1),p={id:"cirrus-ci",tabindex:"-1"},_=e("a",{class:"header-anchor",href:"#cirrus-ci","aria-hidden":"true"},"#",-1),g={href:"https://cirrus-ci.com/github/pulsar-edit/pulsar",target:"_blank",rel:"noopener noreferrer"},f=e("p",null,"Cirrus CI is used for Pulsar's continuous integration as well as for building application binaries.",-1),b={id:"codacy",tabindex:"-1"},m=e("a",{class:"header-anchor",href:"#codacy","aria-hidden":"true"},"#",-1),k={href:"https://app.codacy.com/gh/pulsar-edit/repositories",target:"_blank",rel:"noopener noreferrer"},y=e("p",null,"Codacy is used to scan committed code for any issues that may have been missed.",-1),x=e("p",null,"Currently though Codacy is only used on the following repositories:",-1),C={href:"https://app.codacy.com/gh/pulsar-edit/ppm/dashboard",target:"_blank",rel:"noopener noreferrer"},P={href:"https://app.codacy.com/gh/pulsar-edit/pulsar/dashboard",target:"_blank",rel:"noopener noreferrer"},w={href:"https://app.codacy.com/gh/pulsar-edit/background-tips/dashboard",target:"_blank",rel:"noopener noreferrer"},v={href:"https://app.codacy.com/gh/pulsar-edit/autocomplete-plus/dashboard",target:"_blank",rel:"noopener noreferrer"},T=e("h2",{id:"i18n-internationalization",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#i18n-internationalization","aria-hidden":"true"},"#"),t(" i18n (Internationalization)")],-1),A={id:"crowdin",tabindex:"-1"},I=e("a",{class:"header-anchor",href:"#crowdin","aria-hidden":"true"},"#",-1),D={href:"https://crowdin.pulsar-edit.dev/",target:"_blank",rel:"noopener noreferrer"},E=e("p",null,"Crowdin will be used for Pulsar's internationalization efforts but exact details on this are still pending.",-1),N=e("h2",{id:"package-managers",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#package-managers","aria-hidden":"true"},"#"),t(" Package Managers")],-1),z=e("p",null,"While most repositories you can easily tell what Package Manager is being used by checking for a specific lock file, there are some execptions to this that should be noted.",-1),B={href:"https://github.com/pulsar-edit/pulsar",target:"_blank",rel:"noopener noreferrer"},G=e("code",null,"yarn",-1),L={href:"https://github.com/pulsar-edit/pulsar-edit.github.io",target:"_blank",rel:"noopener noreferrer"},M=e("code",null,"pnpm",-1),V=e("h2",{id:"cloud-database",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#cloud-database","aria-hidden":"true"},"#"),t(" Cloud Database")],-1),H={href:"https://github.com/pulsar-edit/package-backend",target:"_blank",rel:"noopener noreferrer"},O=e("h2",{id:"cloud-compute",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#cloud-compute","aria-hidden":"true"},"#"),t(" Cloud Compute")],-1),S={href:"https://github.com/pulsar-edit/package-backend",target:"_blank",rel:"noopener noreferrer"},Q={href:"https://github.com/pulsar-edit/package-frontend",target:"_blank",rel:"noopener noreferrer"},R=e("h2",{id:"additional-testing-tools",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#additional-testing-tools","aria-hidden":"true"},"#"),t(" Additional Testing tools")],-1),W={id:"action-pulsar-dependency-tester",tabindex:"-1"},j=e("a",{class:"header-anchor",href:"#action-pulsar-dependency-tester","aria-hidden":"true"},"#",-1),q={href:"https://github.com/marketplace/actions/action-pulsar-dependency-tester",target:"_blank",rel:"noopener noreferrer"},F=e("p",null,"Pulsar Dependency Tester is a GitHub action used to test changes of a core Pulsar Dependency if you need to determine how your core package dependency will run on the current version of the Pulsar Editor.",-1),J={href:"https://github.com/marketplace/actions/setup-atom",target:"_blank",rel:"noopener noreferrer"};function K(U,X){const a=d("ExternalLinkIcon");return r(),s("div",null,[i(`This page is generated from the TOOLING.md page on the org-level +documentation at https://github.com/pulsar-edit/.github`),l,h,u,e("h3",p,[_,t(),e("a",g,[t("Cirrus CI"),o(a)])]),f,e("h3",b,[m,t(),e("a",k,[t("Codacy"),o(a)])]),y,x,e("ul",null,[e("li",null,[e("a",C,[t("ppm"),o(a)])]),e("li",null,[e("a",P,[t("pulsar"),o(a)])]),e("li",null,[e("a",w,[t("background-tips"),o(a)])]),e("li",null,[e("a",v,[t("autocomplete-plus"),o(a)])])]),T,e("h3",A,[I,t(),e("a",D,[t("Crowdin"),o(a)])]),E,N,z,e("ul",null,[e("li",null,[e("a",B,[t("pulsar"),o(a)]),t(" uses "),G,t(" as its Package Manager.")]),e("li",null,[e("a",L,[t("pulsar-edit.github.io"),o(a)]),t(" uses "),M,t(" as its Package Manager.")])]),V,e("p",null,[t("The "),e("a",H,[t("package-backend"),o(a)]),t(" currently uses DigitalOcean to host the PostgreSQL Pulsar Package Repositories data in the cloud.")]),O,e("p",null,[t("Both the "),e("a",S,[t("package-backend"),o(a)]),t(" and "),e("a",Q,[t("package-frontend"),o(a)]),t(" use Google App Engine to host the compute instance of these websites in the cloud.")]),R,e("h3",W,[j,t(),e("a",q,[t("Action Pulsar Dependency Tester"),o(a)])]),F,e("p",null,[t("This GitHub Action is a direct Pulsar replacement of the previous Action to test Atom Dependencies "),e("a",J,[t("Setup Atom"),o(a)]),t(".")])])}const Z=n(c,[["render",K],["__file","index.html.vue"]]);export{Z as default}; diff --git a/assets/index.html.5e5c1bba.js b/assets/index.html.5e5c1bba.js new file mode 100644 index 0000000000..fe016d4581 --- /dev/null +++ b/assets/index.html.5e5c1bba.js @@ -0,0 +1 @@ +import{_ as e,o as c,c as t}from"./app.0e1565ce.js";const n={};function _(o,r){return c(),t("div")}const a=e(n,[["render",_],["__file","index.html.vue"]]);export{a as default}; diff --git a/assets/index.html.5eca0895.js b/assets/index.html.5eca0895.js new file mode 100644 index 0000000000..12d2976dbe --- /dev/null +++ b/assets/index.html.5eca0895.js @@ -0,0 +1 @@ +const t=JSON.parse('{"key":"v-08115088","path":"/tag/stars/","title":"stars Tag","lang":"en-US","frontmatter":{"title":"stars Tag","blog":{"type":"category","name":"stars","key":"tag"},"layout":"Blog"},"excerpt":"","headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null}');export{t as data}; diff --git a/assets/index.html.5f67efaf.js b/assets/index.html.5f67efaf.js new file mode 100644 index 0000000000..7a6d639d0a --- /dev/null +++ b/assets/index.html.5f67efaf.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-18ed05d5","path":"/tag/releases/","title":"releases Tag","lang":"en-US","frontmatter":{"title":"releases Tag","blog":{"type":"category","name":"releases","key":"tag"},"layout":"Blog"},"excerpt":"","headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null}');export{e as data}; diff --git a/assets/index.html.61294029.js b/assets/index.html.61294029.js new file mode 100644 index 0000000000..fe016d4581 --- /dev/null +++ b/assets/index.html.61294029.js @@ -0,0 +1 @@ +import{_ as e,o as c,c as t}from"./app.0e1565ce.js";const n={};function _(o,r){return c(),t("div")}const a=e(n,[["render",_],["__file","index.html.vue"]]);export{a as default}; diff --git a/assets/index.html.644bc9d9.js b/assets/index.html.644bc9d9.js new file mode 100644 index 0000000000..fc4b3d8601 --- /dev/null +++ b/assets/index.html.644bc9d9.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-f6458faa","path":"/tag/tree-sitter/","title":"tree-sitter Tag","lang":"en-US","frontmatter":{"title":"tree-sitter Tag","blog":{"type":"category","name":"tree-sitter","key":"tag"},"layout":"Blog"},"excerpt":"","headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null}');export{e as data}; diff --git a/assets/index.html.64f1ae66.js b/assets/index.html.64f1ae66.js new file mode 100644 index 0000000000..14b3eec57a --- /dev/null +++ b/assets/index.html.64f1ae66.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-8daa1a0e","path":"/","title":"Home","lang":"en-us","frontmatter":{"sitemap":{"priority":1,"changefreq":"daily"},"lang":"en-us","title":"Home","home":true,"actions":[{"text":"Download \u{1F4E5}","link":"/download.html","type":"primary"},{"text":"Documentation \u{1F4D6}","link":"/docs/"},{"text":"Donate \u{1F381}","link":"/donate.html"}],"heroText":"Pulsar","features":[{"title":"Cross-platform Editing","details":"Pulsar works across operating systems. Use it on OS X, Windows, or Linux."},{"title":"Built-in package manager","details":"Search and install new packages or create your own right from Pulsar."},{"title":"Smart Autocompletion","details":"Pulsar helps you write code faster with a smart and flexible autocomplete."},{"title":"File system browser","details":"Easily browse and open a single file, a whole project, or multiple projects in one window."},{"title":"Multiple panes","details":"Split your Pulsar interface into multiple panes to compare and edit code across files."},{"title":"Find and replace","details":"Find, preview, and replace text as you type in a file or across all your projects."}]},"excerpt":"","headers":[{"level":2,"title":"Notices","slug":"notices","link":"#notices","children":[{"level":3,"title":"Downloads and Releases","slug":"downloads-and-releases","link":"#downloads-and-releases","children":[]},{"level":3,"title":"App Updates","slug":"app-updates","link":"#app-updates","children":[]},{"level":3,"title":"Blog","slug":"blog","link":"#blog","children":[]},{"level":3,"title":"Packages","slug":"packages","link":"#packages","children":[]},{"level":3,"title":"Support and Community","slug":"support-and-community","link":"#support-and-community","children":[]}]}],"git":{"updatedTime":1706814354000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":7},{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":7},{"name":"DeeDeeG","email":"DeeDeeG@users.noreply.github.com","commits":5},{"name":"confused-Techie","email":"dev@lhbasics.com","commits":3},{"name":"confused_techie","email":"dev@lhbasics.com","commits":2},{"name":"Joshua MIller","email":"16845458+kaosine@users.noreply.github.com","commits":1},{"name":"Joshua Miller","email":"16845458+kaosine@users.noreply.github.com","commits":1},{"name":"Landar","email":"104514709+LandarXT@users.noreply.github.com","commits":1},{"name":"Meadowsys","email":"blazeykirin@gmail.com","commits":1}]},"readingTime":{"minutes":1.32,"words":396},"filePathRelative":"index.md"}');export{e as data}; diff --git a/assets/index.html.6811c420.js b/assets/index.html.6811c420.js new file mode 100644 index 0000000000..fe016d4581 --- /dev/null +++ b/assets/index.html.6811c420.js @@ -0,0 +1 @@ +import{_ as e,o as c,c as t}from"./app.0e1565ce.js";const n={};function _(o,r){return c(),t("div")}const a=e(n,[["render",_],["__file","index.html.vue"]]);export{a as default}; diff --git a/assets/index.html.6f6341d2.js b/assets/index.html.6f6341d2.js new file mode 100644 index 0000000000..854d6a1850 --- /dev/null +++ b/assets/index.html.6f6341d2.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-cf57a236","path":"/docs/resources/","title":"Resources","lang":"en-US","frontmatter":{"title":"Resources"},"excerpt":"","headers":[{"level":2,"title":"Glossary","slug":"glossary","link":"#glossary","children":[]},{"level":2,"title":"Pulsar API","slug":"pulsar-api","link":"#pulsar-api","children":[]},{"level":2,"title":"Project tooling","slug":"project-tooling","link":"#project-tooling","children":[]}],"git":{"updatedTime":1668306676000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":2},{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":0.26,"words":78},"filePathRelative":"docs/resources/index.md"}');export{e as data}; diff --git a/assets/index.html.70b20295.js b/assets/index.html.70b20295.js new file mode 100644 index 0000000000..fe016d4581 --- /dev/null +++ b/assets/index.html.70b20295.js @@ -0,0 +1 @@ +import{_ as e,o as c,c as t}from"./app.0e1565ce.js";const n={};function _(o,r){return c(),t("div")}const a=e(n,[["render",_],["__file","index.html.vue"]]);export{a as default}; diff --git a/assets/index.html.715b0c77.js b/assets/index.html.715b0c77.js new file mode 100644 index 0000000000..fe016d4581 --- /dev/null +++ b/assets/index.html.715b0c77.js @@ -0,0 +1 @@ +import{_ as e,o as c,c as t}from"./app.0e1565ce.js";const n={};function _(o,r){return c(),t("div")}const a=e(n,[["render",_],["__file","index.html.vue"]]);export{a as default}; diff --git a/assets/index.html.719d38d5.js b/assets/index.html.719d38d5.js new file mode 100644 index 0000000000..fe016d4581 --- /dev/null +++ b/assets/index.html.719d38d5.js @@ -0,0 +1 @@ +import{_ as e,o as c,c as t}from"./app.0e1565ce.js";const n={};function _(o,r){return c(),t("div")}const a=e(n,[["render",_],["__file","index.html.vue"]]);export{a as default}; diff --git a/assets/index.html.74bcfb5f.js b/assets/index.html.74bcfb5f.js new file mode 100644 index 0000000000..e45e728afa --- /dev/null +++ b/assets/index.html.74bcfb5f.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-3835d9b2","path":"/docs/atom-archive/using-atom/","title":"Chapter 2 : Using Atom","lang":"en-US","frontmatter":{"title":"Chapter 2 : Using Atom","sitemap":{"priority":0.1}},"excerpt":"","headers":[{"level":2,"title":"Using Atom","slug":"using-atom","link":"#using-atom","children":[{"level":3,"title":"Atom Packages","slug":"atom-packages","link":"#atom-packages","children":[]},{"level":3,"title":"Moving in Atom","slug":"moving-in-atom","link":"#moving-in-atom","children":[]},{"level":3,"title":"Atom Selections","slug":"atom-selections","link":"#atom-selections","children":[]},{"level":3,"title":"Editing and Deleting Text","slug":"editing-and-deleting-text","link":"#editing-and-deleting-text","children":[]},{"level":3,"title":"Find and Replace","slug":"find-and-replace","link":"#find-and-replace","children":[]},{"level":3,"title":"Snippets","slug":"snippets","link":"#snippets","children":[]},{"level":3,"title":"Autocomplete","slug":"autocomplete","link":"#autocomplete","children":[]},{"level":3,"title":"Folding","slug":"folding","link":"#folding","children":[]},{"level":3,"title":"Panes","slug":"panes","link":"#panes","children":[]},{"level":3,"title":"Pending Pane Items","slug":"pending-pane-items","link":"#pending-pane-items","children":[]},{"level":3,"title":"Grammar","slug":"grammar","link":"#grammar","children":[]},{"level":3,"title":"Version Control in Atom","slug":"version-control-in-atom","link":"#version-control-in-atom","children":[]},{"level":3,"title":"GitHub package","slug":"github-package","link":"#github-package","children":[]},{"level":3,"title":"Writing in Atom","slug":"writing-in-atom","link":"#writing-in-atom","children":[]},{"level":3,"title":"Basic Customization","slug":"basic-customization","link":"#basic-customization","children":[]},{"level":3,"title":"Summary","slug":"summary","link":"#summary","children":[]}]}],"git":{"updatedTime":1669438016000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":3},{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1},{"name":"confused-Techie","email":"dev@lhbasics.com","commits":1}]},"readingTime":{"minutes":0.86,"words":259},"filePathRelative":"docs/atom-archive/using-atom/index.md"}');export{e as data}; diff --git a/assets/index.html.758e28c3.js b/assets/index.html.758e28c3.js new file mode 100644 index 0000000000..fe016d4581 --- /dev/null +++ b/assets/index.html.758e28c3.js @@ -0,0 +1 @@ +import{_ as e,o as c,c as t}from"./app.0e1565ce.js";const n={};function _(o,r){return c(),t("div")}const a=e(n,[["render",_],["__file","index.html.vue"]]);export{a as default}; diff --git a/assets/index.html.782819d8.js b/assets/index.html.782819d8.js new file mode 100644 index 0000000000..42e095e1c8 --- /dev/null +++ b/assets/index.html.782819d8.js @@ -0,0 +1,65 @@ +import{_ as o,o as i,c as r,a,b as e,d as n,f as t,r as p}from"./app.0e1565ce.js";const c={},l=t('

    STOP

    This is being kept for archival purposes only from the original Atom documentation. As this may no longer be relevant to Pulsar, use this at your own risk. Current Pulsar documentation is found at documentation home.

    Atom server-side APIs

    This appendix covers the the Atom server-side APIs that various parts of Atom consume.

    WARNING

    Warning: These APIs should be considered pre-release and are subject to change.

    Atom package server API

    ',5),d={href:"https://github.com/atom/apm",target:"_blank",rel:"noopener noreferrer"},u=a("code",null,"apm",-1),h=a("code",null,"package.json",-1),g=a("code",null,"apm",-1),k=a("code",null,"apm",-1),m={href:"https://github.com/atom/settings-view/blob/master/lib/package-manager.coffee",target:"_blank",rel:"noopener noreferrer"},v=a("code",null,"settings-view",-1),b=a("div",{class:"custom-container warning"},[a("p",{class:"custom-container-title"},"WARNING"),a("p",null,[a("strong",null,"Warning:"),e(" This API should be considered pre-release and is subject to change.")])],-1),q=a("h4",{id:"authorization",tabindex:"-1"},[a("a",{class:"header-anchor",href:"#authorization","aria-hidden":"true"},"#"),e(" Authorization")],-1),f={href:"https://atom.io/account",target:"_blank",rel:"noopener noreferrer"},y=a("code",null,"Authorization",-1),_=t(`

    Media type

    All requests that take parameters require application/json.

    API Resources

    Packages
    Listing packages
    GET /api/packages

    Parameters:

    • page (optional)
    • sort (optional) - One of downloads, created_at, updated_at, stars. Defaults to downloads
    • direction (optional) - asc or desc. Defaults to desc. stars can only be ordered desc

    Returns a list of all packages in the following format:

      [
    +    {
    +      "releases": {
    +        "latest": "0.6.0"
    +      },
    +      "name": "thedaniel-test-package",
    +      "repository": {
    +        "type": "git",
    +        "url": "https://github.com/thedaniel/test-package"
    +      }
    +    },
    +    ...
    +  ]
    +

    Results are paginated 30 at a time, and links to the next and last pages are provided in the Link header:

    Link: <https://www.atom.io/api/packages?page=1>; rel="self",
    +      <https://www.atom.io/api/packages?page=41>; rel="last",
    +      <https://www.atom.io/api/packages?page=2>; rel="next"
    +

    By default, results are sorted by download count, descending.

    Searching packages

    Parameters:

    • q (required) - Search query
    • page (optional)
    • sort (optional) - One of downloads, created_at, updated_at, stars. Defaults to the relevance of the search query.
    • direction (optional) - asc or desc. Defaults to desc.

    Returns results in the same format as listing packages.

    Showing package details
    GET /api/packages/:package_name

    Returns package details and versions for a single package

    Parameters:

    `,22),x=a("strong",null,"engine",-1),w={href:"http://semver.org",target:"_blank",rel:"noopener noreferrer"},A=t(`

    Returns:

      {
    +    "releases": {
    +      "latest": "0.6.0"
    +    },
    +    "name": "thedaniel-test-package",
    +    "repository": {
    +      "type": "git",
    +      "url": "https://github.com/thedaniel/test-package"
    +    },
    +    "versions": [
    +      (see single version output below)
    +      ...,
    +    ]
    +  }
    +
    Creating a package
    POST /api/packages

    Create a new package; requires authentication.

    The name and version will be fetched from the package.json file in the specified repository. The authenticating user must have access to the indicated repository.

    Parameters:

    • repository - String. The repository containing the plugin, in the form "owner/repo"

    Returns:

    • 201 - Successfully created, returns created package.
    • 400 - Repository is inaccessible, nonexistent, not an atom package. Possible error messages include:
      • That repo does not exist, isn't an atom package, or atombot does not have access
      • The package.json at owner/repo isn't valid
    • 409 - A package by that name already exists
    Deleting a package
    DELETE /api/packages/:package_name

    Delete a package; requires authentication.

    Returns:

    • 204 - Success
    • 400 - Repository is inaccessible
    • 401 - Unauthorized
    Renaming a package

    Packages are renamed by publishing a new version with the name changed in package.json. See Creating a new package version for details.

    Requests made to the previous name will forward to the new name.

    Package Versions
    GET /api/packages/:package_name/versions/:version_name

    Returns package.json with dist key added for e.g. tarball download:

    {
    +	"bugs": {
    +		"url": "https://github.com/thedaniel/test-package/issues"
    +	},
    +	"dependencies": {
    +		"async": "~0.2.6",
    +		"pegjs": "~0.7.0",
    +		"season": "~0.13.0"
    +	},
    +	"description": "Expand snippets matching the current prefix with \`tab\`.",
    +	"dist": {
    +		"tarball": "https://codeload.github.com/..."
    +	},
    +	"engines": {
    +		"atom": "*"
    +	},
    +	"main": "./lib/snippets",
    +	"name": "thedaniel-test-package",
    +	"publishConfig": {
    +		"registry": "https://..."
    +	},
    +	"repository": {
    +		"type": "git",
    +		"url": "https://github.com/thedaniel/test-package.git"
    +	},
    +	"version": "0.6.0"
    +}
    +
    Creating a new package version
    POST /api/packages/:package_name/versions

    Creates a new package version from a git tag; requires authentication. If rename is not true, the name field in package.json must match the current package name.

    Parameters:

    • tag - A git tag for the version you'd like to create. It's important to note that the version name will not be taken from the tag, but from the version key in the package.json file at that ref. The authenticating user must have access to the package repository.
    • rename - Boolean indicating whether this version contains a new name for the package.

    Returns:

    • 201 - Successfully created. Returns created version.
    • 400 - Git tag not found / Repository inaccessible / package.json invalid
    • 409 - Version exists
    Deleting a version
    DELETE /api/packages/:package_name/versions/:version_name

    Deletes a package version; requires authentication.

    Note that a version cannot be republished with a different tag if it is deleted. If you need to delete the latest version of a package for example for security reasons, you'll need to increment the version when republishing.

    Returns 204 No Content

    Stars

    Listing user stars
    GET /api/users/:login/stars

    List a user's starred packages.

    Return value is similar to GET /api/packages

    GET /api/stars

    List the authenticated user's starred packages; requires authentication.

    Return value is similar to GET /api/packages

    Starring a package
    POST /api/packages/:name/star

    Star a package; requires authentication.

    Returns a package.

    Unstarring a package
    DELETE /api/packages/:name/star

    Unstar a package; requires authentication.

    Returns 204 No Content.

    Listing a package's stargazers
    GET /api/packages/:name/stargazers

    List the users that have starred a package.

    Returns a list of user objects:

    [{ "login": "aperson" }, { "login": "anotherperson" }]
    +

    Atom update server API

    WARNING

    Warning: This API should be considered pre-release and is subject to change.

    Atom updates

    Listing Atom updates
    GET /api/updates
    `,60),T={href:"https://github.com/Squirrel/",target:"_blank",rel:"noopener noreferrer"},j=t(`

    Returns:

    {
    +	"name": "0.96.0",
    +	"notes": "[HTML release notes]",
    +	"pub_date": "2014-05-19T15:52:06.000Z",
    +	"url": "https://www.atom.io/api/updates/download"
    +}
    +
    `,2);function P(R,E){const s=p("ExternalLinkIcon");return i(),r("div",null,[l,a("p",null,[e("This guide describes the web API used by "),a("a",d,[e("apm"),n(s)]),e(" and Atom. The vast majority of use cases are met by the "),u,e(" command-line tool, which does other useful things like incrementing your version in "),h,e(" and making sure you have pushed your git tag. In fact, Atom itself shells out to "),g,e(" rather than hitting the API directly. If you're curious about how Atom uses "),k,e(", see the "),a("a",m,[e("PackageManager class"),n(s)]),e(" in the "),v,e(" package.")]),b,q,a("p",null,[e("For calls to the API that require authentication, provide a valid token from your "),a("a",f,[e("atom.io account page"),n(s)]),e(" in the "),y,e(" header.")]),_,a("ul",null,[a("li",null,[x,e(" (optional) - Only show packages with versions compatible with this Atom version. Must be valid "),a("a",w,[e("SemVer"),n(s)]),e(".")])]),A,a("p",null,[e("Atom update feed, following the format expected by "),a("a",T,[e("Squirrel"),n(s)]),e(".")]),j])}const S=o(c,[["render",P],["__file","index.html.vue"]]);export{S as default}; diff --git a/assets/index.html.79d61236.js b/assets/index.html.79d61236.js new file mode 100644 index 0000000000..d2a910426b --- /dev/null +++ b/assets/index.html.79d61236.js @@ -0,0 +1 @@ +const t=JSON.parse('{"key":"v-132a6ac4","path":"/tag/github/","title":"github Tag","lang":"en-US","frontmatter":{"title":"github Tag","blog":{"type":"category","name":"github","key":"tag"},"layout":"Blog"},"excerpt":"","headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null}');export{t as data}; diff --git a/assets/index.html.7b64fcbd.js b/assets/index.html.7b64fcbd.js new file mode 100644 index 0000000000..85592bfbce --- /dev/null +++ b/assets/index.html.7b64fcbd.js @@ -0,0 +1,33 @@ +import{_ as r}from"./zoom.50f0dc7b.js";import{_ as l}from"./whitespace-settings.13b26c2a.js";import{_ as c}from"./package-issue-link.51bb6d85.js";import{_ as h}from"./welcome-screen-checkbox.604a29ce.js";import{_ as d}from"./update-atom-macos.07b32cce.js";import{_ as u}from"./find-and-replace-newline.08e81a06.js";import{_ as p}from"./wrap-guide-line.159c54bb.js";import{_ as m,o as g,c as f,a as e,b as t,d as o,w as b,f as n,r as i}from"./app.0e1565ce.js";const y={},w=n('

    STOP

    This is being kept for archival purposes only from the original Atom documentation. As this may no longer be relevant to Pulsar, use this at your own risk. Current Pulsar documentation is found at documentation home.

    FAQ

    The collection of Frequently Asked Questions about Atom.

    Is Atom open source?

    ',4),v={href:"https://github.com/atom/atom/blob/master/LICENSE.md",target:"_blank",rel:"noopener noreferrer"},k=e("h3",{id:"what-does-atom-cost",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#what-does-atom-cost","aria-hidden":"true"},"#"),t(" What does Atom cost?")],-1),_=e("sup",null,"th",-1),x={href:"https://github.blog/2014-05-06-atom-free-and-open-source-for-everyone/",target:"_blank",rel:"noopener noreferrer"},A=e("h3",{id:"what-platforms-does-atom-run-on",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#what-platforms-does-atom-run-on","aria-hidden":"true"},"#"),t(" What platforms does Atom run on?")],-1),I=e("p",null,"Prebuilt versions of Atom are available for OS X 10.10 or later, Windows 7 or later, RedHat Linux, and Ubuntu Linux.",-1),S={href:"https://github.com/atom/atom/blob/master/README.md#building",target:"_blank",rel:"noopener noreferrer"},T=e("h3",{id:"how-can-i-contribute-to-atom",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#how-can-i-contribute-to-atom","aria-hidden":"true"},"#"),t(" How can I contribute to Atom?")],-1),q={href:"https://flight-manual.atom.io/hacking-atom/sections/tools-of-the-trade/",target:"_blank",rel:"noopener noreferrer"},O={href:"https://github.com/atom",target:"_blank",rel:"noopener noreferrer"},W={href:"https://github.com/atom/atom/blob/master/CONTRIBUTING.md",target:"_blank",rel:"noopener noreferrer"},C=e("h3",{id:"why-does-atom-collect-usage-data",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#why-does-atom-collect-usage-data","aria-hidden":"true"},"#"),t(" Why does Atom collect usage data?")],-1),L=e("p",null,"In the same way that aggregate usage information is important when developing a web application, we've found that it's just as important for desktop applications. By knowing which Atom features are being used the most, and how the editor is performing, we can focus our development efforts in the right place. For details on what data Atom is sending or to learn how to disable metrics gathering, visit https://github.com/atom/metrics.",-1),P=e("h3",{id:"atom-in-the-cloud",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#atom-in-the-cloud","aria-hidden":"true"},"#"),t(" Atom in the cloud?")],-1),M={href:"https://github.com/atom/atom/discussions",target:"_blank",rel:"noopener noreferrer"},E=e("h3",{id:"what-s-the-difference-between-an-ide-and-an-editor",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#what-s-the-difference-between-an-ide-and-an-editor","aria-hidden":"true"},"#"),t(" What's the difference between an IDE and an editor?")],-1),j=e("p",null,[t('The term "IDE" comes from '),e("strong",null,"I"),t("ntegrated "),e("strong",null,"D"),t("evelopment "),e("strong",null,"E"),t("nvironment. It is intended as a set of tools that all work together: text editor, compiler, build or make integration, debugging, etc. Virtually all IDEs are tied specifically to a language or framework or tightly collected set of languages or frameworks. Some examples: Visual Studio for .NET and other Microsoft languages, RubyMine for Ruby, IntelliJ for Java, XCode for Apple technologies.")],-1),D={href:"https://daringfireball.net/projects/markdown/syntax",target:"_blank",rel:"noopener noreferrer"},R={href:"http://orgmode.org/",target:"_blank",rel:"noopener noreferrer"},F=e("p",null,[t("The tradeoff here is that while you can generally get off the ground faster if you're working within the realm of a given IDE, over the long term you spend a bunch of time retraining yourself when you inevitably change from one language or toolchain to the next. If you use an editor, you can continue to use the same workflows that you always have. Tools that you've built into your editor can be carried over to the next language and framework. Your editor becomes more powerful and more customized to how you want to work not just over years but potentially "),e("em",null,"decades"),t(". Just ask people who use vim or Emacs ... both of which have been available for over 25 years!")],-1),Y=e("p",null,"So, if you want something that you can just jump into and be productive right away in a specific technology, perhaps an IDE is what you're looking for. If you want a tool that you can shape and customize into exactly what you want out of it even if it costs you some time up front configuring things, then an editor is probably more your speed \u26A1",-1),z=e("h3",{id:"how-can-i-tell-if-subpixel-antialiasing-is-working",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#how-can-i-tell-if-subpixel-antialiasing-is-working","aria-hidden":"true"},"#"),t(" How can I tell if subpixel antialiasing is working?")],-1),H=e("p",null,"If you take a screenshot and blow it up you'll see something like this:",-1),B=e("p",null,[e("img",{src:r,alt:"Subpixel antialiasing enlarged"})],-1),N=e("p",null,"The text of lines 34-36 are subpixel antialiased. Line 37 is not. You can tell it is subpixel antialiased because one side of the characters will be shifted red and the other side will be shifted blue. The direction of the shift is dependent on the monitor being used.",-1),V={href:"https://en.wikipedia.org/wiki/Subpixel_rendering",target:"_blank",rel:"noopener noreferrer"},J=e("h3",{id:"why-is-atom-deleting-trailing-whitespace-why-is-there-a-newline-at-the-end-of-the-file",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#why-is-atom-deleting-trailing-whitespace-why-is-there-a-newline-at-the-end-of-the-file","aria-hidden":"true"},"#"),t(" Why is Atom deleting trailing whitespace? Why is there a newline at the end of the file?")],-1),$={href:"https://github.com/atom/whitespace",target:"_blank",rel:"noopener noreferrer"},U=e("code",null,"whitespace",-1),X={href:"http://stackoverflow.com/a/729795/1459498",target:"_blank",rel:"noopener noreferrer"},G=e("p",null,"You can disable this feature by going to the Packages list in the Settings View and finding the whitespace package:",-1),Q=e("p",null,[e("img",{src:l,alt:"Whitespace package settings",title:"Whitespace package settings"})],-1),K=n('

    What does Safe Mode do?

    Atom's Safe Mode, which can be activated by completely exiting all instances of Atom and launching it again using the command atom --safe from the command line, does the following:

    1. Does not load any packages from ~/.atom/packages or ~/.atom/dev/packages
    2. Does not run your init.coffee
    3. Loads only default-installed themes

    The intent of Safe Mode is to determine if a problem is being caused by a community package or is caused by built-in functionality of Atom. Disabling the init script was added because people tend to use the init script as a mini-package of sorts by adding code, commands and other functionality that would normally be in a package.

    ',4),Z={href:"https://pulsar-edit.dev/docs/atom-archive/hacking-atom/#debugging",target:"_blank",rel:"noopener noreferrer"},ee=e("h3",{id:"i-have-a-question-about-a-specific-atom-community-package-where-is-the-best-place-to-ask-it",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#i-have-a-question-about-a-specific-atom-community-package-where-is-the-best-place-to-ask-it","aria-hidden":"true"},"#"),t(" I have a question about a specific Atom community package. Where is the best place to ask it?")],-1),te=e("p",null,"The best place to get a question answered quickly is probably the Issues list for that specific package. You can find the Issues list for a package by going to that package's page on https://atom.io and clicking the Bugs button:",-1),oe=e("p",null,[e("img",{src:c,alt:"Bugs button link"})],-1),ae={href:"https://github.com/atom/atom/discussions",target:"_blank",rel:"noopener noreferrer"},ne=e("h3",{id:"i-m-using-an-international-keyboard-and-keys-that-use-altgr-or-ctrl-alt-aren-t-working",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#i-m-using-an-international-keyboard-and-keys-that-use-altgr-or-ctrl-alt-aren-t-working","aria-hidden":"true"},"#"),t(" I\u2019m using an international keyboard and keys that use AltGr or Ctrl+Alt aren\u2019t working")],-1),ie={href:"http://blog.atom.io/2016/10/17/the-wonderful-world-of-keyboards.html",target:"_blank",rel:"noopener noreferrer"},se=e("h3",{id:"i-m-having-a-problem-with-julia-what-do-i-do",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#i-m-having-a-problem-with-julia-what-do-i-do","aria-hidden":"true"},"#"),t(" I\u2019m having a problem with Julia! What do I do?")],-1),re={href:"http://junolab.org/",target:"_blank",rel:"noopener noreferrer"},le={href:"https://discourse.julialang.org/c/tools/juno",target:"_blank",rel:"noopener noreferrer"},ce=n(`

    I\u2019m getting an error about a \u201Cself-signed certificate\u201D. What do I do?

    This means that there is a proxy between you and our servers where someone (typically your employer) has installed a "self-signed" security certificate in the proxy. A self-signed certificate is one that isn't trusted by anyone but the person who created the certificate. Most security certificates are backed by known, trusted and certified companies. So Atom is warning you that your connection to our servers can be snooped and even hacked by whoever created the self-signed certificate. Since it is self-signed, Atom has no way of knowing who that is.

    If you decide that unsecured connections to our servers is acceptable to you, you can use the following instructions.

    DANGER

    \u{1F6A8} Danger: If you decide that unsecured connections to our servers is acceptable to you, you can use the following command:

    apm config set strict-ssl false
    +

    I\u2019m having a problem with PlatformIO! What do I do?

    `,5),he={href:"http://platformio.org/",target:"_blank",rel:"noopener noreferrer"},de={href:"https://community.platformio.org/",target:"_blank",rel:"noopener noreferrer"},ue=n(`

    How do I make Atom recognize a file with extension X as language Y?

    Atom includes a feature called "custom file types" which you can use by adding some entries into your config.cson that look like this:

    core:
    +  customFileTypes:
    +    'source.ruby': [
    +      'Cheffile'
    +      'this-is-also-ruby'
    +    ]
    +    'source.cpp': [
    +      'h'
    +    ]
    +
    `,3),pe=e("code",null,"source.ruby",-1),me={href:"http://flight-manual.atom.io/using-atom/sections/basic-customization/#finding-a-languages-scope-name",target:"_blank",rel:"noopener noreferrer"},ge=n('

    How do I make the Welcome screen stop showing up?

    You can make the Welcome screen stop showing up by unchecking this box in the welcome screen itself:

    Box to uncheck to make the Welcome screen not show next time Atom is launched

    How do I preview web page changes automatically?

    There are a couple different approaches, for example:

    ',5),fe={href:"https://atom.io/packages/browser-plus",target:"_blank",rel:"noopener noreferrer"},be={href:"https://atom.io/packages/livereload",target:"_blank",rel:"noopener noreferrer"},ye={href:"https://atom.io/packages",target:"_blank",rel:"noopener noreferrer"},we=e("h3",{id:"how-do-i-accept-input-from-my-program-or-script-when-using-the-script-package",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#how-do-i-accept-input-from-my-program-or-script-when-using-the-script-package","aria-hidden":"true"},"#"),t(" How do I accept input from my program or script when using the script package?")],-1),ve={href:"https://atom.io/packages/script",target:"_blank",rel:"noopener noreferrer"},ke={href:"https://atom.io/packages/search?q=terminal",target:"_blank",rel:"noopener noreferrer"},_e={href:"https://github.com/rgbkrk/atom-script/issues/743",target:"_blank",rel:"noopener noreferrer"},xe=n('

    I am unable to update to the latest version of Atom on macOS. How do I fix this?

    Atom shows there is a new version available but the version fails to install. You might have an error message showing a permissions error for example:

    Updating Atom on macOS

    or it will say downloading but forever loops without restarting or updating.

    You need to fix one or more of the following directories:

    • /Applications/Atom.app/
    • ~/Library/Caches/com.github.atom.ShipIt
    • ~/Library/Application Support/com.github.atom.ShipIt

    Do the following:

    1. Completely exit Atom
    2. Open a terminal
    3. Execute: whoami
    4. Write down the result of the above command, this is your user name

    And then execute these steps for each directory listed above in order:

    1. Execute: stat -f "%Su" [directory]
    2. It should output either your username or root
    3. If it says root then execute: sudo chown -R $(whoami) [directory]

    Once you've done the above for both directories, start Atom normally and attempt to update \u{1F44D}

    I\u2019m trying to change my syntax colors from styles.less, but it isn\u2019t working!

    The best way to tweak the syntax is to wrap your syntax style rules with atom-text-editor and then prepend every scope with syntax--. If you want your comments to be blue, for example, you would do the following:

    atom-text-editor {
    +	.syntax--comment {
    +		color: blue;
    +	}
    +}
    +

    How do I build or execute code I've written in Atom?

    `,15),Ae={href:"https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop",target:"_blank",rel:"noopener noreferrer"},Ie=e("ol",null,[e("li",null,"Launch Atom"),e("li",null,[t("Select the menu "),e("code",null,"View > Developer > Toggle Developer Tools")]),e("li",null,'Click the "Console" tab')],-1),Se=e("p",null,"If you're looking for a JavaScript execution environment beyond a REPL, Atom doesn't come with anything built-in for that purpose.",-1),Te=e("p",null,"If you want to build code or execute scripts from within Atom there are a number of packages available including:",-1),qe={href:"https://atom.io/packages/build",target:"_blank",rel:"noopener noreferrer"},Oe={href:"https://atom.io/packages/script",target:"_blank",rel:"noopener noreferrer"},We=e("hr",null,null,-1),Ce=e("p",null,"Resources on getting started with languages that are commonly asked about:",-1),Le={href:"https://www.python.org/about/gettingstarted/",target:"_blank",rel:"noopener noreferrer"},Pe=n(`

    How do I uninstall Atom on macOS?

    To uninstall Atom on macOS, run the following commands from the command line:

    rm -rf ~/.atom
    +rm -rf /usr/local/bin/atom
    +rm -rf /usr/local/bin/apm
    +rm -rf /Applications/Atom.app
    +rm -rf ~/Library/Preferences/com.github.atom.plist
    +rm -rf "~/Library/Application Support/com.github.atom.ShipIt"
    +rm -rf "~/Library/Application Support/Atom"
    +rm -rf "~/Library/Saved Application State/com.github.atom.savedState"
    +rm -rf ~/Library/Caches/com.github.atom
    +rm -rf ~/Library/Caches/Atom
    +

    MacOS Mojave font rendering change

    `,4),Me={href:"https://www.apple.com/macos/mojave/",target:"_blank",rel:"noopener noreferrer"},Ee={href:"https://github.com/atom/atom/issues/17486",target:"_blank",rel:"noopener noreferrer"},je=n('

    If this change is something that you dislike, there are a couple workarounds that the community has identified.

    Change the OS defaults

    1. Execute at the Terminal: defaults write -g CGFontRenderingFontSmoothingDisabled -bool NO
    2. Completely exit Atom
    3. Start Atom again

    This appears to re-enable the old "LCD font smoothing" option that was removed in Mojave. It is important to note that this is an OS-wide change.

    Change your font weight

    ',5),De={href:"https://flight-manual.atom.io/using-atom/sections/basic-customization/#style-tweaks",target:"_blank",rel:"noopener noreferrer"},Re=n(`
    atom-text-editor {
    +	font-weight: bold;
    +}
    +

    This has the benefit of being a change local to Atom only if the rest of the OS looks fine to you.

    Why does macOS say that Atom wants to access my calendar, contacts, photos, etc.?

    With macOS 10.14 Mojave, Apple introduced new privacy protections similar to the existing protections found in iOS. Whenever an application attempts to access the files inside certain newly-protected directories, macOS asks the user whether they want to allow the application to access the content in those directories. These new privacy protections apply to the directories that contain your calendars, contacts, photos, mail, messages, and Time Machine backups.

    Applications trigger these new macOS prompts when attempting to access these directories in any way. Simply attempting to list the files in one of these directories is enough to trigger these prompts. These protections even apply to Apple's own applications. For example, if you open Terminal.app and try to list the files in ~/Library/Calendars, macOS shows a prompt saying, '"Terminal" would like access to your calendar.'

    screen shot 2018-10-03 at 14 04 40 pm

    Why does Atom need access to my calendar, contacts, photos, etc.?

    `,7),Fe=e("code",null,"~",-1),Ye={href:"https://flight-manual.atom.io/getting-started/sections/atom-basics/#opening-a-file-in-a-project",target:"_blank",rel:"noopener noreferrer"},ze=e("img",{width:"1030",alt:"fuzzy-finder can trigger prompt",src:"https://user-images.githubusercontent.com/2988/46432185-9bfe9f80-c71b-11e8-83f7-758c3212d16c.png"},null,-1),He={href:"https://flight-manual.atom.io/using-atom/sections/find-and-replace/",target:"_blank",rel:"noopener noreferrer"},Be=n(`

    In addition to containing the files you're intending to edit inside Atom, your home directory also contains your files that have new OS-level protections in Mojave:

    • Calendar files (~/Library/Calendars)
    • Contacts files (~/Library/Application\\ Support/AddressBook
    • Mail files (~/Library/Mail)
    • Photos files (~/Pictures/Photos\\ Library.photoslibrary)

    Before letting Atom read these files, Mojave is understandably asking whether you want Atom to be able to access this personal data.

    What should I do when I see these prompts?

    Most people don't use Atom to view or edit their calendar files, contact files, photo library, etc. If you don't intend to use Atom to view/edit these files, then Atom doesn't need access to them. If you see a prompt from macOS saying that Atom would like to access these items, simply click Don't Allow.

    What happens if I allow Atom to access my calendar, contacts, photos, etc.?

    To Atom, these items are just files on disk. Atom treats them exactly like any other file you would view in Atom. Therefore, if you allow Atom to access these items, you'll be able to use Atom to browse the directories that contain these items, and you'll be able to view the files in those directories. That's it. Nothing more.

    You'll only be prompted once

    Fortunately, macOS will only prompt you once for each type of personal data. In other words, you might see a prompt asking you whether Atom can access your calendar, and you might see a prompt asking you whether Atom can access your contacts, but once you make those decisions, you won't see those prompts again.

    What if I change my mind?

    At any time, you can change your choices via System Preferences. Inside System Preferences, go to Security and Privacy, click the Privacy tab, and then click on Calendars to manage which apps can access your Calendars. The same goes for Contacts, Photos, etc.:

    manage access in system preferences

    What if I never want to see these prompts?

    Many people understandably expect their text editor to be able to open any file on disk. And that's exactly how things worked prior to macOS Mojave. If you would like to restore that behavior, you can proactively instruct macOS to allow you to access all files with Atom. To do so:

    1. Open your Applications folder in the Finder
    2. Open System Preferences, click the Security and Privacy icon, click the Privacy tab, and then click on Full Disk Access in the left-hand sidebar
    3. Click the lock icon to unlock System Preferences
    4. Drag Atom into Full Disk Access as shown below

    restore pre-mojave security via full-disk access

    How do I turn on line wrap?

    1. Open the Settings View using Cmd+, on macOS or Ctrl+, on other platforms
    2. Click the \u201CEditor\u201D tab on the left of the settings view
    3. Put a check in the \u201CSoft Wrap\u201D setting

    For more details about soft wrap, see: https://flight-manual.atom.io/getting-started/sections/atom-basics/#soft-wrap.

    The menu bar disappeared, how do I get it back?

    If you're running Windows or Linux and you don't see the menu bar, it may have been accidentally toggled it off. You can bring it back from the Command Palette with Window: Toggle Menu Bar or by pressing Alt.

    You can disable hiding the menu bar with Alt by unchecking Settings > Core > Auto Hide Menu Bar.

    How do I use a newline in the result of find and replace?

    To use a newline in the result of find and replace, enable the Use Regex option and use "\\n" in your replacement text. For example, given this text:

    hello, world, goodbye
    +

    If you'd like to replace the ", " with a newline so you end up with this text:

    hello
    +world
    +goodbye
    +

    In the find and replace settings, enable Use Regex, enter ", " as the find text, and enter "\\n" as the replace text:

    Find and replace with newline replace

    Then click Find All and finally, click Replace All.

    What is this line on the right in the editor view?

    Wrap guide line

    That's the wrap guide. It is a visual indicator of when your lines of code are getting too long. It defaults to the column that your Preferred Line Length is set to.

    If you want to turn it off, you can disable the wrap-guide package in the Settings View.

    ',34);function Ne(Ve,Je){const a=i("ExternalLinkIcon"),s=i("RouterLink");return g(),f("div",null,[w,e("p",null,[t("Yes, "),e("a",v,[t("Atom is licensed under the MIT license."),o(a)])]),k,e("p",null,[t("Since the 6"),_,t(" of May, 2014, "),e("a",x,[t("Atom has been available for download free of charge for everyone"),o(a)]),t(". This includes business and enterprise use.")]),A,I,e("p",null,[t("If you would like to build from source on Windows, Linux, or OS X, see the "),e("a",S,[t("Atom README"),o(a)]),t(" for more information.")]),T,e("p",null,[t("You can contribute by "),e("a",q,[t("creating a package"),o(a)]),t(" that adds something awesome to Atom!")]),e("p",null,[t("Also, if you\u2019d like to contribute to the core editor, one of the bundled packages, or one of the libraries that power Atom, just go to "),e("a",O,[t("github.com/atom"),o(a)]),t(".")]),e("p",null,[t("You should also read the "),e("a",W,[t("contributing guide"),o(a)]),t(" before getting started.")]),C,L,P,e("p",null,[t("The Atom team has no plans to make a cloud- or server-based version of Atom. For discussion of the idea, see the "),e("a",M,[t("Atom message board"),o(a)]),t(".")]),E,j,e("p",null,[t("An editor is simply that, a tool that is designed to edit text. Typically they are optimized for programming languages though many programmer's text editors are branching out and adding features for non-programming text like "),e("a",D,[t("Markdown"),o(a)]),t(" or "),e("a",R,[t("Org Mode"),o(a)]),t(". The key here is that text editors are designed to work with whatever language or framework you choose.")]),F,Y,z,H,B,N,e("p",null,[t("You can find more information on "),e("a",V,[t("subpixel rendering on Wikipedia"),o(a)]),t(".")]),J,e("p",null,[t("Atom ships with the "),e("a",$,[U,t(" package"),o(a)]),t(", which by default strips trailing whitespace from lines in your file, and inserts a final trailing newline to indicate end-of-file "),e("a",X,[t("as per the POSIX standard"),o(a)]),t(".")]),G,Q,e("p",null,[t("Take a look at "),o(s,{to:"/using-atom/sections/editing-and-deleting-text/#whitespace"},{default:b(()=>[t("the Whitespace section")]),_:1}),t(" for more information.")]),K,e("p",null,[t("For more information on Safe Mode, check the "),e("a",Z,[t("debugging section"),o(a)]),t(".")]),ee,te,oe,e("p",null,[t("And you can always ask Atom-related questions in the "),e("a",ae,[t("official Atom message board"),o(a)]),t(". Someone here may know the answer! It's just with over 3,500 packages (as of early February 2016), the forum members may not know all answers for all packages \u{1F600}")]),ne,e("p",null,[t("As of Atom v1.12, a fix is available for this. See "),e("a",ie,[t('the blog post "The Wonderful World of Keyboards"'),o(a)]),t(" for more information.")]),se,e("p",null,[e("a",re,[t("Juno"),o(a)]),t(" is a development environment built on top of Atom but has enough separate customizations that they have their own "),e("a",le,[t("message board"),o(a)]),t(". You will probably have better luck asking your question there.")]),ce,e("p",null,[e("a",he,[t("PlatformIO"),o(a)]),t(" is a development environment built on top of Atom but has enough separate customizations that they have their own "),e("a",de,[t("message board"),o(a)]),t(". If your question has to do with PlatformIO specifically, you may have better luck getting your answer there.")]),ue,e("p",null,[t("The key (for example "),pe,t(" in the above snippet) is the language's "),e("a",me,[t("scope name"),o(a)]),t(". The value is an array of file extensions, without the period, to match to that scope name.")]),ge,e("ul",null,[e("li",null,[e("a",fe,[t("browser-plus"),o(a)]),t(" gives a reasonably full browser implementation within Atom")]),e("li",null,[e("a",be,[t("livereload"),o(a)]),t(" gives you a preview in any browser, but requires you to save the file first.")])]),e("p",null,[t("Other packages may be available now, you can search for Atom packages on the "),e("a",ye,[t("packages site"),o(a)]),t(".")]),we,e("p",null,[t("The "),e("a",ve,[t("script package"),o(a)]),t(" doesn't support accepting input from the user in the scripts it runs. The option with the best chance of success is to run the script or program from the terminal that comes with your operating system. If that isn't something you want to do, you could try one of the many "),e("a",ke,[t("terminal packages"),o(a)]),t(" that are available.")]),e("p",null,[t("See "),e("a",_e,[t("rgbkrk/atom-script#743"),o(a)]),t(" for details.")]),xe,e("p",null,[t("Atom doesn't have built-in support for building any type of code nor does it have built-in support for executing any kind of code other than JavaScript. Atom has a JavaScript interactive command-line (also known as a "),e("a",Ae,[t("REPL"),o(a)]),t(") available through the Developer Tools. You can access the JavaScript REPL by using the following steps:")]),Ie,Se,Te,e("ul",null,[e("li",null,[e("a",qe,[t("build"),o(a)])]),e("li",null,[e("a",Oe,[t("script"),o(a)])])]),We,Ce,e("ul",null,[e("li",null,[e("a",Le,[t("Python"),o(a)])])]),Pe,e("p",null,[t("In "),e("a",Me,[t("macOS Mojave v10.14.x"),o(a)]),t(', Apple disabled subpixel antialiasing on all monitors by default. Previous to Mojave, subpixel antialiasing was disabled only on Retina displays or on all displays if the "LCD font smoothing" option was disabled in System Preferences. With this change in Mojave, some users have reported that '),e("a",Ee,[t('their fonts in Atom appear "thinner" or "dimmer" than they did previously.'),o(a)]),t(" It can look better or worse depending on your font and theme selections, but in all cases this is completely a side-effect of the change that Apple made to their font rendering and is outside Atom's and Electron's control.")]),je,e("p",null,[t("Add the following to "),e("a",De,[t("your stylesheet"),o(a)]),t(":")]),Re,e("p",null,[t("Atom doesn't need access to these items, but you might unintentionally cause Atom to try to access these items. This commonly occurs when you open your home directory ("),Fe,t(") inside Atom and run a command that examines all files and directories beneath your home directory. For example, when you open the "),e("a",Ye,[t("fuzzy-finder"),o(a)]),t(", it indexes the currently-open directory so that it can show you the available files:")]),ze,e("p",null,[t("Similarly, using "),e("a",He,[t("find-and-replace"),o(a)]),t(" across the entire home directory will cause Atom to scan all files under your home directory.")]),Be])}const tt=m(y,[["render",Ne],["__file","index.html.vue"]]);export{tt as default}; diff --git a/assets/index.html.7ceb711f.js b/assets/index.html.7ceb711f.js new file mode 100644 index 0000000000..47a448a17e --- /dev/null +++ b/assets/index.html.7ceb711f.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-0b77a069","path":"/tag/backend/","title":"backend Tag","lang":"en-US","frontmatter":{"title":"backend Tag","blog":{"type":"category","name":"backend","key":"tag"},"layout":"Blog"},"excerpt":"","headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null}');export{e as data}; diff --git a/assets/index.html.817cae06.js b/assets/index.html.817cae06.js new file mode 100644 index 0000000000..2b93019bb2 --- /dev/null +++ b/assets/index.html.817cae06.js @@ -0,0 +1,70 @@ +import{_ as h,a as u,b as p}from"./windows-downloads.e9b59e10.js";import{_ as g}from"./windows-system-settings.141561e2.js";import{_ as f,a as b,b as y,c as w,d as v,e as _,f as k,g as x}from"./finder.04f876a5.js";import{_ as A}from"./platform-selector.697b4dd8.js";import{_ as S,o as T,c as C,d as o,w as a,a as e,b as t,f as r,r as m}from"./app.0e1565ce.js";const I={},P=r('

    STOP

    This is being kept for archival purposes only from the original Atom documentation. As this may no longer be relevant to Pulsar, you use this at your own risk. Current Pulsar documentation for this section is found at the documentation home.

    Getting Started

    This chapter is about getting started with Atom.

    Why Atom?

    There are a lot of text editors out there; why should you spend your time learning about and using Atom?

    Editors like Sublime and TextMate offer convenience but only limited extensibility. On the other end of the spectrum, Emacs and Vim offer extreme flexibility, but they aren't very approachable and can only be customized with special-purpose scripting languages.

    We think we can do better. Our goal is a zero-compromise combination of hackability and usability: an editor that will be welcoming to an elementary school student on their first day learning to code, but also a tool they won't outgrow as they develop into seasoned hackers.

    As we've used Atom to build Atom, what began as an experiment has gradually matured into a tool we can't live without. On the surface, Atom is the modern desktop text editor you've come to expect. Pop the hood, however, and you'll discover a system begging to be hacked on.

    The Nucleus of Atom

    The web is not without its faults, but two decades of development has forged it into an incredibly malleable and powerful platform. So when we set out to write a text editor that we ourselves would want to extend, web technology was the obvious choice. But first, we had to free it from its chains.

    The Native Web

    Web browsers are great for browsing web pages, but writing code is a specialized activity that warrants dedicated tools. More importantly, the browser severely restricts access to the local system for security reasons, and for us, a text editor that couldn't write files or run local subprocesses was a non-starter.

    For this reason, we didn't build Atom as a traditional web application. Instead, Atom is a specialized variant of Chromium designed to be a text editor rather than a web browser. Every Atom window is essentially a locally-rendered web page.

    All the APIs available to a typical Node.js application are also available to the code running in each window's JavaScript context. This hybrid provides a unique client-side development experience.

    Since everything is local, you don't have to worry about asset pipelines, script concatenation, and asynchronous module definitions. If you want to load some code, just require it at the top of your file. Node's module system makes it easy to break the system down into lots of small, focused packages.

    JavaScript, Meet C++

    Interacting with native code is also really simple. For example, we wrote a wrapper around the Oniguruma regular expression engine for our TextMate grammar support. In a browser, that would have required adventures with NaCl or Esprima. Node integration made it easy.

    In addition to the Node APIs, we also expose APIs for native dialogs, adding application and context menu items, manipulating the window dimensions, etc.

    Web Tech: The Fun Parts

    Another great benefit, that comes with writing code for Atom, is the guarantee that it's running on the newest version of Chromium. That means we can ignore issues like browser compatibility and polyfills. We can use all the web's shiny features of tomorrow, today.

    For example, the layout of our workspace and panes is based on flexbox. It's an emerging standard and has gone through a lot of change since we started using it, but none of that mattered as long as it worked.

    With the entire industry pushing web technology forward, we're confident that we're building Atom on fertile ground. Native UI technologies come and go, but the web is a standard that becomes more capable and ubiquitous with every passing year. We're excited to dig deeper into its toolbox.

    An Open-Source Text Editor

    We see Atom as a perfect complement to GitHub's primary mission of building better software by working together. Atom is a long-term investment, and GitHub will continue to support its development with a dedicated team going forward. But we also know that we can't achieve our vision for Atom alone. As Emacs and Vim have demonstrated over the past three decades, if you want to build a thriving, long-lasting community around a text editor, it has to be open source.

    The entire Atom editor is free and open source and is available under the https://github.com/atom organization.

    Installing Atom

    To get started with Atom, we'll need to get it on your system. This section will go over installing Atom on your system, as well as the basics of how to build it from source.

    Installing Atom should be fairly simple. Generally, you can go to https://atom.io and you should see a download button as shown here:

    ',28),O=e("p",null,[e("img",{src:h,alt:"Download buttons on https://atom.io",title:"Download buttons on https://atom.io"})],-1),z=e("p",null,[e("img",{src:u,alt:"Download buttons on https://atom.io",title:"Download buttons on https://atom.io"})],-1),F=e("p",null,[e("img",{src:p,alt:"Download buttons on https://atom.io",title:"Download buttons on https://atom.io"})],-1),$=e("p",null,"The button or buttons should be specific to your platform and the download package should be easily installable. However, let's go over them here in a bit of detail.",-1),Y=e("h4",{id:"installing-atom-on-linux",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#installing-atom-on-linux","aria-hidden":"true"},"#"),t(" Installing Atom on Linux")],-1),E=e("p",null,"You can install Atom on Linux using your distribution's package manager by configuring it to use one of our official package repositories. This will also enable you to update Atom when new releases are published.",-1),N=e("h5",{id:"debian-and-ubuntu-deb-apt",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#debian-and-ubuntu-deb-apt","aria-hidden":"true"},"#"),t(" Debian and Ubuntu (deb/apt)")],-1),W=e("p",null,"To install Atom on Debian, Ubuntu, or related distributions, add our official package repository to your system by running the following commands:",-1),U=e("div",{class:"language-command-line ext-command-line line-numbers-mode"},[e("pre",{class:"language-command-line"},[e("code",null,`$ wget -qO - https://packagecloud.io/AtomEditor/atom/gpgkey | sudo apt- +key add - +$ sudo sh -c 'echo "deb [arch=amd64] https://packagecloud.io/AtomEditor/atom/any/ any main" > /etc/apt/sources.list.d/atom.list' +$ sudo apt-get update +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),q=e("p",null,[t("You can now install Atom using "),e("code",null,"apt-get"),t(" (or "),e("code",null,"apt"),t(" on Ubuntu):")],-1),V=e("div",{class:"language-command-line ext-command-line line-numbers-mode"},[e("pre",{class:"language-command-line"},[e("code",null,`# Install Atom +$ sudo apt-get install atom + +# Install Atom Beta +$ sudo apt-get install atom-beta +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),j={href:"https://atom.io/download/deb",target:"_blank",rel:"noopener noreferrer"},M=e("div",{class:"language-command-line ext-command-line line-numbers-mode"},[e("pre",{class:"language-command-line"},[e("code",null,`# Install Atom +$ sudo apt install ./atom-amd64.deb +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),L=e("h5",{id:"red-hat-and-centos-yum-or-fedora-dnf",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#red-hat-and-centos-yum-or-fedora-dnf","aria-hidden":"true"},"#"),t(" Red Hat and CentOS (YUM), or Fedora (DNF)")],-1),B=e("p",null,"To install Atom on CentOS, Oracle Linux, Red Hat Enterprise Linux, Scientific Linux, Fedora, or related distributions that use the YUM or DNF package managers, add our official package repository to your system by running the following commands:",-1),D=e("div",{class:"language-command-line ext-command-line line-numbers-mode"},[e("pre",{class:"language-command-line"},[e("code",null,`$ sudo rpm --import https://packagecloud.io/AtomEditor/atom/gpgkey +$ sudo sh -c 'echo -e "[Atom]\\nname=Atom Editor\\nbaseurl= +https://packagecloud.io/AtomEditor/atom/el/7/ +\\$basearch\\nenabled=1\\ngpgcheck=0\\nrepo_gpgcheck=1\\ngpgkey= +https://packagecloud.io/AtomEditor/atom/gpgkey" > /etc/yum.repos.d/ +atom.repo' +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),H=e("p",null,[t("You can now install Atom using "),e("code",null,"dnf"),t(" (or "),e("code",null,"yum"),t(" depending on your distribution):")],-1),G=e("div",{class:"language-command-line ext-command-line line-numbers-mode"},[e("pre",{class:"language-command-line"},[e("code",null,`# Install Atom +$ sudo dnf install atom + +# Install Atom Beta +$ sudo dnf install atom-beta +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),R={href:"https://atom.io/download/rpm",target:"_blank",rel:"noopener noreferrer"},J=e("div",{class:"language-command-line ext-command-line line-numbers-mode"},[e("pre",{class:"language-command-line"},[e("code",null,`# On YUM-based distributions +$ sudo yum install -y atom.x86_64.rpm + +# On DNF-based distributions +$ sudo dnf install -y atom.x86_64.rpm +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),K=e("h5",{id:"suse-zypp",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#suse-zypp","aria-hidden":"true"},"#"),t(" SUSE (zypp)")],-1),X=e("p",null,"To install Atom on openSUSE or other distributions that use the Zypp package manager, add our official package repository to your system by running the following commands:",-1),Z=e("div",{class:"language-command-line ext-command-line line-numbers-mode"},[e("pre",{class:"language-command-line"},[e("code",null,`$ sudo sh -c 'echo -e "[Atom]\\nname=Atom Editor\\nbaseurl= +https://packagecloud.io/AtomEditor/atom/el/7/\\$basearch\\nenabled=1\\ntype=rpm- +md\\ngpgcheck=0\\nrepo_gpgcheck=1\\ +ngpgkey=https://packagecloud.io/AtomEditor/atom/gpgkey" > +/etc/zypp/repos.d/atom.repo' +$ sudo zypper --gpg-auto-import-keys refresh +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),Q=e("p",null,[t("You can now install Atom using "),e("code",null,"zypper"),t(":")],-1),ee=e("div",{class:"language-command-line ext-command-line line-numbers-mode"},[e("pre",{class:"language-command-line"},[e("code",null,`# Install Atom +$ sudo zypper install atom + +# Install Atom Beta +$ sudo zypper install atom-beta +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),te={href:"https://atom.io/download/rpm",target:"_blank",rel:"noopener noreferrer"},oe=e("div",{class:"language-command-line ext-command-line line-numbers-mode"},[e("pre",{class:"language-command-line"},[e("code",null,`$ sudo zypper in -y atom.x86_64.rpm +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"})])],-1),ae=e("h4",{id:"installing-atom-on-mac",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#installing-atom-on-mac","aria-hidden":"true"},"#"),t(" Installing Atom on Mac")],-1),ne={href:"https://github.com/atom/atom/releases/latest",target:"_blank",rel:"noopener noreferrer"},ie=e("code",null,"atom-mac.zip",-1),se=e("code",null,"Atom",-1),le=e("p",null,[t("When you first open Atom, it will try to install the "),e("code",null,"atom"),t(" and "),e("code",null,"apm"),t(" commands for use in the terminal. In some cases, Atom might not be able to install these commands because it needs an administrator password. To check if Atom was able to install the "),e("code",null,"atom"),t(" command, for example, open a terminal window and type "),e("code",null,"which atom"),t(". If the "),e("code",null,"atom"),t(" command has been installed, you'll see something like this:")],-1),re=e("div",{class:"language-command-line ext-command-line line-numbers-mode"},[e("pre",{class:"language-command-line"},[e("code",null,`$ which atom +> /usr/local/bin/atom +$ +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),de=e("p",null,[t("If the "),e("code",null,"atom"),t(" command wasn't installed, the "),e("code",null,"which"),t(" command won't return anything:")],-1),ce=e("div",{class:"language-command-line ext-command-line line-numbers-mode"},[e("pre",{class:"language-command-line"},[e("code",null,`$ which atom +$ +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),me=e("p",null,[t("To install the "),e("code",null,"atom"),t(" and "),e("code",null,"apm"),t(' commands, run "Window: Install Shell Commands" from the '),e("a",{href:"/getting-started/sections/atom-basics#command-palette"},"Command Palette"),t(", which will prompt you for an administrator password.")],-1),he=e("h4",{id:"installing-atom-on-windows",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#installing-atom-on-windows","aria-hidden":"true"},"#"),t(" Installing Atom on Windows")],-1),ue={href:"https://github.com/atom/atom/releases/latest",target:"_blank",rel:"noopener noreferrer"},pe=e("code",null,"AtomSetup.exe",-1),ge=e("code",null,"AtomSetup-x64.exe",-1),fe=e("code",null,"atom",-1),be=e("code",null,"apm",-1),ye=e("code",null,"PATH",-1),we=e("p",null,[e("img",{src:g,alt:"Atom on Windows"})],-1),ve=e("p",null,[t("The context menu "),e("code",null,"Open with Atom"),t(" in File Explorer, and the option to make Atom available for file association using "),e("code",null,"Open with..."),t(", is controlled by the System Settings panel as seen above.")],-1),_e=e("p",null,[t("With Atom open, click on "),e("code",null,"File > Settings"),t(", and then the "),e("code",null,"System"),t(" tab on the left. Check the boxes next to "),e("code",null,"Show in file context menus"),t(", as well as "),e("code",null,"Show in folder context menus"),t(". And you\u2019re all set.")],-1),ke=e("h4",{id:"updating-atom",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#updating-atom","aria-hidden":"true"},"#"),t(" Updating Atom")],-1),xe=e("p",null,"You should consider updating Atom periodically for the latest improvements to the software. Additionally, When Atom receives hotfixes for security vulnerabilities you will want to update your version of Atom as soon as possible.",-1),Ae=e("code",null,".rpm",-1),Se=e("code",null,".deb",-1),Te={href:"https://flight-manual.atom.io/getting-started/sections/installing-atom/#installing-atom-on-linux",target:"_blank",rel:"noopener noreferrer"},Ce={href:"https://flight-manual.atom.io/getting-started/sections/atom-basics/#settings-and-preferences",target:"_blank",rel:"noopener noreferrer"},Ie=e("p",null,"To perform a manual update:",-1),Pe=e("li",null,[t("Click on the "),e("code",null,"Atom > Check for Update"),t(" menu item in the menu bar.")],-1),Oe=e("code",null,"Application: About",-1),ze={href:"https://flight-manual.atom.io/getting-started/sections/atom-basics/#command-palette",target:"_blank",rel:"noopener noreferrer"},Fe=e("code",null,"Check now",-1),$e=e("p",null,"Atom will begin to update if an update is available.",-1),Ye={href:"https://flight-manual.atom.io/getting-started/sections/atom-basics/#settings-and-preferences",target:"_blank",rel:"noopener noreferrer"},Ee=e("p",null,"To perform a manual update:",-1),Ne=e("li",null,[t("Click on the "),e("code",null,"Help > Check for Update"),t(" menu item in the menu bar.")],-1),We=e("code",null,"Application: About",-1),Ue={href:"https://flight-manual.atom.io/getting-started/sections/atom-basics/#command-palette",target:"_blank",rel:"noopener noreferrer"},qe=e("code",null,"Check now",-1),Ve=e("p",null,"Atom will begin to update if an update is available.",-1),je=e("h4",{id:"portable-mode",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#portable-mode","aria-hidden":"true"},"#"),t(" Portable Mode")],-1),Me=e("p",null,[t("Atom stores configuration and state in a "),e("code",null,".atom"),t(" directory usually located in your home directory "),e("span",{class:"platform-windows"},[t("("),e("code",null,"%userprofile%"),t(" on Windows)")]),t(". You can however run Atom in portable mode where both the app and the configuration are stored together such as on a removable storage device.")],-1),Le={href:"https://github.com/atom/atom/releases/latest",target:"_blank",rel:"noopener noreferrer"},Be=e("p",null,[t("Then create a "),e("code",null,".atom"),t(" directory alongside the directory that contains the Atom binary, for example:")],-1),De=e("div",{class:"language-text ext-text line-numbers-mode"},[e("pre",{class:"language-text"},[e("code",null,`/media/myusb/atom-1.14/atom +/media/myusb/.atom +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),He=e("p",null,[t("Then create a "),e("code",null,".atom"),t(" directory alongside the Atom.app application, for example:")],-1),Ge=e("div",{class:"language-text ext-text line-numbers-mode"},[e("pre",{class:"language-text"},[e("code",null,`/MyUSB/Atom.app +/MyUSB/.atom +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),Re=e("p",null,[t("Then create a "),e("code",null,".atom"),t(" directory alongside the directory that contains atom.exe, for example:")],-1),Je=e("div",{class:"language-text ext-text line-numbers-mode"},[e("pre",{class:"language-text"},[e("code",null,`e:\\atom-1.14\\atom.exe +e:\\.atom +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),Ke=r('
    Portable Notes
    • The .atom directory must be writeable
    • You can move an existing .atom directory to your portable device
    • Atom can also store its Electron user data in your .atom directory - just create a subdirectory called electronUserData inside .atom
    • Alternatively you can set the ATOM_HOME environment variable to point wherever you want (you can write a .sh or .cmd script to temporarily set it and launch it from that)
    • Portable mode installations will not automatically update

    Building Atom from Source

    ',3),Xe=r(`

    Proxy and Firewall Settings

    Behind a Firewall?

    If you are behind a firewall and seeing SSL errors when installing packages you can disable strict SSL by running:

    $ apm config set strict-ssl false
    +
    Using a Proxy?

    If you are using a HTTP(S) proxy you can configure apm to use it by running:

    $ apm config set https-proxy <em>YOUR_PROXY_ADDRESS</em>
    +

    You can run apm config get https-proxy to verify it has been set correctly.

    Atom Basics

    Now that Atom is installed on your system, let's fire it up, configure it and get acquainted with the editor.

    When you launch Atom for the first time, you should get a screen that looks like this:

    Atom's welcome screen

    This is the Atom welcome screen and gives you a pretty good starting point for how to get started with the editor.

    Terminology

    ',14),Ze=r('

    Command Palette

    In that welcome screen, we are introduced to probably the most important command in Atom, the Command Palette. If you press Cmd+Shift+PCtrl+Shift+P while focused in an editor pane, the command palette will pop up.

    Note:

    Throughout the book, we will use shortcut keybindings like Cmd+Shift+PCtrl+Shift+P to demonstrate how to run a command. These are the default keybindings for the platform that we detected you running.

    If you want to see a different platform than the one we detected, you may choose a different one by using the platform selector near the top of the page:

    Platform Selector

    If the Platform Selector is not present, then the current page doesn't have any platform-specific content.

    If you have customized your Atom keymap, you can always see the keybinding you have mapped in the Command Palette or the Keybindings tab in the Settings View.

    This search-driven menu can do just about any major task that is possible in Atom. Instead of clicking around all the application menus to look for something, you can press Cmd+Shift+PCtrl+Shift+P and search for the command.

    Command Palette

    Not only can you see and quickly search through thousands of possible commands, but you can also see if there is a keybinding associated with it. This is great because it means you can guess your way to doing interesting things while also learning the shortcut keystrokes for doing it.

    For the rest of the book, we will try to be clear as to the text you can search for in the Command Palette in addition to the keybinding for different commands.

    Settings and Preferences

    Atom has a number of settings and preferences you can modify in the Settings View.

    Settings View

    This includes things like changing the theme, specifying how to handle wrapping, font settings, tab size, scroll speed and much more. You can also use this screen to install new packages and themes, which we'll cover in Atom Packages.

    To open the Settings View, you can: ::: tabs Settings View

    ',14),Qe=r('
    • Use the File > Settings menu item in the menu bar

    :::

    • Search for settings-view:open in the Command Palette
    • Use the Cmd+, or Ctrl+, keybinding
    Changing the Theme

    The Settings View also lets you change the themes for Atom. Atom ships with 4 different UI themes, dark and light variants of the Atom and One theme, as well as 8 different syntax themes. You can modify the active theme by clicking on the Themes tab in the sidebar of the Settings View, or you can install new themes by clicking the Install tab.

    Changing the theme from the Settings View

    The UI themes control the style of UI elements like the tabs and the tree view, while the syntax themes control the syntax highlighting of text you load into the editor. To change the syntax or UI theme, simply pick something different in the appropriate dropdown list.

    There are also dozens of themes on https://atom.io that you can choose from if you want something different. We will cover customizing a theme in Style Tweaks and creating your own theme in Creating a Theme.

    Soft Wrap

    You can use the Settings View to specify your whitespace and wrapping preferences.

    Whitespace and wrapping preferences settings

    Enabling "Soft Tabs" will insert spaces instead of actual tab characters when you press the Tab key and the "Tab Length" setting specifies how many spaces to insert when you do so, or how many spaces are used to represent a tab if "Soft Tabs" is disabled.

    The "Soft Wrap" option will wrap lines that are too long to fit in your current window. If soft wrapping is disabled, the lines will simply run off the side of the screen and you will have to scroll the window to see the rest of the content. If "Soft Wrap At Preferred Line Length" is toggled, the lines will wrap at 80 characters instead of the end of the screen. You can also change the default line length to a value other than 80 on this screen.

    ',13),et=r('

    Opening, Modifying, and Saving Files

    Now that your editor is looking and acting how you want, let's start opening up and editing files. This is a text editor after all, right?

    Opening a File

    There are several ways to open a file in Atom. You can do it by choosing File > Open from the menu bar or by pressing Cmd+O Ctrl+O to choose a file from the standard dialog.

    Open file by dialog

    This is useful for opening a file that is not contained in the project you're currently in (more on that next), or if you're starting from a new window for some reason.

    ',6),tt=e("code",null,"atom",-1),ot={class:"platform-mac"},at=e("code",null,"atom",-1),nt=e("code",null,"apm",-1),it={class:"platform-windows platform-linux"},st=e("code",null,"atom",-1),lt=e("code",null,"apm",-1),rt=r(`

    You can run the atom command with one or more file paths to open up those files in Atom.

    $ atom --help
    +> Atom Editor v1.8.0
    +
    +> Usage: atom [options] [path ...]
    +
    +> One or more paths to files or folders may be specified. If there is an
    +> existing Atom window that contains all of the given folders, the paths
    +> will be opened in that window. Otherwise, they will be opened in a new
    +> window.
    +
    +> ...
    +

    This is a great tool if you're used to the terminal or you work from the terminal a lot. Just fire off atom [files] and you're ready to start editing. You can even open a file at a certain line (and optionally column) so the cursor will be positioned exactly where you want. For example, you may search some keyword in a repository to find the line you want to edit:

    $ git grep -n 'Opening a File$'
    +content/getting-started/sections/atom-basics.md:84:##### Opening a File
    +

    and then jump to the beginning of that line by appending a colon and the line number to the file path:

    $ atom content/getting-started/sections/atom-basics.md:84
    +

    Sometimes you may want the cursor to jump to the exact column position of the searched keyword. Just append another colon plus the column number:

    $ git grep -n --column 'Windows Explorer'
    +content/getting-started/sections/atom-basics.md:150:722
    +$ atom content/getting-started/sections/atom-basics.md:150:722
    +
    Editing and Saving a File
    `,9),dt={href:"https://atom.io/packages",target:"_blank",rel:"noopener noreferrer"},ct=r('

    To save a file you can choose File > Save from the menu bar or Cmd+SCtrl+S to save the file. If you choose File > Save As or press Cmd+Shift+SCtrl+Shift+S then you can save the current content in your editor under a different file name. Finally, you can choose File > Save All or press Alt+Cmd+S to save all the open files in Atom.

    Opening Directories

    Atom doesn't just work with single files though; you will most likely spend most of your time working on projects with multiple files. To open a directory, choose the menu item File > OpenFile > Open Folder and select a directory from the dialog. You can also add more than one directory to your current Atom window, by choosing File > Add Project Folder from the menu bar or pressing Cmd+Shift+OCtrl+Shift+A.

    You can open any number of directories from the command line by passing their paths to the atom command line tool. For example, you could run the command atom ./hopes ./dreams to open both the hopes and the dreams directories at the same time.

    When you open Atom with one or more directories, you will automatically get a Tree View on the side of your window.

    Tree View in an open project

    The Tree View allows you to explore and modify the file and directory structure of your project. You can open, rename, delete and create new files from this view.

    You can also hide and show it with Cmd+\\Ctrl+\\ or the tree-view: toggle command from the Command Palette, and Ctrl+0 Alt+\\ will focus it. When the Tree view has focus you can press A, M, or Delete to add, move or delete files and folders. You can also right-click on a file or folder in the Tree view to see many of the various options, including all of these plus showing the file in FinderWindows Exploreryour native filesystem or copying the file path to the clipboard.

    Note

    Atom Packages

    Like many parts of Atom, the Tree View is not built directly into the editor, but is its own standalone package that is shipped with Atom by default. Packages that are bundled with Atom are referred to as Core packages. Ones that aren't bundled with Atom are referred to as Community packages.

    You can find the source code to the Tree View on GitHub at https://github.com/atom/tree-view.

    This is one of the interesting things about Atom. Many of its core features are actually just packages implemented the same way you would implement any other functionality. This means that if you don't like the Tree View for example, you could write your own implementation of that functionality and replace it entirely.

    Opening a File in a Project

    Once you have a project open in Atom, you can easily find and open any file within that project.

    If you press Cmd+TCtrl+T or Cmd+PCtrl+P, the Fuzzy Finder will pop up. This will let you quickly search for any file in your project by typing parts of the path.

    Opening files with the Fuzzy Finder

    You can also search through only the files currently opened (rather than every file in your project) with Cmd+BCtrl+B. This searches through your "buffers" or open files. You can also limit this fuzzy search with Cmd+Shift+BCtrl+Shift+B, which searches only through the files which are new or have been modified since your last Git commit.

    ',14),mt=e("code",null,"core.ignoredNames",-1),ht=e("code",null,"fuzzy-finder.ignoredNames",-1),ut=e("code",null,"core.excludeVCSIgnoredPaths",-1),pt={href:"https://git-scm.com/docs/gitignore",target:"_blank",rel:"noopener noreferrer"},gt=e("code",null,".gitignore",-1),ft=e("code",null,"core.ignoredNames",-1),bt=e("code",null,"fuzzy-finder.ignoredNames",-1),yt={href:"https://github.com/isaacs/minimatch",target:"_blank",rel:"noopener noreferrer"},wt=r('

    Tips

    Configuration Setting Notation

    Sometimes you'll see us refer to configuration settings all spelled out like "Ignored Names in Core Settings". Other times you'll see us use the shorthand name like core.ignoredNames. Both of these refer to the same thing. The shorthand is the package name, then a dot ., followed by the "camel-cased" name of the setting.

    If you have a phrase you want to camel-case, follow these steps:

    1. Lowercase the first word
    2. Capitalize the first letter in all other words
    3. Remove the spaces

    So "Ignored Names" becomes "ignoredNames".

    Summary

    You should now have a basic understanding of what Atom is and what you want to do with it. You should also have it installed on your system and be able to use it for the most basic text editing operations.

    Now you're ready to start digging into the fun stuff.

    ',4);function vt(_t,kt){const c=m("Tabs"),n=m("ExternalLinkIcon"),d=m("RouterLink");return T(),C("div",null,[P,o(c,{id:"89",data:[{title:"Linux"},{title:"Mac"},{title:"Windows"}],"tab-id":"installing-atom"},{tab0:a(({title:i,value:s,isActive:l})=>[O]),tab1:a(({title:i,value:s,isActive:l})=>[z]),tab2:a(({title:i,value:s,isActive:l})=>[F]),_:1}),$,o(c,{id:"109",data:[{title:"Linux"},{title:"Mac"},{title:"Windows"}],"tab-id":"installing-atom"},{tab0:a(({title:i,value:s,isActive:l})=>[Y,E,N,W,U,q,V,e("p",null,[t("Alternatively, you can download the "),e("a",j,[t("Atom .deb package"),o(n)]),t(" and install it directly:")]),M,L,B,D,H,G,e("p",null,[t("Alternatively, you can download the "),e("a",R,[t("Atom .rpm package"),o(n)]),t(" and install it directly:")]),J,K,X,Z,Q,ee,e("p",null,[t("Alternatively, you can download the "),e("a",te,[t("Atom .rpm package"),o(n)]),t(" and install it directly:")]),oe]),tab1:a(({title:i,value:s,isActive:l})=>[ae,e("p",null,[t("Atom follows the standard Mac zip installation process. You can either press the download button from the https://atom.io site or you can go to the "),e("a",ne,[t("Atom releases page"),o(n)]),t(" to download the "),ie,t(" file explicitly. Once you have that file, you can click on it to extract the application and then drag the new "),se,t(' application into your "Applications" folder.')]),le,re,de,ce,me]),tab2:a(({title:i,value:s,isActive:l})=>[he,e("p",null,[t("Atom is available with Windows installers that can be downloaded from https://atom.io or from the "),e("a",ue,[t("Atom releases page"),o(n)]),t(". Use "),pe,t(" for 32-bit systems and "),ge,t(" for 64-bit systems. This setup program will install Atom, add the "),fe,t(" and "),be,t(" commands to your "),ye,t(", and create shortcuts on the desktop and in the start menu.")]),we,ve,_e]),_:1}),ke,xe,o(c,{id:"206",data:[{title:"Linux"},{title:"Mac"},{title:"Windows"}],"tab-id":"installing-atom"},{tab0:a(({title:i,value:s,isActive:l})=>[e("p",null,[t("If you are using Atom's official package repositories, use your distribution's package manager to update Atom. Otherwise, you will need to manually download and install the latest "),Ae,t(" or "),Se,t(" package from https://atom.io. For more details, see "),e("a",Te,[t("Installing Atom on Linux."),o(n)])])]),tab1:a(({title:i,value:s,isActive:l})=>[e("p",null,[t('"Automatically Update" is enabled by default in Core Settings of the '),e("a",Ce,[t("Settings View"),o(n)]),t(", which will allow Atom to check for updates automatically. If you disable this setting you can update Atom manually.")]),Ie,e("ul",null,[Pe,e("li",null,[t("Search for "),Oe,t(" in the "),e("a",ze,[t("Command Palette"),o(n)]),t(" and click the "),Fe,t(" button.")])]),$e]),tab2:a(({title:i,value:s,isActive:l})=>[e("p",null,[t('"Automatically Update" is enabled by default in Core Settings of the '),e("a",Ye,[t("Settings View"),o(n)]),t(", which will allow Atom to check for updates automatically. If you disable this setting you can update Atom manually.")]),Ee,e("ul",null,[Ne,e("li",null,[t("Search for "),We,t(" in the "),e("a",Ue,[t("Command Palette"),o(n)]),t(" and click the "),qe,t(" button.")])]),Ve]),_:1}),je,Me,e("p",null,[t("To setup Atom in portable mode download the "),e("a",Le,[t("zip/tar.gz package for your system"),o(n)]),t(" and extract it to your removable storage.")]),o(c,{id:"268",data:[{title:"Linux"},{title:"Mac"},{title:"Windows"}],"tab-id":"installing-atom"},{tab0:a(({title:i,value:s,isActive:l})=>[Be,De]),tab1:a(({title:i,value:s,isActive:l})=>[He,Ge]),tab2:a(({title:i,value:s,isActive:l})=>[Re,Je]),_:1}),Ke,e("p",null,[t("The "),o(d,{to:"/hacking-atom/sections/hacking-on-atom-core/"},{default:a(()=>[t("Hacking on Atom Core")]),_:1}),t(" section of the flight manual covers instructions on how to clone and build the source code if you prefer that option.")]),Xe,e("p",null,[t("You can find definitions for all of the various terms that we use throughout the manual in our "),o(d,{to:"/resources/sections/glossary/"},{default:a(()=>[t("Glossary")]),_:1}),t(".")]),Ze,e("template",null,[Qe,e("p",null,[t("In "),o(d,{to:"/sections/basic-customization/"},{default:a(()=>[t("Basic Customization")]),_:1}),t(" we will see how to set different wrap preferences for different types of files (for example, if you want to wrap Markdown files but not other files).")]),et,e("p",null,[t("Another way to open a file in Atom is from the command line using the "),tt,t(" command. "),e("span",ot,[t('The Atom menu bar has a command named "Install Shell Commands" which installs the '),at,t(" and "),nt,t(" commands "),o(d,{to:"/installing-atom/#installing-atom-on-mac"},{default:a(()=>[t("if Atom wasn't able to install them itself")]),_:1}),t(" .")]),e("span",it,[t("The "),st,t(" and "),lt,t(" commands are installed automatically as a part of Atom's "),o(d,{to:"/sections/installing-atom/"},{default:a(()=>[t("installation process")]),_:1}),t(".")])]),rt,e("p",null,[t("Editing a file is pretty straightforward. You can click around and scroll with your mouse and type to change the content. There is no special editing mode or key commands. If you prefer editors with modes or more complex key commands, you should take a look at the "),e("a",dt,[t("Atom package list"),o(n)]),t(". There are a lot of packages that emulate popular styles.")]),ct,e("p",null,[t("The fuzzy finder uses the "),mt,t(", "),ht,t(" and "),ut,t(" configuration settings to filter out files and folders that will not be shown. If you have a project with tons of files you don't want it to search through, you can add patterns or paths to either of these config settings or your "),e("a",pt,[t("standard "),gt,t(" files"),o(n)]),t(". We'll learn more about config settings in "),o(d,{to:"/using-atom/sections/basic-customization/#global-configuration-settings"},{default:a(()=>[t("Global Configuration Settings")]),_:1}),t(", but for now you can easily set these in the Settings View under Core Settings.")]),e("p",null,[t("Both "),ft,t(" and "),bt,t(" are interpreted as glob patterns as implemented by the "),e("a",yt,[t("minimatch Node module"),o(n)]),t(".")]),wt])])}const It=S(I,[["render",vt],["__file","index.html.vue"]]);export{It as default}; diff --git a/assets/index.html.85a58c87.js b/assets/index.html.85a58c87.js new file mode 100644 index 0000000000..fe016d4581 --- /dev/null +++ b/assets/index.html.85a58c87.js @@ -0,0 +1 @@ +import{_ as e,o as c,c as t}from"./app.0e1565ce.js";const n={};function _(o,r){return c(),t("div")}const a=e(n,[["render",_],["__file","index.html.vue"]]);export{a as default}; diff --git a/assets/index.html.8656dbb7.js b/assets/index.html.8656dbb7.js new file mode 100644 index 0000000000..d85ccef767 --- /dev/null +++ b/assets/index.html.8656dbb7.js @@ -0,0 +1 @@ +import{_ as a,o as e,c as i,f as s}from"./app.0e1565ce.js";const t={},n=s('

    Getting Started

    Here you will learn everything you need to know about actually using Pulsar. This introduces what Pulsar is, how to install it and how to use its basic features.

    Using Pulsar

    This section is all about the various functions built right into Pulsar and how you can use them to get the most out of it.

    It will teach you everything from cursor movement all the way to basic customization of the application.

    Hacking the Core

    This is really what Pulsar is all about, here you will get to grips with what lies under the skin of Pulsar so you can begin hacking on it to extend and customize its functionality.

    Behind Pulsar

    Here you will get into the real heart of Pulsar, where you get into the details of various internal APIs and full details of how to maintain packages.

    FAQ

    Frequently asked questions. This is where you can find some of the more common questions and answers regarding Pulsar.

    ',11),o=[n];function r(h,l){return e(),i("div",null,o)}const u=a(t,[["render",r],["__file","index.html.vue"]]);export{u as default}; diff --git a/assets/index.html.89530304.js b/assets/index.html.89530304.js new file mode 100644 index 0000000000..fe016d4581 --- /dev/null +++ b/assets/index.html.89530304.js @@ -0,0 +1 @@ +import{_ as e,o as c,c as t}from"./app.0e1565ce.js";const n={};function _(o,r){return c(),t("div")}const a=e(n,[["render",_],["__file","index.html.vue"]]);export{a as default}; diff --git a/assets/index.html.8a1f00a1.js b/assets/index.html.8a1f00a1.js new file mode 100644 index 0000000000..fe016d4581 --- /dev/null +++ b/assets/index.html.8a1f00a1.js @@ -0,0 +1 @@ +import{_ as e,o as c,c as t}from"./app.0e1565ce.js";const n={};function _(o,r){return c(),t("div")}const a=e(n,[["render",_],["__file","index.html.vue"]]);export{a as default}; diff --git a/assets/index.html.8df00eba.js b/assets/index.html.8df00eba.js new file mode 100644 index 0000000000..02a543ef40 --- /dev/null +++ b/assets/index.html.8df00eba.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-6e0db890","path":"/docs/atom-archive/resources/","title":"Appendix A : Resources","lang":"en-US","frontmatter":{"title":"Appendix A : Resources","sitemap":{"priority":0.1}},"excerpt":"","headers":[{"level":2,"title":"Resources","slug":"resources","link":"#resources","children":[{"level":3,"title":"Glossary","slug":"glossary","link":"#glossary","children":[]}]}],"git":{"updatedTime":1669438016000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":2},{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":1},{"name":"confused-Techie","email":"dev@lhbasics.com","commits":1}]},"readingTime":{"minutes":0.22,"words":66},"filePathRelative":"docs/atom-archive/resources/index.md"}');export{e as data}; diff --git a/assets/index.html.8ea55446.js b/assets/index.html.8ea55446.js new file mode 100644 index 0000000000..fe016d4581 --- /dev/null +++ b/assets/index.html.8ea55446.js @@ -0,0 +1 @@ +import{_ as e,o as c,c as t}from"./app.0e1565ce.js";const n={};function _(o,r){return c(),t("div")}const a=e(n,[["render",_],["__file","index.html.vue"]]);export{a as default}; diff --git a/assets/index.html.90c99035.js b/assets/index.html.90c99035.js new file mode 100644 index 0000000000..fe016d4581 --- /dev/null +++ b/assets/index.html.90c99035.js @@ -0,0 +1 @@ +import{_ as e,o as c,c as t}from"./app.0e1565ce.js";const n={};function _(o,r){return c(),t("div")}const a=e(n,[["render",_],["__file","index.html.vue"]]);export{a as default}; diff --git a/assets/index.html.941947fb.js b/assets/index.html.941947fb.js new file mode 100644 index 0000000000..fe016d4581 --- /dev/null +++ b/assets/index.html.941947fb.js @@ -0,0 +1 @@ +import{_ as e,o as c,c as t}from"./app.0e1565ce.js";const n={};function _(o,r){return c(),t("div")}const a=e(n,[["render",_],["__file","index.html.vue"]]);export{a as default}; diff --git a/assets/index.html.956b9123.js b/assets/index.html.956b9123.js new file mode 100644 index 0000000000..fe016d4581 --- /dev/null +++ b/assets/index.html.956b9123.js @@ -0,0 +1 @@ +import{_ as e,o as c,c as t}from"./app.0e1565ce.js";const n={};function _(o,r){return c(),t("div")}const a=e(n,[["render",_],["__file","index.html.vue"]]);export{a as default}; diff --git a/assets/index.html.96af6444.js b/assets/index.html.96af6444.js new file mode 100644 index 0000000000..9ba60c605b --- /dev/null +++ b/assets/index.html.96af6444.js @@ -0,0 +1 @@ +const e=JSON.parse(`{"key":"v-3e3eb0e0","path":"/docs/launch-manual/sections/faq/","title":"FAQ","lang":"en-US","frontmatter":{"lang":"en-US","title":"FAQ","description":"Frequently asked questions"},"excerpt":"","headers":[{"level":2,"title":"Having trouble?","slug":"having-trouble","link":"#having-trouble","children":[]},{"level":2,"title":"Common Issues","slug":"common-issues","link":"#common-issues","children":[{"level":3,"title":"macOS error \\"App is damaged and can\u2019t be opened\\"","slug":"macos-error-app-is-damaged-and-can-t-be-opened","link":"#macos-error-app-is-damaged-and-can-t-be-opened","children":[]},{"level":3,"title":"Pulsar does not launch \\"GPU process isn't usable. Goodbye\\"","slug":"pulsar-does-not-launch-gpu-process-isn-t-usable-goodbye","link":"#pulsar-does-not-launch-gpu-process-isn-t-usable-goodbye","children":[]}]}],"git":{"updatedTime":1694141555000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":3}]},"readingTime":{"minutes":0.18,"words":54},"filePathRelative":"docs/launch-manual/sections/faq/index.md"}`);export{e as data}; diff --git a/assets/index.html.9c7c1197.js b/assets/index.html.9c7c1197.js new file mode 100644 index 0000000000..bcfba196dd --- /dev/null +++ b/assets/index.html.9c7c1197.js @@ -0,0 +1 @@ +import{_ as n,o as s,c as l,a as e,d as o,w as i,b as a,r}from"./app.0e1565ce.js";const c={},d=e("h1",{id:"core-pulsar-packages-documentation",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#core-pulsar-packages-documentation","aria-hidden":"true"},"#"),a(" Core Pulsar Packages Documentation")],-1),u=e("p",null,"This aims to be a collection of information about the first-party packages published for Pulsar.",-1),h=e("p",null,"Secondly, and currently, it is an archive of every Wiki that was published from the upstream Atom Organization.",-1),_=e("p",null,"This means some of this information may be old, or no longer relevant. If it is, it has been marked as so at the top of the file under a warning.",-1),p=e("p",null,"None-the-less the information can still be helpful, and may prove to be valuable. Which is why it has been archived here.",-1),m=e("h2",{id:"archived-wikis",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#archived-wikis","aria-hidden":"true"},"#"),a(" Archived Wikis")],-1);function f(k,g){const t=r("RouterLink");return s(),l("div",null,[d,u,h,_,p,m,e("ul",null,[e("li",null,[o(t,{to:"/docs/packages/core/atom-languageclient/"},{default:i(()=>[a("Atom-LanguageClient")]),_:1})]),e("li",null,[o(t,{to:"/docs/packages/core/autocomplete-plus/"},{default:i(()=>[a("Autocomplete-Plus")]),_:1})]),e("li",null,[o(t,{to:"/docs/packages/core/github/"},{default:i(()=>[a("GitHub")]),_:1})]),e("li",null,[o(t,{to:"/docs/packages/core/ide-java/"},{default:i(()=>[a("IDE-Java")]),_:1})])])])}const v=n(c,[["render",f],["__file","index.html.vue"]]);export{v as default}; diff --git a/assets/index.html.a5b4ee80.js b/assets/index.html.a5b4ee80.js new file mode 100644 index 0000000000..cfa77bc1c2 --- /dev/null +++ b/assets/index.html.a5b4ee80.js @@ -0,0 +1 @@ +import{_ as r,o as i,c as d,a,b as e,d as o,w as c,f as u,r as t}from"./app.0e1565ce.js";const h={},l=u('

    FAQ

    Under Construction

    This document is under construction, please check back soon for updates. Please see our socials and feel free to ask for assistance or inquire as to the status of this document.

    Having trouble?

    If you have any issues then please feel free to ask for help from the Pulsar Team or the wider community via any of our Community areas

    ',4),p={href:"https://github.com/pulsar-edit/pulsar/issues",target:"_blank",rel:"noopener noreferrer"},m={href:"https://github.com/pulsar-edit/pulsar/issues/new?assignees=&labels=bug%2Ctriage&projects=&template=bug-report.yml",target:"_blank",rel:"noopener noreferrer"},f=a("h2",{id:"common-issues",tabindex:"-1"},[a("a",{class:"header-anchor",href:"#common-issues","aria-hidden":"true"},"#"),e(" Common Issues")],-1),b=a("h3",{id:"macos-error-app-is-damaged-and-can-t-be-opened",tabindex:"-1"},[a("a",{class:"header-anchor",href:"#macos-error-app-is-damaged-and-can-t-be-opened","aria-hidden":"true"},"#"),e(' macOS error "'),a("em",null,"App is damaged and can\u2019t be opened"),e('"')],-1),_=a("code",null,"xattr -cr /Applications/Pulsar.app/",-1),g={href:"https://appletoolbox.com/app-is-damaged-cannot-be-opened-mac/",target:"_blank",rel:"noopener noreferrer"},y=a("h3",{id:"pulsar-does-not-launch-gpu-process-isn-t-usable-goodbye",tabindex:"-1"},[a("a",{class:"header-anchor",href:"#pulsar-does-not-launch-gpu-process-isn-t-usable-goodbye","aria-hidden":"true"},"#"),e(' Pulsar does not launch "'),a("em",null,"GPU process isn't usable. Goodbye"),e('"')],-1),x=a("code",null,"--no-sandbox",-1),k={href:"https://github.com/pulsar-edit/pulsar/issues/174",target:"_blank",rel:"noopener noreferrer"};function v(w,C){const n=t("ExternalLinkIcon"),s=t("RouterLink");return i(),d("div",null,[l,a("p",null,[e("If you think you have found a bug then please have a look through our existing "),a("a",p,[e("issues"),o(n)]),e(" and if you can't find anything then please "),a("a",m,[e("create a new bug report"),o(n)]),e(".")]),f,b,a("p",null,[e("The binary is likely from before macOS binaries started being signed. A up-to-date and signed binary may be downloaded from "),o(s,{to:"/download.html"},{default:c(()=>[e("the download page")]),_:1}),e(". The unsigned binary can be made to run by running "),_,e(" in the terminal. See "),a("a",g,[e("here"),o(n)]),e(" for more information.")]),y,a("p",null,[e("You may need to launch the application with the argument "),x,e(" to get around this issue. This is something "),a("a",k,[e("under investigation"),o(n)]),e(".")])])}const I=r(h,[["render",v],["__file","index.html.vue"]]);export{I as default}; diff --git a/assets/index.html.a8a660db.js b/assets/index.html.a8a660db.js new file mode 100644 index 0000000000..2c0ee24e19 --- /dev/null +++ b/assets/index.html.a8a660db.js @@ -0,0 +1 @@ +const a=JSON.parse('{"key":"v-31f54a17","path":"/tag/package-manager/","title":"package manager Tag","lang":"en-US","frontmatter":{"title":"package manager Tag","blog":{"type":"category","name":"package manager","key":"tag"},"layout":"Blog"},"excerpt":"","headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null}');export{a as data}; diff --git a/assets/index.html.a9c04ba9.js b/assets/index.html.a9c04ba9.js new file mode 100644 index 0000000000..6a43e5182c --- /dev/null +++ b/assets/index.html.a9c04ba9.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-145ac574","path":"/blog/","title":"Articles","lang":"en-US","frontmatter":{"title":"Articles","blog":{"type":"type","key":"article"},"layout":"Blog"},"excerpt":"","headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null}');export{e as data}; diff --git a/assets/index.html.ad5774dc.js b/assets/index.html.ad5774dc.js new file mode 100644 index 0000000000..79cee55574 --- /dev/null +++ b/assets/index.html.ad5774dc.js @@ -0,0 +1,54 @@ +import{_ as o,o as i,c as l,a as s,b as e,d as a,f as t,r as c}from"./app.0e1565ce.js";const d={},r=t('

    STOP

    This is being kept for archival purposes only from the original Atom documentation. As this may no longer be relevant to Pulsar, use this at your own risk. Current Pulsar documentation is found at documentation home.

    Shadow DOM

    In Atom 1.13 the Shadow DOM got removed from text editors. Find a guide how to migrate your theme or package in this appendix.

    Removing Shadow DOM styles

    ',4),p=s("code",null,"1.13",-1),u={href:"https://github.com/atom/atom/pull/12903",target:"_blank",rel:"noopener noreferrer"},m=t(`

    Summary

    Here is a quick summary to see all the changes at a glance:

    Before+/-After
    atom-text-editor::shadow {}- ::shadowatom-text-editor {}
    .class /deep/ .class {}- /deep/.class .class {}
    atom-text-editor, :host {}- :hostatom-text-editor {}
    .comment {}+ .syntax--.syntax--comment {}

    Below you'll find more detailed examples.

    UI themes and packages

    ::shadow

    Remove the ::shadow pseudo-element selector from atom-text-editor.

    Before:

    atom-text-editor::shadow .cursor {
    +	border-color: hotpink;
    +}
    +

    After:

    atom-text-editor .cursor {
    +	border-color: hotpink;
    +}
    +
    /deep/

    Remove the /deep/ combinator selector. It didn't get used that often, mainly to customize the scrollbars.

    Before:

    .scrollbars-visible-always /deep/ ::-webkit-scrollbar {
    +	width: 8px;
    +	height: 8px;
    +}
    +

    After:

    .scrollbars-visible-always ::-webkit-scrollbar {
    +	width: 8px;
    +	height: 8px;
    +}
    +

    Syntax themes

    :host

    Remove the :host pseudo-element selector. To scope any styles to the text editor, using atom-text-editor is already enough.

    Before:

    atom-text-editor,
    +:host {
    +	.cursor {
    +		border-color: hotpink;
    +	}
    +}
    +

    After:

    atom-text-editor {
    +	.cursor {
    +		border-color: hotpink;
    +	}
    +}
    +
    syntax--

    Add a syntax-- prefix to all grammar selectors used by language packages. It looks a bit verbose, but it will protect all the grammar selectors from accidental style pollution.

    Before:

    .comment {
    +	color: @mono-3;
    +	font-style: italic;
    +
    +	.markup.link {
    +		color: @mono-3;
    +	}
    +}
    +

    After:

    .syntax--comment {
    +	color: @mono-3;
    +	font-style: italic;
    +
    +	.syntax--markup.syntax--link {
    +		color: @mono-3;
    +	}
    +}
    +

    Note: Selectors like the .gutter, .indent-guide, .cursor among others, that are also part of Syntax themes, don't need a prefix. Only grammar selectors that get used by language packages. For example .syntax--keyword, .syntax--keyword.syntax--operator.syntax--js.

    Context-Targeted Style Sheets

    Atom also allowed you to target a specific shadow DOM context with an entire style sheet by adding .atom-text-editor to the file name. This is now not necessary anymore and can be removed.

    Before:

    my-ui-theme/
    +  styles/
    +    index.atom-text-editor.less
    +

    After:

    my-ui-theme/
    +  styles/
    +    index.less
    +

    I followed the guide, but now my styling is broken!

    By replacing atom-text-editor::shadow with atom-text-editor.editor, specificity might have changed. This can cause the side effect that some of your properties won't be strong enough. To fix that, you can increase specificity on your selector. A simple way is to just repeat your class (in the example below it's .my-class):

    Before:

    atom-text-editor::shadow .my-class {
    +	color: hotpink;
    +}
    +

    After:

    atom-text-editor .my-class.my-class {
    +	color: hotpink;
    +}
    +

    When should I migrate my theme/package?

    `,44),h=s("li",null,[e("If you already want to test the migration on master or Beta channel, make sure to change your "),s("code",null,"package.json"),e(" file to "),s("code",null,'"engines": { "atom": ">=1.13.0 <2.0.0" }'),e(". This will prevent Atom from updating your theme or package before the user also updates Atom to version "),s("code",null,"1.13"),e(".")],-1),v=s("code",null,"1.13",-1),b=s("strong",null,"Stable",-1),k={href:"https://blog.atom.io/",target:"_blank",rel:"noopener noreferrer"},g=s("code",null,"1.13",-1),y={href:"https://github.com/atom/deprecation-cop",target:"_blank",rel:"noopener noreferrer"};function x(f,w){const n=c("ExternalLinkIcon");return i(),l("div",null,[r,s("p",null,[e("In Atom "),p,e(" the Shadow DOM got removed from text editors. For more background on the reasoning, check out the "),s("a",u,[e("Pull Request"),a(n)]),e(" where it was removed. In this guide you will learn how to migrate your theme or package.")]),m,s("ul",null,[h,s("li",null,[e("Or you can wait until Atom "),v,e(" reaches "),b,e(". Check "),s("a",k,[e("blog.atom.io"),a(n)]),e(" to see if "),g,e(" already has been released. Don't worry if you're a bit late, Atom will transform the deprecated selectors automatically to avoid breaking any themes or packages. But users will start to see a deprecation warning in "),s("a",y,[e("deprecation-cop"),a(n)]),e(".")])])])}const A=o(d,[["render",x],["__file","index.html.vue"]]);export{A as default}; diff --git a/assets/index.html.b03183ad.js b/assets/index.html.b03183ad.js new file mode 100644 index 0000000000..3fc447fd26 --- /dev/null +++ b/assets/index.html.b03183ad.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-65f23183","path":"/category/log/","title":"log Category","lang":"en-US","frontmatter":{"title":"log Category","blog":{"type":"category","name":"log","key":"category"},"layout":"Blog"},"excerpt":"","headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null}');export{e as data}; diff --git a/assets/index.html.b1e370f8.js b/assets/index.html.b1e370f8.js new file mode 100644 index 0000000000..fe016d4581 --- /dev/null +++ b/assets/index.html.b1e370f8.js @@ -0,0 +1 @@ +import{_ as e,o as c,c as t}from"./app.0e1565ce.js";const n={};function _(o,r){return c(),t("div")}const a=e(n,[["render",_],["__file","index.html.vue"]]);export{a as default}; diff --git a/assets/index.html.b3509800.js b/assets/index.html.b3509800.js new file mode 100644 index 0000000000..03875337e8 --- /dev/null +++ b/assets/index.html.b3509800.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-7080a64e","path":"/docs/resources/website/","title":"Website","lang":"en-US","frontmatter":{"title":"Website"},"excerpt":"","headers":[{"level":2,"title":"Building the website","slug":"building-the-website","link":"#building-the-website","children":[{"level":3,"title":"Prerequisites","slug":"prerequisites","link":"#prerequisites","children":[]},{"level":3,"title":"Clone the repository and install","slug":"clone-the-repository-and-install","link":"#clone-the-repository-and-install","children":[]},{"level":3,"title":"Running the website","slug":"running-the-website","link":"#running-the-website","children":[]},{"level":3,"title":"Notes","slug":"notes","link":"#notes","children":[]}]},{"level":2,"title":"Configuration files","slug":"configuration-files","link":"#configuration-files","children":[{"level":3,"title":"config.js","slug":"config-js","link":"#config-js","children":[]},{"level":3,"title":"navbar.js","slug":"navbar-js","link":"#navbar-js","children":[]},{"level":3,"title":"sidebar.js","slug":"sidebar-js","link":"#sidebar-js","children":[]},{"level":3,"title":"Theme","slug":"theme","link":"#theme","children":[]}]},{"level":2,"title":"File Organization","slug":"file-organization","link":"#file-organization","children":[{"level":3,"title":"index.md","slug":"index-md","link":"#index-md","children":[]},{"level":3,"title":"Sections","slug":"sections","link":"#sections","children":[]},{"level":3,"title":"Assets","slug":"assets","link":"#assets","children":[]}]},{"level":2,"title":"Documentation style & reusable components","slug":"documentation-style-reusable-components","link":"#documentation-style-reusable-components","children":[{"level":3,"title":"Structure","slug":"structure","link":"#structure","children":[]},{"level":3,"title":"Links","slug":"links","link":"#links","children":[]},{"level":3,"title":"Naming","slug":"naming","link":"#naming","children":[]},{"level":3,"title":"Containers","slug":"containers","link":"#containers","children":[]},{"level":3,"title":"Writing style","slug":"writing-style","link":"#writing-style","children":[]}]},{"level":2,"title":"Blog guide","slug":"blog-guide","link":"#blog-guide","children":[{"level":3,"title":"Writing a new post","slug":"writing-a-new-post","link":"#writing-a-new-post","children":[]},{"level":3,"title":"Testing locally","slug":"testing-locally","link":"#testing-locally","children":[]},{"level":3,"title":"Website \\"Blog\\" page(s)","slug":"website-blog-page-s","link":"#website-blog-page-s","children":[]},{"level":3,"title":"Questions? Suggestions","slug":"questions-suggestions","link":"#questions-suggestions","children":[]}]}],"git":{"updatedTime":1668356740000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":0.22,"words":67},"filePathRelative":"docs/resources/website/index.md"}');export{e as data}; diff --git a/assets/index.html.b35e00fe.js b/assets/index.html.b35e00fe.js new file mode 100644 index 0000000000..fe016d4581 --- /dev/null +++ b/assets/index.html.b35e00fe.js @@ -0,0 +1 @@ +import{_ as e,o as c,c as t}from"./app.0e1565ce.js";const n={};function _(o,r){return c(),t("div")}const a=e(n,[["render",_],["__file","index.html.vue"]]);export{a as default}; diff --git a/assets/index.html.b41a041c.js b/assets/index.html.b41a041c.js new file mode 100644 index 0000000000..d6de450e0d --- /dev/null +++ b/assets/index.html.b41a041c.js @@ -0,0 +1,43 @@ +import{_ as r,o as l,c as d,a as e,b as t,d as o,w as c,f as i,r as s}from"./app.0e1565ce.js";const h={},u=e("p",null,"Here is all the information on how the Pulsar website is built and configured.",-1),p={href:"https://v2.vuepress.vuejs.org/",target:"_blank",rel:"noopener noreferrer"},m={href:"https://vuepress-theme-hope.github.io/",target:"_blank",rel:"noopener noreferrer"},b=e("p",null,"See the documentation there for generic items not covered in this guide.",-1),f=e("h2",{id:"building-the-website",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#building-the-website","aria-hidden":"true"},"#"),t(" Building the website")],-1),g=e("h3",{id:"prerequisites",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#prerequisites","aria-hidden":"true"},"#"),t(" Prerequisites")],-1),v={href:"https://github.com/nvm-sh/nvm",target:"_blank",rel:"noopener noreferrer"},w=e("li",null,"git",-1),y={href:"https://pnpm.io/",target:"_blank",rel:"noopener noreferrer"},_=e("code",null,"corepack enable",-1),k=e("h3",{id:"clone-the-repository-and-install",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#clone-the-repository-and-install","aria-hidden":"true"},"#"),t(" Clone the repository and install")],-1),x={href:"https://github.com/pulsar-edit/pulsar-edit.github.io",target:"_blank",rel:"noopener noreferrer"},T=i(`
    git clone https://github.com/pulsar-edit/pulsar-edit.github.io
    +
    +cd pulsar-edit.github.io
    +
    +pnpm install
    +

    Running the website

    Once installed there are a number of scripts available to help you develop and build the site. Just prefix each command with pnpm. e,g, pnpm dev.

    dev

    Starts a watch task that will rebuild VuePress whenever a change has been made to the included Markdown and JavaScript files. Additionally, it launches the development server to test the results in the browser.

    build

    Creates an optimized production build.

    format

    `,8),j={href:"https://prettier.io",target:"_blank",rel:"noopener noreferrer"},L=i('

    Note: This task will run automatically on every commit, so it can be ignored in most cases

    lint

    Lints all Markdown files in the repository.

    Note: This task will run automatically on every commit, so it can be ignored in most cases

    Notes

    Whilst dev does run a watch task, not everything will be updated and some changes will require you to shut down the server and start it again. For example adding @include files to another file will not rebuild automatically.

    If you wish to add new files from another repository via alias or @include then you will need to run pnpm update to get the latest version of the repository - the pnpm-lock.yml file will also be updated and must be part of any commit.

    Configuration files

    Nearly everything regarding the configuration of the website itself is controlled via the files found in the /docs/.vuepress directory.

    Currently we have three main configuration files.

    config.js

    This is the main configuration file for the website. This controls everything from the available settings, additional VuePress plugins, website description and various other elements to control various settings and plugins.

    ',12),A={href:"https://v2.vuepress.vuejs.org/reference/config.html",target:"_blank",rel:"noopener noreferrer"},S={href:"https://vuepress-theme-hope.github.io/v2/config/",target:"_blank",rel:"noopener noreferrer"},q=e("p",null,[t("This file is broken down to to keep it tidy, the below files are imported to "),e("code",null,"config.js"),t(" to extend the configuration file without making it unwieldy.")],-1),I=e("h3",{id:"navbar-js",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#navbar-js","aria-hidden":"true"},"#"),t(),e("code",null,"navbar.js")],-1),W=e("p",null,"This controls the layout for the links in the top middle of the page and is always displayed.",-1),C=e("p",null,"Items that go here are ones that we always want to be shown and should always be available for quick navigation.",-1),M=e("p",null,"Each object can have a number of different values. The main ones we use are:",-1),N=e("li",null,[e("code",null,"text"),t(": This sets the text for the label.")],-1),O=e("code",null,"icon",-1),H={href:"https://fontawesome.com/",target:"_blank",rel:"noopener noreferrer"},E=e("code",null,"fa-",-1),F={href:"https://fontawesome.com/icons/house?s=solid&f=classic",target:"_blank",rel:"noopener noreferrer"},P=e("code",null,"solid fa-house",-1),Y=e("li",null,[e("code",null,"link"),t(": This controls where the link will actually take you. This can be a relative reference internal to the website or can be a URL to an external site.")],-1),U=e("li",null,[e("code",null,"children"),t(": Allows you to specify an array of child objects which will appear as a dropdown on mouseover. Use of this disables the "),e("code",null,"link"),t(" value. Each child can be defined as a full object as described here or can simply be a relative link from which the text will be set by the YAML title.")],-1),V={href:"https://v2.vuepress.vuejs.org/reference/default-theme/config.html#navbar",target:"_blank",rel:"noopener noreferrer"},B={href:"https://vuepress-theme-hope.github.io/v2/config/theme/layout.html#navbar-config",target:"_blank",rel:"noopener noreferrer"},D=e("h3",{id:"sidebar-js",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#sidebar-js","aria-hidden":"true"},"#"),t(),e("code",null,"sidebar.js")],-1),R=e("p",null,[t("This control what is displayed in the sidebar on the left of the website. It is not displayed globally, only on directories which are set within the sidebar, currently we only have "),e("code",null,"docs"),t(" configured.")],-1),z=e("p",null,[t("Like "),e("code",null,"navbar.js"),t(" each sidebare item is configured as an object with a number of different values.")],-1),G=e("li",null,[e("code",null,"text"),t(": This sets the text for the label.")],-1),X=e("li",null,[e("code",null,"link"),t(": Controls the relative link for navigating the documents within the section")],-1),J=e("code",null,"icon",-1),Q={href:"https://fontawesome.com/",target:"_blank",rel:"noopener noreferrer"},K=e("code",null,"fa-",-1),Z={href:"https://fontawesome.com/icons/house?s=solid&f=classic",target:"_blank",rel:"noopener noreferrer"},$=e("code",null,"solid fa-house",-1),ee=e("li",null,[e("code",null,"prefix"),t(": This adds a file path prefix to the item so its children do not need to specify the full path.")],-1),te=e("code",null,"collapsable",-1),oe=e("em",null,"Note",-1),ne=e("code",null,"collapsible",-1),ie={href:"https://github.com/vuepress-theme-hope/vuepress-theme-hope/issues/2377",target:"_blank",rel:"noopener noreferrer"},se=e("li",null,[e("code",null,"children"),t(": Takes an array of objects configured as above. Can also be set as a simple relative link in which case the title will be the YAML title of the document it links to.")],-1),ae={href:"https://v2.vuepress.vuejs.org/reference/default-theme/config.html#sidebar",target:"_blank",rel:"noopener noreferrer"},re={href:"https://vuepress-theme-hope.github.io/v2/config/theme/layout.html#sidebar-config",target:"_blank",rel:"noopener noreferrer"},le=i(`

    Theme

    Within the styles/ directory you will find the .scss file for controlling various aspects of the website's theme.

    TODO: This will be updated when we actually modify these files extensively.

    File Organization

    One of the most important things to take note of when adding new documentation is where it should go within the website layout.

    The generalized overall layout of the website looks like this:

    docs
    +\u251C\u2500\u2500 root level .md files
    +\u2514\u2500\u2500 section folder
    +    \u251C\u2500\u2500 sections
    +    \u2514\u2500\u2500 index.md
    +

    The general idea is that for files that can stand by themselves (for example the About Us page, Repositories etc.) they exist at the docs/ "root" level.

    For anything that is more complex it needs to have a section directory named appropriately, an index.md file within it and a sections directory.

    index.md

    This index file needs to have a YAML frontmatter to define, at a minimum, the title of the document. This is displayed as an H1 header for the page (note: subsequent H1 headers will be ignored so always start at H2).

    The rest of this index file will be used to display the actual content you want to show. This is done in a number of ways.

    First of all you can just include standard markdown. This is often used for introducing the section or adding one of our reusable components (e.g. a danger container).

    The rest of the file should consist of @includes which take data from other folders on the website and integrates it automatically. Usually this will be the sections files which will be covered next.

    e.g.

    @include(sections/file-organization.md)
    +
    `,16),de=e("code",null,"@include",-1),ce={href:"https://github.com/pulsar-edit/.github",target:"_blank",rel:"noopener noreferrer"},he=i(`

    This is done by having a value defined on the config.js file which will provide an alias for us to use:

    if (file.startsWith("@orgdocs")) {
    +	return file.replace(
    +		"@orgdocs",
    +		path.resolve(__dirname, "../../node_modules/.github/")
    +	);
    +}
    +

    This allows us to include org-level docs by using this special alias.

    e.g.

    @include(@orgdocs/TOOLING.md)
    +

    Sections

    The sections directory is where we include the rest of the documents broken down by section. These should be self contained files which can be used alone but are designed to be included on the section page. This approach allows us flexibility with ordering as well as including these files in other places without needing to duplicate the material.

    Files here can be navigated to directly on the website but should not be linked to directly.

    These files shoud not have any YAML frontmatter as they will be included and shown as text.

    Assets

    `,10),ue={href:"https://github.com/pulsar-edit/.github/tree/main/images/",target:"_blank",rel:"noopener noreferrer"},pe=i(`

    An alias for this exists in config.js to access files from this repository.

    alias: {
    +  '@images': path.resolve(__dirname, '../../node_modules/.github/images')
    +},
    +

    So to include an image you simply need to use the standard markdown image link along with the alias:

    e.g.

    ![MyImage](@images/path/to/image.png)
    +

    Documentation style & reusable components

    The documentation for the project should maintain a consistent style where possible. This covers a number of aspects such as writing style, naming conventions, folder structure, links etc.

    All docs are currently in American English (en-US) but localization is planned.

    Structure

    The main structure for documentation can be seen in docs/docs. There are a number of "root" sections:

    • launch-manual - For the current main Pulsar documentation
    • packages - Currently holds wiki info from the atom package repos
    • resources - For other referenced docs
    • blog - For the website blog
    • atom-archive - For "as is" archived Atom documentation

    Within each section is an index.md which will usually contain info about each sub-section as well as links to them. These correspond to the second level items on the sidebar.

    Inside sections are the sub-sections which group more specific topics. These also have an index.md which corresponds to the third level item on the sidebar. This file is displayed on the website as a single long documenent but is actually created from a number of @include() lines which reference individual sections within the next sections directory. These should be relative to the location of index.md e.g. @include(sections/pulsar-packages.md). This file also contains the frontmatter for defining the title, language and description of the file and should also be the first level heading for the page. Here is also where you can place a container such as Under Construction to apply to the entire page.

    Inside the next sections directory should be the actual content of the document. Each section should start with a second level header and should not contain any frontmatter.

    Internal links can just be to the header (e.g.[Structure](#structure)), this to all sections included on the parent index.md so care should be made to not create any duplicate headers.

    All other links should be relative but do not need to reference the index file itself (e.g.[Installing](../getting-started#installing)) will find the heading #installing within the index file in the getting-started directory above.

    Images should be added to [pulsar-assets](https://github.com/pulsar-edit/pulsar-assets) and referenced from the package imported from it. This is done via an alias on the .vuepress/config.js file which adds most of the path for you: '@images': path.resolve(__dirname, '../../node_modules/pulsar-assets/images')
    so the link to your image would just be ![myImage](@images/pulsar/myImage.png "My Image").

    Naming

    The name of the application is Pulsar and should be capitalized as such. Whilst the website and GitHub org name is Pulsar-Edit, this should not be used within documentation outside of links to the GitHub org or website.

    Operating systems should be named as such:

    • Linux - All GNU/Linux distributions
    • macOS - Apple's current operating system family
    • Windows - Microsoft Windows family of operating systems

    This is also the order they should appear in within the tab switcher.

    When using the #tabs switcher they should be in this order.

    When referencing them inline then they should be abbreviated to the following, strongly emphasized and separated by a -:

    • Linux = LNX
    • macOS = MAC
    • Windows = WIN

    To keep order consistent it should be LNX -> MAC -> WIN. If instructions: common to two then it should either be LNX/MAC, LNX/WIN -> MAC/WIN

    For Linux we may sometimes need to reference particular distros or families of distributions. We currently use:

    • Ubuntu/Debian for all distributions based on Debian or Ubuntu
    • Fedora/RHEL for all distrububtions based on Fedora Linux & Red Hat Red Hat Enterprise Linux. This includes AlmaLinux, CentOS, Rocky Linux etc.
    • Arch - for all Arch based distributions such as Manjaro, Garuda, ArcoLinux etc.
    • OpenSUSE - for all OpenSUSE based distributions such as GeckoLinux

    We may need to add more in the future but generally users of less popular or more technical distributions such as Gentoo or NixOS understand how to adapt to their OS from the instructions above.

    Containers

    Where you want to display an info, warning or tab/code switcher in the document you should use a container with the ::: syntax.

    e.g.

    ::: tabs#filename
    +
    +@tab Linux
    +
    +Lorem ipsum dolor sit amet...
    +
    +@tab macOS
    +
    +Lorem ipsum dolor sit amet...
    +
    +@tab Windows
    +
    +Lorem ipsum dolor sit amet...
    +
    +:::
    +

    or

    ::: tip My Helpful Tip
    +
    +You might want to do X to get Y
    +
    +:::
    +
    `,36),me={href:"https://github.com/pulsar-edit/pulsar-edit.github.io/blob/main/common-text-blocks.md",target:"_blank",rel:"noopener noreferrer"},be={href:"https://vuepress-theme-hope.github.io/v2/guide/get-started/markdown.html#theme-enhancement",target:"_blank",rel:"noopener noreferrer"},fe=e("h3",{id:"writing-style",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#writing-style","aria-hidden":"true"},"#"),t(" Writing style")],-1),ge=e("p",null,"TODO: Needs consensus",-1),ve=e("h2",{id:"blog-guide",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#blog-guide","aria-hidden":"true"},"#"),t(" Blog guide")],-1),we={href:"https://pulsar-edit.dev/article/",target:"_blank",rel:"noopener noreferrer"},ye={href:"https://vuepress-theme-hope.github.io/v2/blog/",target:"_blank",rel:"noopener noreferrer"},_e={href:"https://github.com/pulsar-edit/pulsar-edit.github.io",target:"_blank",rel:"noopener noreferrer"},ke=e("h3",{id:"writing-a-new-post",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#writing-a-new-post","aria-hidden":"true"},"#"),t(" Writing a new post")],-1),xe=i('
  • Create a new .md file in pulsar-edit.github.io/docs/blog.
    • This file should be named YYYYMMDD-<author>-<title>.md e.g 20221031-CreativeUsername-ThisIsMyBlogPost.md
  • The metadata displayed on the website is dependent on a number of items that are configured in the YAML frontmatter of the file. You may in theory omit any of these except the title field but it's strongly recommend that you use title, author, date, category and tag as the minimum as the others will default to false.
    • Frontmatter items supported currently are:
      • title - String: The displayed title of the post, consider this as H1
      • author - String: The name of the author to be displayed.
      • date - Date (ISO 8601): Allows display of date as well as enabling sorting on the timeline, set to the same as your filename date but with hyphens (e.g. 2022-10-31).
      • category - String (multiline): Enables filtering by category, this should be based on the subject of the post e.g. release, dev log, announcement. This is a multiline field if you want to set more than one category.
      • tag - String (multiline): Enables filtering by tags, this should be based on the content of the post and areas it touches on e.g. website, editor, config.
      • sticky - Bool: Enables "pinning" on the
      • star - Bool: Enables use of the star category for any important articles we want to remain visible.
      • article - Bool: You probably won't want to use this but setting this to false will exclude this page from appearing. This is set on the "example" blog post intentionally.
  • An excerpt can be added to the post by creating an html comment <!-- more -->. Anything above the comment will be treated as the excerpt and anything underneath will be the content of the post.
    • We are looking at implementing an auto excerpt feature but this doesn't seem to be working on the theme at the moment, this file will be updated if and when it is available.
  • ',3),Te={href:"https://vuepress-theme-hope.github.io/v2/md-enhance/config.html#vueplayground",target:"_blank",rel:"noopener noreferrer"},je=e("li",null,[t("Images are supported with the standard image syntax ("),e("code",null,"![myImage](./assets/myImage.png)"),t(") and should be located in the "),e("code",null,"blog/assets"),t(" directory. You may also use standard html "),e("code",null,""),t(" tags, particularly if you wish to control the displayed size of the image.")],-1),Le=e("li",null,"Create a PR to the repo and make it obvious it is a blog post, by including [BLOG] in the title of your PR. Please don't submit it alongside any website changes.",-1),Ae={href:"https://github.com/pulsar-edit/pulsar-edit.github.io/blob/main/docs/blog/20221112-Daeraxa-ExamplePost.md",target:"_blank",rel:"noopener noreferrer"},Se=e("h3",{id:"testing-locally",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#testing-locally","aria-hidden":"true"},"#"),t(" Testing locally")],-1),qe=i('

    Website "Blog" page(s)

    The website itself has a number of features which the aforementioned frontmatter fields can influence.

    There is a "selector" at the top of the site which currently reads All, Star, Slides, Encrypted. The only ones we use are the first two and currently the theme doesn't support configuration of this. If necessary we may be able to hide it with CSS.
    Any article with Star set to true will be shown in the star category.

    On the right bar are 4 counters/links and filters for Articles, Category, Tag and Timeline.
    Category and Tag are used to filter articles depending on the categories submitted in the respective frontmatter fields. The Timeline allows views of blog posts over time according to the dates set in the date frontmatter field.

    Questions? Suggestions

    Just ask in Discord or GH Discussions and ping the @documentation team.

    This is very much barebones default config so please let us know if you have any suggestions as to how we can improve it.

    ',7);function Ie(We,Ce){const n=s("ExternalLinkIcon"),a=s("RouterLink");return l(),d("div",null,[u,e("p",null,[t("The website itself is built using "),e("a",p,[t("VuePress v2"),o(n)]),t(" and the "),e("a",m,[t("Vuepress Hope Theme"),o(n)]),t(".")]),b,f,g,e("ul",null,[e("li",null,[t("Node.js - at least version 16 (recommended installation via "),e("a",v,[t("nvm"),o(n)]),t(").")]),w,e("li",null,[e("a",y,[t("pnpm"),o(n)]),t(" - simply run "),_])]),k,e("p",null,[t("The website repository is "),e("a",x,[t("https://github.com/pulsar-edit/pulsar-edit.github.io"),o(n)]),t(". Other assets are stored on other repositories but these will be downloaded automatically.")]),T,e("p",null,[t("Runs "),e("a",j,[t("Prettier"),o(n)]),t(" over all Markdown files included in the repository to ensure consistent formatting.")]),L,e("p",null,[t("For a full reference you can look at the documentation for "),e("a",A,[t("VuePress"),o(n)]),t(" and the "),e("a",S,[t("Hope Theme"),o(n)]),t(".")]),q,I,W,C,M,e("ul",null,[N,e("li",null,[O,t(": Used to prefix an icon to the item. The theme supports the free "),e("a",H,[t("FontAwesome"),o(n)]),t(" font natively. To add an icon you need to specify its name without the first "),E,t(" e.g. "),e("a",F,[t("fa-house"),o(n)]),t(" would be specified as "),P,t(".")]),Y,U]),e("p",null,[t("For a full reference you can look at the documentation for "),e("a",V,[t("VuePress"),o(n)]),t(" and the "),e("a",B,[t("Hope Theme"),o(n)]),t(".")]),D,R,z,e("ul",null,[G,X,e("li",null,[J,t(": Used to prefix an icon to the item. The theme supports the free "),e("a",Q,[t("FontAwesome"),o(n)]),t(" font natively. To add an icon you need to specify its name without the first "),K,t(" e.g. "),e("a",Z,[t("fa-house"),o(n)]),t(" would be specified as "),$,t(".")]),ee,e("li",null,[te,t(": (sic) Controls whether the item can be collapsed. "),oe,t(": This a breaking change on a future version of the Hope Theme so will need to be renamed "),ne,t(" when updated, see: "),e("a",ie,[t("Issue"),o(n)]),t(".")]),se]),e("p",null,[t("For a full reference you can look at the documentation for "),e("a",ae,[t("VuePress"),o(n)]),t(" and the "),e("a",re,[t("Hope Theme"),o(n)]),t(".")]),le,e("p",null,[t("However you can also use "),de,t(" to feature files from a different section of the website or even files from outside the main site. We use this to include files which are maintained on the organization "),e("a",ce,[t(".github repo"),o(n)]),t(" for org-level documents.")]),he,e("p",null,[t("Assets should be uploaded to the "),e("a",ue,[t(".github repo"),o(n)]),t(" repository so they can be used org-wide.")]),pe,e("p",null,[t("You can also find a list of currently maintained preformatted containers for various purposes at "),e("a",me,[t("pulsar-edit.github.io/common-text-blocks.md"),o(n)]),t(".")]),e("p",null,[t("See "),e("a",be,[t("VuePress Hope documentation"),o(n)])]),fe,ge,ve,e("p",null,[t("This is a guide on how to add a blog post to the website which will be shown on "),e("a",we,[t("https://pulsar-edit.dev/article/"),o(n)]),t(".")]),e("p",null,[t("We are using the "),e("a",ye,[t("Vuepress Blog Plugin"),o(n)]),t(" which comes as part of our Vuepress Hope Theme with some light configuration to suit our purposes.")]),e("p",null,[t("This is all implemented in the main "),e("a",_e,[t("website repository"),o(n)]),t(".")]),ke,e("ul",null,[xe,e("li",null,[t("The rest of the post is just standard Markdown - we are currently only supporting the standard features as per the "),e("a",Te,[t("MDEnhance"),o(n)]),t(" plugin but if we need more features such as GFM then please discuss and we can look at adding it to the website.")]),je,Le]),e("p",null,[t("See "),e("a",Ae,[t("example post"),o(n)]),t(" with everything above.")]),Se,e("p",null,[t("See "),o(a,{to:"/docs/resources/website/#building-the-website"},{default:c(()=>[t("building")]),_:1})]),qe])}const Ne=r(h,[["render",Ie],["__file","index.html.vue"]]);export{Ne as default}; diff --git a/assets/index.html.b8230621.js b/assets/index.html.b8230621.js new file mode 100644 index 0000000000..3cd09dfbdf --- /dev/null +++ b/assets/index.html.b8230621.js @@ -0,0 +1 @@ +const e=JSON.parse(`{"key":"v-0aa2ec94","path":"/docs/launch-manual/sections/behind-pulsar/","title":"Behind Pulsar","lang":"en-US","frontmatter":{"lang":"en-US","title":"Behind Pulsar","description":"Advanced Pulsar hacking"},"excerpt":"","headers":[{"level":2,"title":"Configuration API","slug":"configuration-api","link":"#configuration-api","children":[{"level":3,"title":"Reading Config Settings","slug":"reading-config-settings","link":"#reading-config-settings","children":[]},{"level":3,"title":"Writing Config Settings","slug":"writing-config-settings","link":"#writing-config-settings","children":[]}]},{"level":2,"title":"Keymaps In-Depth","slug":"keymaps-in-depth","link":"#keymaps-in-depth","children":[{"level":3,"title":"Structure of a Keymap File","slug":"structure-of-a-keymap-file","link":"#structure-of-a-keymap-file","children":[]},{"level":3,"title":"Removing Bindings","slug":"removing-bindings","link":"#removing-bindings","children":[]},{"level":3,"title":"Forcing Chromium's Native Keystroke Handling","slug":"forcing-chromium-s-native-keystroke-handling","link":"#forcing-chromium-s-native-keystroke-handling","children":[]},{"level":3,"title":"Overloading Key Bindings","slug":"overloading-key-bindings","link":"#overloading-key-bindings","children":[]},{"level":3,"title":"Step-by-Step: How Keydown Events are Mapped to Commands","slug":"step-by-step-how-keydown-events-are-mapped-to-commands","link":"#step-by-step-how-keydown-events-are-mapped-to-commands","children":[]},{"level":3,"title":"Overriding Pulsar's Keyboard Layout Recognition","slug":"overriding-pulsar-s-keyboard-layout-recognition","link":"#overriding-pulsar-s-keyboard-layout-recognition","children":[]}]},{"level":2,"title":"Scoped Settings, Scopes and Scope Descriptors","slug":"scoped-settings-scopes-and-scope-descriptors","link":"#scoped-settings-scopes-and-scope-descriptors","children":[{"level":3,"title":"Scope Names in Syntax Tokens","slug":"scope-names-in-syntax-tokens","link":"#scope-names-in-syntax-tokens","children":[]},{"level":3,"title":"Scope Selectors","slug":"scope-selectors","link":"#scope-selectors","children":[]},{"level":3,"title":"Scope Descriptors","slug":"scope-descriptors","link":"#scope-descriptors","children":[]}]},{"level":2,"title":"Serialization in Pulsar","slug":"serialization-in-pulsar","link":"#serialization-in-pulsar","children":[{"level":3,"title":"Package Serialization Hook","slug":"package-serialization-hook","link":"#package-serialization-hook","children":[]},{"level":3,"title":"Serialization Methods","slug":"serialization-methods","link":"#serialization-methods","children":[]},{"level":3,"title":"Versioning","slug":"versioning","link":"#versioning","children":[]}]},{"level":2,"title":"Developing Node Modules","slug":"developing-node-modules","link":"#developing-node-modules","children":[{"level":3,"title":"Linking a Node Module Into Your Pulsar Dev Environment","slug":"linking-a-node-module-into-your-pulsar-dev-environment","link":"#linking-a-node-module-into-your-pulsar-dev-environment","children":[]}]},{"level":2,"title":"Interacting With Other Packages Via Services","slug":"interacting-with-other-packages-via-services","link":"#interacting-with-other-packages-via-services","children":[]},{"level":2,"title":"Maintaining Your Packages","slug":"maintaining-your-packages","link":"#maintaining-your-packages","children":[{"level":3,"title":"Publishing a Package Manually","slug":"publishing-a-package-manually","link":"#publishing-a-package-manually","children":[]},{"level":3,"title":"Adding a Collaborator","slug":"adding-a-collaborator","link":"#adding-a-collaborator","children":[]},{"level":3,"title":"Transferring Ownership","slug":"transferring-ownership","link":"#transferring-ownership","children":[]},{"level":3,"title":"Unpublish Your Package","slug":"unpublish-your-package","link":"#unpublish-your-package","children":[]},{"level":3,"title":"Rename Your Package","slug":"rename-your-package","link":"#rename-your-package","children":[]}]},{"level":2,"title":"Summary","slug":"summary","link":"#summary","children":[]}],"git":{"updatedTime":1694141555000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":4}]},"readingTime":{"minutes":0.51,"words":154},"filePathRelative":"docs/launch-manual/sections/behind-pulsar/index.md"}`);export{e as data}; diff --git a/assets/index.html.bb4bd5d9.js b/assets/index.html.bb4bd5d9.js new file mode 100644 index 0000000000..cbb0d6e199 --- /dev/null +++ b/assets/index.html.bb4bd5d9.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-0166a572","path":"/docs/packages/","title":"Packages","lang":"en-us","frontmatter":{"lang":"en-us","title":"Packages","description":"Documentation Resource for Packages"},"excerpt":"","headers":[{"level":2,"title":"Core Packages","slug":"core-packages","link":"#core-packages","children":[{"level":3,"title":"Updated","slug":"updated","link":"#updated","children":[]},{"level":3,"title":"Archived Documentation","slug":"archived-documentation","link":"#archived-documentation","children":[]}]},{"level":2,"title":"Community Packages","slug":"community-packages","link":"#community-packages","children":[]}],"git":{"updatedTime":1673840801000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":4},{"name":"confused-Techie","email":"dev@lhbasics.com","commits":2}]},"readingTime":{"minutes":1.07,"words":322},"filePathRelative":"docs/packages/index.md"}');export{e as data}; diff --git a/assets/index.html.bb5ab2a9.js b/assets/index.html.bb5ab2a9.js new file mode 100644 index 0000000000..fe016d4581 --- /dev/null +++ b/assets/index.html.bb5ab2a9.js @@ -0,0 +1 @@ +import{_ as e,o as c,c as t}from"./app.0e1565ce.js";const n={};function _(o,r){return c(),t("div")}const a=e(n,[["render",_],["__file","index.html.vue"]]);export{a as default}; diff --git a/assets/index.html.be37943f.js b/assets/index.html.be37943f.js new file mode 100644 index 0000000000..f6114c0b8a --- /dev/null +++ b/assets/index.html.be37943f.js @@ -0,0 +1,188 @@ +import{_ as g,a as b,b as f,c as k}from"./unity-theme.516a5ae9.js";import{_ as y,a as v}from"./symbol.b88bf5a9.js";import{_ as w}from"./bookmarks.736c4e14.js";import{b as _,_ as C,a as x}from"./encodings.f93acf64.js";import{a as S,_ as P}from"./find-replace-project.ada882a7.js";import{_ as q,a as T}from"./snippet-scope.1f381b52.js";import{_ as A}from"./autocomplete.b0bae406.js";import{_ as p}from"./folding.6d09f8df.js";import{_ as u}from"./panes.a7cec271.js";import{_ as I}from"./allow-pending-pane-items.0fe3dd4a.js";import{_ as L}from"./grammar.c16b8cd6.js";import{_ as M,c as R,a as N,b as F,d as O,e as W}from"./open-on-github.6fa9b166.js";import{_ as H,a as G,b as D,c as j,d as Y,e as E,f as z,g as U,h as $,i as B,j as V,k as X,l as K,m as J,n as Z,o as Q,p as ee,q as te,r as ne,s as ae,t as oe,u as se,v as ie,w as le,x as re,y as ce}from"./github-review-reply.6d405e4b.js";import{_ as de,a as pe}from"./preview.2eaf146a.js";import{_ as ue,a as he,b as me,c as ge,d as be}from"./portable-mode-folder.90669a9a.js";import{_ as fe,o as ke,c as ye,a as e,b as t,d as n,w as o,e as h,f as r,r as d}from"./app.0e1565ce.js";const ve={},we=r('

    Using Pulsar

    Under Construction

    This document is under construction, please check back soon for updates. Please see our socials and feel free to ask for assistance or inquire as to the status of this document.

    Pulsar Packages

    ',3),_e={href:"https://github.com/pulsar-edit/tree-view",target:"_blank",rel:"noopener noreferrer"},Ce={href:"https://github.com/pulsar-edit/settings-view",target:"_blank",rel:"noopener noreferrer"},xe={href:"https://github.com/pulsar-edit/welcome",target:"_blank",rel:"noopener noreferrer"},Se={href:"https://github.com/pulsar-edit/spell-check",target:"_blank",rel:"noopener noreferrer"},Pe={href:"https://github.com/pulsar-edit/one-dark-ui",target:"_blank",rel:"noopener noreferrer"},qe={href:"https://github.com/pulsar-edit/fuzzy-finder",target:"_blank",rel:"noopener noreferrer"},Te=e("p",null,"This means that packages can be incredibly powerful and can change everything from the very look and feel of the entire interface to the basic operation of even core functionality.",-1),Ae=e("p",null,[t("In order to install a new package, you can use the Install tab in the now familiar Settings View. Open up the Settings View using "),e("strong",null,[e("em",null,"LNX/WIN")]),t(": "),e("kbd",null,"Ctrl+,"),t(" - "),e("strong",null,[e("em",null,"MAC")]),t(": "),e("kbd",null,"Cmd+,"),t(' click on the "Install" tab and type your search query into the box under Install Packages.')],-1),Ie={href:"https://web.pulsar-edit.dev",target:"_blank",rel:"noopener noreferrer"},Le=r('

    Package install screen

    All of the packages will come up with an "Install" button. Clicking that will download the package and install it. Your editor will now have the functionality that the package provides.

    Package Settings

    Once a package is installed in Pulsar, it will show up in the Settings View under the "Packages" tab, along with all the pre-installed packages that come with Pulsar. To filter the list in order to find one, you can type into search box directly under the "Installed Packages" heading.

    Package settings screen

    Clicking on the "Settings" button for a package will give you the settings screen for that package specifically. Here you have the option of changing some of the default variables for the package, seeing what all the command keybindings are, disabling the package temporarily, looking at the source code, seeing the current version of the package, reporting issues and uninstalling the package.

    If a new version of any of your packages is released, Pulsar will automatically detect it and you can upgrade the package from either this screen or from the "Updates" tab. This helps you easily keep all your installed packages up to date.

    Pulsar Themes

    You can also find and install new themes for Pulsar from the Settings View. These can be either UI themes or syntax themes and you can search for them from the "Install" tab, just like searching for new packages. Make sure to press the "Themes" toggle next to the search box.

    Theme search screen

    Clicking on the theme title will take you to a profile page for the theme on pulsar-edit.dev, which often has a screenshot of the theme. This way you can see what it looks like before installing it.

    Clicking on "Install" will install the theme and make it available in the Theme dropdowns as we saw in Changing the Theme.

    Example of the Unity UI theme with Monokai syntax theme

    Command Line

    You can also install packages or themes from the command line using ppm (Pulsar Package Manager). This is used by running pulsar -p <commmand> or pulsar --package <command>.

    Tip

    Check that you have ppm available by running the following command in your terminal:

    $ pulsar -p help install
    +

    You should see a message print out with details about the pulsar -p install command.

    If you do not, see the Installing Pulsar section for instructions on how to install the pulsar command for your system.

    You can install packages by using the pulsar -p install command:

    • pulsar -p install <package_name> to install the latest version.
    • pulsar -p install <package_name>@<package_version> to install a specific version.
    `,18),Me=e("code",null,"pulsar -p install minimap@4.40.0 ",-1),Re=e("code",null,"4.40.0",-1),Ne={href:"https://pulsar-edit.dev/packages/minimap",target:"_blank",rel:"noopener noreferrer"},Fe=r(`

    You can also use ppm to find new packages to install. If you run pulsar -p search, you can search the package registry for a search term.

    $ pulsar -p search linter
    +> Search Results For 'linter' (30)
    +> \u251C\u2500\u2500 linter A Base Linter with Cow Powers (9863242 downloads, 4757 stars)
    +> \u251C\u2500\u2500 linter-ui-default Default UI for the Linter package (7755748 downloads, 1201 stars)
    +> \u251C\u2500\u2500 linter-eslint Lint JavaScript on the fly, using ESLint (v7 or older) (2418043 downloads, 1660 stars)
    +> \u251C\u2500\u2500 linter-jshint Linter plugin for JavaScript, using jshint (1202044 downloads, 1271 stars)
    +> \u251C\u2500\u2500 linter-gcc Lint C and C++ source files using gcc / g++ (863989 downloads, 194 stars)
    +> ...
    +> \u251C\u2500\u2500 linter-shellcheck Lint Bash on the fly, using shellcheck (136938 downloads, 280 stars)
    +> \u2514\u2500\u2500 linter-rust Lint Rust-files, using rustc and/or cargo (132550 downloads, 91 stars)
    +

    You can use pulsar -p view to see more information about a specific package.

    $ pulsar -p view linter
    +> linter
    +> \u251C\u2500\u2500 3.4.0
    +> \u251C\u2500\u2500 https://github.com/steelbrain/linter
    +> \u251C\u2500\u2500 A Base Linter with Cow Powers
    +> \u251C\u2500\u2500 9863242 downloads
    +> \u2514\u2500\u2500 4757 stars
    +>
    +> Run \`pulsar -p install linter\` to install this package.
    +

    Using ppm to install from other sources

    `,5),Oe={href:"https://web.pulsar-edit.dev/",target:"_blank",rel:"noopener noreferrer"},We=r('
    Github or Git Remotes

    Pulsar can install from a GitHub repository or any valid git remote url. The -b option can then be used to specify a particular tag or branch.

    Git remote
    pulsar -p install <git_remote> [-b <branch_or_tag>]

    GitHub
    pulsar -p install <github_username>/<github_project> [-b <branch_or_tag>]

    ',4),He={href:"https://github.com/mauricioszabo/generic-lsp/",target:"_blank",rel:"noopener noreferrer"},Ge=r('

    pulsar -p install https://github.com/mauricioszabo/generic-lsp/

    or

    pulsar -p install mauricioszabo/generic-lsp

    This will use the current HEAD commit of the default branch. If you want to install a specific version from a branch or tag then use the -b option:

    e.g. pulsar -p install https://github.com/mauricioszabo/generic-lsp/ -b v1.0.3

    Moving in Pulsar

    While it's pretty easy to move around Pulsar by clicking with the mouse or using the arrow keys, there are some keybindings that may help you keep your hands on the keyboard and navigate around a little faster.

    ',7),De=e("p",null,"Pulsar has support for all the standard Linux cursor movement key combinations. To go up, down, left or right a single character you can use the arrow keys.",-1),je=e("p",null,"In addition to single character movement, there are a number of other movement keybindings:",-1),Ye=e("ul",null,[e("li",null,[e("kbd",null,"Ctrl+Left"),t(" - Move to the beginning of word")]),e("li",null,[e("kbd",null,"Ctrl+Right"),t(" - Move to the end of word")]),e("li",null,[e("kbd",null,"Home"),t(" - Move to the first character of the current line")]),e("li",null,[e("kbd",null,"End"),t(" - Move to the end of the line")]),e("li",null,[e("kbd",null,"Ctrl+Home"),t(" - Move to the top of the file")]),e("li",null,[e("kbd",null,"Ctrl+End"),t(" - Move to the bottom of the file")])],-1),Ee=e("p",null,[t("Pulsar ships with many of the basic Emacs keybindings for navigating a document. To go up and down a single character, you can use "),e("kbd",null,"Ctrl+P"),t(" and "),e("kbd",null,"Ctrl+N"),t(". To go left and right a single character, you can use "),e("kbd",null,"Ctrl+B"),t(" and "),e("kbd",null,"Ctrl+F"),t(". These are the equivalent of using the arrow keys, though some people prefer not having to move their hands to where the arrow keys are located on their keyboard.")],-1),ze=e("p",null,"In addition to single character movement, there are a number of other movement keybindings:",-1),Ue=e("ul",null,[e("li",null,[e("kbd",null,"Alt+Left"),t(" or "),e("kbd",null,"Alt+B"),t(" - Move to the beginning of word")]),e("li",null,[e("kbd",null,"Alt+Right"),t(" or "),e("kbd",null,"Alt+F"),t(" - Move to the end of word")]),e("li",null,[e("kbd",null,"Cmd+Left"),t(" or "),e("kbd",null,"Ctrl+A"),t(" - Move to the first character of the current line")]),e("li",null,[e("kbd",null,"Cmd+Right"),t(" or "),e("kbd",null,"Ctrl+E"),t(" - Move to the end of the line")]),e("li",null,[e("kbd",null,"Cmd+Up"),t(" - Move to the top of the file")]),e("li",null,[e("kbd",null,"Cmd+Down"),t(" - Move to the bottom of the file")])],-1),$e=e("p",null,"Pulsar has support for all the standard Windows cursor movement key combinations. To go up, down, left or right a single character you can use the arrow keys.",-1),Be=e("p",null,"In addition to single character movement, there are a number of other movement keybindings:",-1),Ve=e("ul",null,[e("li",null,[e("kbd",null,"Ctrl+Left"),t(" - Move to the beginning of word")]),e("li",null,[e("kbd",null,"Ctrl+Right"),t(" - Move to the end of word")]),e("li",null,[e("kbd",null,"Home"),t(" - Move to the first character of the current line")]),e("li",null,[e("kbd",null,"End"),t(" - Move to the end of the line")]),e("li",null,[e("kbd",null,"Ctrl+Home"),t(" - Move to the top of the file")]),e("li",null,[e("kbd",null,"Ctrl+End"),t(" - Move to the bottom of the file")])],-1),Xe=r('

    You can also move directly to a specific line (and column) number with Ctrl+G. This will bring up a dialog that asks which line you would like to jump to. You can also use the row:column syntax to jump to a character in that line as well.

    Go directly to a line

    Additional Movement and Selection Commands

    Pulsar also has a few movement and selection commands that don't have keybindings by default. You can access these commands from the Command Palette, but if you find yourself using commands that don't have a keybinding often, have no fear! You can easily add an entry to your keymap.cson to create a key combination. You can open keymap.cson file in an editor from the LNX: Edit > Keymap - MAC: Pulsar > Keymap - WIN: File > Keymap menu. For example, the command editor:move-to-beginning-of-screen-line is available in the command palette, but it's not bound to any key combination. To create a key combination you need to add an entry in your keymap.cson file. For editor:select-to-previous-word-boundary, you can add the following to your keymap.cson:

    ',4),Ke=e("div",{class:"language-coffee ext-coffee line-numbers-mode"},[e("pre",{class:"language-coffee"},[e("code",null,[e("span",{class:"token string-property property"},"'atom-text-editor'"),e("span",{class:"token operator"},":"),t(` + `),e("span",{class:"token string-property property"},"'ctrl-shift-e'"),e("span",{class:"token operator"},":"),t(),e("span",{class:"token string"},"'editor:select-to-previous-word-boundary'"),t(` +`)])]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),Je=e("div",{class:"language-coffee ext-coffee line-numbers-mode"},[e("pre",{class:"language-coffee"},[e("code",null,[e("span",{class:"token string-property property"},"'atom-text-editor'"),e("span",{class:"token operator"},":"),t(` + `),e("span",{class:"token string-property property"},"'cmd-shift-e'"),e("span",{class:"token operator"},":"),t(),e("span",{class:"token string"},"'editor:select-to-previous-word-boundary'"),t(` +`)])]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),Ze=e("div",{class:"language-coffee ext-coffee line-numbers-mode"},[e("pre",{class:"language-coffee"},[e("code",null,[e("span",{class:"token string-property property"},"'atom-text-editor'"),e("span",{class:"token operator"},":"),t(` + `),e("span",{class:"token string-property property"},"'ctrl-shift-e'"),e("span",{class:"token operator"},":"),t(),e("span",{class:"token string"},"'editor:select-to-previous-word-boundary'"),t(` +`)])]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),Qe=r('

    This will bind the command editor:select-to-previous-word-boundary to LNX/WIN: Ctrl+Shift+E - MAC: Cmd+Shift+E. For more information on customizing your keybindings, see Customizing Keybindings.

    Here's a list of Movement and Selection Commands that do not have a keyboard shortcut by default:

    ',2),et=e("div",{class:"language-text ext-text line-numbers-mode"},[e("pre",{class:"language-text"},[e("code",null,`editor:move-to-beginning-of-next-paragraph +editor:move-to-beginning-of-previous-paragraph +editor:move-to-beginning-of-screen-line +editor:move-to-beginning-of-line +editor:move-to-end-of-line +editor:move-to-first-character-of-line +editor:move-to-beginning-of-next-word +editor:move-to-previous-word-boundary +editor:move-to-next-word-boundary +editor:select-to-beginning-of-next-paragraph +editor:select-to-beginning-of-previous-paragraph +editor:select-to-end-of-line +editor:select-to-beginning-of-line +editor:select-to-beginning-of-next-word +editor:select-to-next-word-boundary +editor:select-to-previous-word-boundary +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),tt=e("div",{class:"language-text ext-text line-numbers-mode"},[e("pre",{class:"language-text"},[e("code",null,`editor:move-to-beginning-of-next-paragraph +editor:move-to-beginning-of-previous-paragraph +editor:move-to-beginning-of-screen-line +editor:move-to-beginning-of-line +editor:move-to-beginning-of-next-word +editor:move-to-previous-word-boundary +editor:move-to-next-word-boundary +editor:select-to-beginning-of-next-paragraph +editor:select-to-beginning-of-previous-paragraph +editor:select-to-beginning-of-line +editor:select-to-beginning-of-next-word +editor:select-to-next-word-boundary +editor:select-to-previous-word-boundary +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),nt=e("div",{class:"language-text ext-text line-numbers-mode"},[e("pre",{class:"language-text"},[e("code",null,`editor:move-to-beginning-of-next-paragraph +editor:move-to-beginning-of-previous-paragraph +editor:move-to-beginning-of-screen-line +editor:move-to-beginning-of-line +editor:move-to-end-of-line +editor:move-to-first-character-of-line +editor:move-to-beginning-of-next-word +editor:move-to-previous-word-boundary +editor:move-to-next-word-boundary +editor:select-to-beginning-of-next-paragraph +editor:select-to-beginning-of-previous-paragraph +editor:select-to-end-of-line +editor:select-to-beginning-of-line +editor:select-to-beginning-of-next-word +editor:select-to-next-word-boundary +editor:select-to-previous-word-boundary +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),at=r('

    You can also jump around a little more informatively with the Symbols View. To jump to a symbol such as a method definition, press LNX/WIN: Ctrl+R - MAC: Cmd+R. This opens a list of all symbols in the current file, which you can fuzzy filter similarly to LNX/WIN: Ctrl+T - MAC: Cmd+T. You can also search for symbols across your project but it requires a tags file.

    Search by symbol across your project

    ',3),ot=e("code",null,"tags",-1),st={href:"https://ctags.io/",target:"_blank",rel:"noopener noreferrer"},it=e("code",null,"tags",-1),lt={href:"https://docs.ctags.io/en/latest/",target:"_blank",rel:"noopener noreferrer"},rt=e("p",null,[t("Once you have your "),e("code",null,"tags"),t(" file generated, you can use it to search for symbols across your project by pressing "),e("kbd",null,"Ctrl+Shift+R"),t(". This also enables you to use "),e("kbd",null,"Alt+Ctrl+Down"),t(" to go to and "),e("kbd",null,"Alt+Ctrl+Up"),t(" to return from the declaration of the symbol under the cursor.")],-1),ct=e("p",null,[t("Once you have your "),e("code",null,"tags"),t(" file generated, you can use it to search for symbols across your project by pressing "),e("kbd",null,"Cmd+Shift+R"),t(". This also enables you to use "),e("kbd",null,"Alt+Cmd+Down"),t(" to go to and "),e("kbd",null,"Alt+Cmd+Up"),t(" to return from the declaration of the symbol under the cursor.")],-1),dt=e("p",null,[t("Once you have your "),e("code",null,"tags"),t(" file generated, you can use it to search for symbols across your project by pressing "),e("kbd",{class:""},"Ctrl+Shift+R"),t(".")],-1),pt=e("code",null,".ctags",-1),ut=e("strong",null,[e("em",null,"LNX/MAC")],-1),ht=e("code",null,"~/.ctags",-1),mt=e("strong",null,[e("em",null,"WIN")],-1),gt=e("code",null,"%USERPROFILE%\\.ctags",-1),bt={href:"https://github.com/pulsar-edit/symbols-view/blob/master/lib/ctags-config",target:"_blank",rel:"noopener noreferrer"},ft={href:"https://github.com/pulsar-edit/symbols-view",target:"_blank",rel:"noopener noreferrer"},kt=r('

    Bookmarks

    Pulsar also has a great way to bookmark specific lines in your project so you can jump back to them quickly.

    If you press LNX/WIN: Alt+Ctrl+F2 - MAC Cmd+F2, Pulsar will toggle a "bookmark" on the current line. You can set these throughout your project and use them to quickly find and jump to important lines of your project. A small bookmark symbol is added to the line gutter, like on line 22 of the image below.

    If you hit F2, Pulsar will jump to the next bookmark in the file you currently have focused. If you use Shift+F2 it will cycle backwards through them instead.

    You can also see a list of all your project's current bookmarks and quickly filter them and jump to any of them by hitting Ctrl+F2.

    View and filter bookmarks

    ',6),yt={href:"https://github.com/pulsar-edit/bookmarks",target:"_blank",rel:"noopener noreferrer"},vt=e("h2",{id:"pulsar-selections",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#pulsar-selections","aria-hidden":"true"},"#"),t(" Pulsar Selections")],-1),wt=e("p",null,"Text selections in Pulsar support a number of actions, such as scoping deletion, indentation and search actions, and marking text for actions such as quoting and bracketing.",-1),_t=e("p",null,[t("Selections mirror many of the movement commands. They're actually exactly the same keybindings as the movement commands, but with a "),e("kbd",null,"Shift"),t(" key added in.")],-1),Ct=e("ul",null,[e("li",null,[e("kbd",null,"Shift+Up"),t(" - Select up")]),e("li",null,[e("kbd",null,"Shift+Down"),t(" - Select down")]),e("li",null,[e("kbd",null,"Shift+Left"),t(" - Select previous character")]),e("li",null,[e("kbd",null,"Shift+Right"),t(" - Select next character")]),e("li",null,[e("kbd",null,"Ctrl+Shift+Left"),t(" - Select to beginning of word")]),e("li",null,[e("kbd",null,"Ctrl+Shift+Right"),t(" - Select to end of word")]),e("li",null,[e("kbd",null,"Shift+End"),t(" - Select to end of line")]),e("li",null,[e("kbd",null,"Shift+Home"),t(" - Select to first character of line")]),e("li",null,[e("kbd",null,"Ctrl+Shift+Home"),t(" - Select to top of file")]),e("li",null,[e("kbd",null,"Ctrl+Shift+End"),t(" - Select to bottom of file")])],-1),xt=e("p",null,"In addition to the cursor movement selection commands, there are also a few commands that help with selecting specific areas of content.",-1),St=e("ul",null,[e("li",null,[e("kbd",null,"Ctrl+A"),t(" - Select the entire contents of the file")]),e("li",null,[e("kbd",null,"Ctrl+L"),t(" - Select the entire line")])],-1),Pt=e("ul",null,[e("li",null,[e("kbd",null,"Shift+Up"),t(" or "),e("kbd",null,"Ctrl+Shift+P"),t(" - Select up")]),e("li",null,[e("kbd",null,"Shift+Down"),t(" or "),e("kbd",null,"Ctrl+Shift+N"),t(" - Select down")]),e("li",null,[e("kbd",null,"Shift+Left"),t(" or "),e("kbd",null,"Ctrl+Shift+B"),t(" - Select previous character")]),e("li",null,[e("kbd",null,"Shift+Right"),t(" or "),e("kbd",null,"Ctrl+Shift+F"),t(" - Select next character")]),e("li",null,[e("kbd",null,"Alt+Shift+Left"),t(" or "),e("kbd",null,"Alt+Shift+B"),t(" - Select to beginning of word")]),e("li",null,[e("kbd",null,"Alt+Shift+Right"),t(" or "),e("kbd",null,"Alt+Shift+F"),t(" - Select to end of word")]),e("li",null,[e("kbd",null,"Cmd+Shift+Right"),t(" or "),e("kbd",null,"Ctrl+Shift+E"),t(" - Select to end of line")]),e("li",null,[e("kbd",null,"Cmd+Shift+Left"),t(" or "),e("kbd",null,"Ctrl+Shift+A"),t(" - Select to first character of line")]),e("li",null,[e("kbd",null,"Cmd+Shift+Up"),t(" - Select to top of file")]),e("li",null,[e("kbd",null,"Cmd+Shift+Down"),t(" - Select to bottom of file")])],-1),qt=e("p",null,"In addition to the cursor movement selection commands, there are also a few commands that help with selecting specific areas of content.",-1),Tt=e("ul",null,[e("li",null,[e("kbd",null,"Cmd+A"),t(" - Select the entire contents of the file")]),e("li",null,[e("kbd",null,"Cmd+L"),t(" - Select the entire line")]),e("li",null,[e("kbd",null,"Ctrl+Shift+W"),t(" - Select the current word")])],-1),At=e("ul",null,[e("li",null,[e("kbd",null,"Shift+Up"),t(" - Select up")]),e("li",null,[e("kbd",null,"Shift+Down"),t(" - Select down")]),e("li",null,[e("kbd",null,"Shift+Left"),t(" - Select previous character")]),e("li",null,[e("kbd",null,"Shift+Right"),t(" - Select next character")]),e("li",null,[e("kbd",null,"Ctrl+Shift+Left"),t(" - Select to beginning of word")]),e("li",null,[e("kbd",null,"Ctrl+Shift+Right"),t(" - Select to end of word")]),e("li",null,[e("kbd",null,"Shift+End"),t(" - Select to end of line")]),e("li",null,[e("kbd",null,"Shift+Home"),t(" - Select to first character of line")]),e("li",null,[e("kbd",null,"Ctrl+Shift+Home"),t(" - Select to top of file")]),e("li",null,[e("kbd",null,"Ctrl+Shift+End"),t(" - Select to bottom of file")])],-1),It=e("p",null,"In addition to the cursor movement selection commands, there are also a few commands that help with selecting specific areas of content.",-1),Lt=e("ul",null,[e("li",null,[e("kbd",null,"Ctrl+A"),t(" - Select the entire contents of the file")]),e("li",null,[e("kbd",null,"Ctrl+L"),t(" - Select the entire line")])],-1),Mt=e("h2",{id:"editing-and-deleting-text",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#editing-and-deleting-text","aria-hidden":"true"},"#"),t(" Editing and Deleting Text")],-1),Rt=e("p",null,"So far we've looked at a number of ways to move around and select regions of a file, so now let's actually change some of that text. Obviously you can type in order to insert characters, but there are also a number of ways to delete and manipulate text that could come in handy.",-1),Nt=e("h3",{id:"basic-manipulation",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#basic-manipulation","aria-hidden":"true"},"#"),t(" Basic Manipulation")],-1),Ft=e("p",null,"There are a handful of cool keybindings for basic text manipulation that might come in handy. These range from moving around lines of text and duplicating lines to changing the case.",-1),Ot=e("ul",null,[e("li",null,[e("kbd",null,"Ctrl+J"),t(" - Join the next line to the end of the current line")]),e("li",null,[e("kbd",null,"Ctrl+Up/Down"),t(" - Move the current line up or down")]),e("li",null,[e("kbd",null,"Ctrl+Shift+D"),t(" - Duplicate the current line")]),e("li",null,[e("kbd",null,"Ctrl+K"),t(),e("kbd",null,"Ctrl+U"),t(" - Upper case the current word")]),e("li",null,[e("kbd",null,"Ctrl+K"),t(),e("kbd",null,"Ctrl+L"),t(" - Lower case the current word")])],-1),Wt=e("ul",null,[e("li",null,[e("kbd",null,"Cmd+J"),t(" - Join the next line to the end of the current line")]),e("li",null,[e("kbd",null,"Cmd+Ctrl+Up/Down"),t(" - Move the current line up or down")]),e("li",null,[e("kbd",null,"Cmd+Shift+D"),t(" - Duplicate the current line")]),e("li",null,[e("kbd",null,"Cmd+K"),t(),e("kbd",null,"Cmd+U"),t(" - Upper case the current word")]),e("li",null,[e("kbd",null,"Cmd+K"),t(),e("kbd",null,"Cmd+L"),t(" - Lower case the current word")]),e("li",null,[e("kbd",null,"Ctrl+T"),t(" - Transpose characters. This swaps the two characters on either side of the cursor.")])],-1),Ht=e("ul",null,[e("li",null,[e("kbd",null,"Ctrl+J"),t(" - Join the next line to the end of the current line")]),e("li",null,[e("kbd",null,"Ctrl+Up/Down"),t(" - Move the current line up or down")]),e("li",null,[e("kbd",null,"Ctrl+Shift+D"),t(" - Duplicate the current line")]),e("li",null,[e("kbd",null,"Ctrl+K"),t(),e("kbd",null,"Ctrl+U"),t(" - Upper case the current word")]),e("li",null,[e("kbd",null,"Ctrl+K"),t(),e("kbd",null,"Ctrl+L"),t(" - Lower case the current word")])],-1),Gt=r('

    Pulsar also has built in functionality to re-flow a paragraph to hard-wrap at a given maximum line length. You can format the current selection to have lines no longer than 80 (or whatever number editor.preferredLineLength is set to) characters using LNX/WIN: Ctrl+Shift+Q - MAC: Alt+Cmd+Q. If nothing is selected, the current paragraph will be reflowed.

    Deleting and Cutting

    You can also delete or cut text out of your buffer with some shortcuts. Be ruthless.

    ',3),Dt=e("ul",null,[e("li",null,[e("kbd",null,"Ctrl+Shift+K"),t(" - Delete current line")]),e("li",null,[e("kbd",null,"Ctrl+Backspace"),t(" - Delete to beginning of word")]),e("li",null,[e("kbd",null,"Ctrl+Delete"),t(" - Delete to end of word")])],-1),jt=e("ul",null,[e("li",null,[e("kbd",null,"Ctrl+Shift+K"),t(" - Delete current line")]),e("li",null,[e("kbd",null,"Alt+Backspace"),t(" or "),e("kbd",null,"Alt+H"),t(" - Delete to beginning of word")]),e("li",null,[e("kbd",null,"Alt+Delete"),t(" or "),e("kbd",null,"Alt+D"),t(" - Delete to end of word")]),e("li",null,[e("kbd",null,"Cmd+Delete"),t(" - Delete to end of line")]),e("li",null,[e("kbd",null,"Ctrl+K"),t(" - Cut to end of line")]),e("li",null,[e("kbd",null,"Cmd+Backspace"),t(" - Delete to beginning of line")])],-1),Yt=e("ul",null,[e("li",null,[e("kbd",null,"Ctrl+Shift+K"),t(" - Delete current line")]),e("li",null,[e("kbd",null,"Ctrl+Backspace"),t(" - Delete to beginning of word")]),e("li",null,[e("kbd",null,"Ctrl+Delete"),t(" - Delete to end of word")])],-1),Et=e("h3",{id:"multiple-cursors-and-selections",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#multiple-cursors-and-selections","aria-hidden":"true"},"#"),t(" Multiple Cursors and Selections")],-1),zt=e("p",null,"One of the cool things that Pulsar can do out of the box is support multiple cursors. This can be incredibly helpful in manipulating long lists of text.",-1),Ut=e("ul",null,[e("li",null,[e("kbd",null,"Ctrl+Click"),t(" - Add a new cursor at the clicked location")]),e("li",null,[e("kbd",null,"Alt+Shift+Up/Down"),t(" - Add another cursor above/below the current cursor")]),e("li",null,[e("kbd",null,"Ctrl+D"),t(" - Select the next word in the document that is the same as the currently selected word")]),e("li",null,[e("kbd",null,"Alt+F3"),t(" - Select all words in the document that are the same as the currently selected word")])],-1),$t=e("ul",null,[e("li",null,[e("kbd",null,"Cmd+Click"),t(" - Add a new cursor at the clicked location")]),e("li",null,[e("kbd",null,"Ctrl+Shift+Up/Down"),t(" - Add another cursor above/below the current cursor")]),e("li",null,[e("kbd",null,"Cmd+D"),t(" - Select the next word in the document that is the same as the currently selected word")]),e("li",null,[e("kbd",null,"Cmd+Ctrl+G"),t(" - Select all words in the document that are the same as the currently selected word")]),e("li",null,[e("kbd",null,"Cmd+Shift+L"),t(" - Convert a multi-line selection into multiple cursors")])],-1),Bt=e("ul",null,[e("li",null,[e("kbd",null,"Ctrl+Click"),t(" - Add a new cursor at the clicked location")]),e("li",null,[e("kbd",null,"Alt+Ctrl+Up/Down"),t(" - Add another cursor above/below the current cursor")]),e("li",null,[e("kbd",null,"Ctrl+D"),t(" - Select the next word in the document that is the same as the currently selected word")]),e("li",null,[e("kbd",null,"Alt+F3"),t(" - Select all words in the document that are the same as the currently selected word")])],-1),Vt=r('

    Using these commands you can place cursors in multiple places in your document and effectively execute the same commands in multiple places at once.

    Using multiple cursors

    This can be incredibly helpful in doing many type of repetitive tasks such as renaming variables or changing the format of some text. You can use this with almost any package or command - for example, changing case and moving or duplicating lines.

    You can also use the mouse to select text with the LNX/WIN: Ctrl - MAC: Cmd key pressed down to select multiple regions of your text simultaneously.

    Return to a single cursor with Esc or by clicking anywhere in the file to position a cursor there as normal.

    Whitespace

    Pulsar comes with several commands to help you manage the whitespace in your document. One very useful pair of commands converts leading spaces into tabs and converts leading tabs into spaces. If you're working with a document that has mixed whitespace, these commands are great for helping to normalize the file. There are no keybindings for the whitespace commands, so you will have to search your command palette for "Convert Spaces to Tabs" (or vice versa) to run one of these commands.

    ',7),Xt={href:"https://github.com/pulsar-edit/whitespace",target:"_blank",rel:"noopener noreferrer"},Kt=e("code",null,"whitespace",-1),Jt=r('

    Managing your whitespace settings

    Note

    The "Remove Trailing Whitespace" option is on by default. This means that every time you save any file opened in Pulsar, it will strip all trailing whitespace from the file. If you want to disable this, go to the whitespace package in your settings panel and uncheck that option.

    Pulsar will also by default ensure that your file has a trailing newline. You can also disable this option on that screen.

    Brackets

    Pulsar ships with intelligent and easy to use bracket handling.

    It will by default highlight [], (), and {} style brackets when your cursor is over them. It will also highlight matching XML and HTML tags.

    Pulsar will also automatically autocomplete [], (), and {}, "", '', \u201C\u201D, \u2018\u2019, \xAB\xBB, \u2039\u203A, and `` when you type the leading one. If you have a selection and you type any of these opening brackets or quotes, Pulsar will enclose the selection with the opening and closing brackets or quotes.

    There are a few other interesting bracket related commands that you can use.

    ',8),Zt=e("ul",null,[e("li",null,[e("kbd",null,"Ctrl+M"),t(" - Jump to the bracket matching the one adjacent to the cursor. It jumps to the nearest enclosing bracket when there's no adjacent bracket.")]),e("li",null,[e("kbd",null,"Alt+Ctrl+,"),t(" - Select all the text inside the current brackets")]),e("li",null,[e("kbd",null,"Alt+Ctrl+."),t(" - Close the current XML/HTML tag")])],-1),Qt=e("ul",null,[e("li",null,[e("kbd",null,"Ctrl+M"),t(" - Jump to the bracket matching the one adjacent to the cursor. It jumps to the nearest enclosing bracket when there's no adjacent bracket.")]),e("li",null,[e("kbd",null,"Cmd+Ctrl+M"),t(" - Select all the text inside the current brackets")]),e("li",null,[e("kbd",null,"Alt+Cmd+."),t(" - Close the current XML/HTML tag")])],-1),en=e("ul",null,[e("li",null,[e("kbd",null,"Ctrl+M"),t(" - Jump to the bracket matching the one adjacent to the cursor. It jumps to the nearest enclosing bracket when there's no adjacent bracket.")]),e("li",null,[e("kbd",null,"Alt+Ctrl+,"),t(" - Select all the text inside the current brackets")]),e("li",null,[e("kbd",null,"Alt+Ctrl+."),t(" - Close the current XML/HTML tag")])],-1),tn={href:"https://github.com/pulsar-edit/bracket-matcher",target:"_blank",rel:"noopener noreferrer"},nn=e("h3",{id:"encoding",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#encoding","aria-hidden":"true"},"#"),t(" Encoding")],-1),an=e("p",null,"Pulsar also ships with some basic file encoding support should you find yourself working with non-UTF-8 encoded files, or should you wish to create one.",-1),on=e("p",null,[e("kbd",null,"Alt+U"),t(" - Toggle menu to change file encoding")],-1),sn=e("p",null,[e("kbd",null,"Ctrl+Shift+U"),t(" - Toggle menu to change file encoding")],-1),ln=e("p",null,[e("kbd",null,"Ctrl+Shift+U"),t(" - Toggle menu to change file encoding")],-1),rn=e("p",null,"If you pull up the file encoding dialog, you can choose an alternate file encoding to save your file in.",-1),cn=e("p",null,"When you open a file, Pulsar will try to auto-detect the encoding. If Pulsar can't identify the encoding, the encoding will default to UTF-8, which is also the default encoding for new files.",-1),dn=e("p",null,[e("img",{src:_,alt:"Changing your file encoding"})],-1),pn=e("p",null,"If you pull up the encoding menu and change the active encoding to something else, the file will be written out in that encoding the next time you save the file.",-1),un={href:"https://github.com/pulsar-edit/encoding-selector",target:"_blank",rel:"noopener noreferrer"},hn=e("h2",{id:"find-and-replace",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#find-and-replace","aria-hidden":"true"},"#"),t(" Find and Replace")],-1),mn=e("p",null,"Finding and replacing text in your file or project is quick and easy in Pulsar.",-1),gn=e("ul",null,[e("li",null,[e("kbd",null,"Ctrl+F"),t(" - Search within a buffer")]),e("li",null,[e("kbd",null,"Ctrl+Shift+F"),t(" - Search the entire project")])],-1),bn=e("ul",null,[e("li",null,[e("kbd",null,"Cmd+F"),t(" - Search within a buffer")]),e("li",null,[e("kbd",null,"Cmd+Shift+F"),t(" - Search the entire project")])],-1),fn=e("ul",null,[e("li",null,[e("kbd",null,"Ctrl+F"),t(" - Search within a buffer")]),e("li",null,[e("kbd",null,"Ctrl+Shift+F"),t(" - Search the entire project")])],-1),kn=r('

    If you launch either of those commands, you'll be greeted with the Find and Replace panel at the bottom of your screen.

    Find and replace text in the current file

    To search within your current file you can press LNX/WIN: Cmd+F - MAC: Ctrl+F, type in a search string and press LNX/WIN/MAC: Enter or LNX/WINF3 - MAC: Cmd+G or the "Find Next" button) multiple times to cycle through all the matches in that file. Alt+Enter will find all occurrences of the search string. The Find and Replace panel also contains buttons for toggling case sensitivity, performing regular expression matching, scoping the search to selections, and performing whole word search.

    If you type a string in the replacement text box, you can replace matches with a different string. For example, if you wanted to replace every instance of the string "Atom" with the string "Pulsar", you would enter those values in the two text boxes and press the "Replace All" button to perform the replacements.

    ',4),yn={class:"custom-container note"},vn=e("p",{class:"custom-container-title"},"Note",-1),wn=e("p",null,[e("strong",null,"Note:"),t(" Pulsar uses JavaScript regular expressions to perform regular expression searches.")],-1),_n={href:"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions",target:"_blank",rel:"noopener noreferrer"},Cn=r('

    You can also find and replace throughout your entire project if you invoke the panel with LNX/WIN: Ctrl+Shift+F - MAC: Cmd+Shift+F.

    Find and replace text in your project

    This is a great way to find out where in your project a function is called, an anchor is linked to or a specific misspelling is located. Click on the matching line to jump to that location in that file.

    ',3),xn={href:"https://en.wikipedia.org/wiki/Glob_%28programming%29",target:"_blank",rel:"noopener noreferrer"},Sn=e("code",null,"src/*.js",-1),Pn=e("code",null,"src",-1),qn=e("code",null,"**",-1),Tn=e("code",null,"docs/**/*.md",-1),An=e("code",null,"docs/a/foo.md",-1),In=e("code",null,"docs/a/b/foo.md",-1),Ln=e("p",null,[t("When you have multiple project folders open, this feature can also be used to search in only one of those folders. For example, if you had the folders "),e("code",null,"/path1/folder1"),t(" and "),e("code",null,"/path2/folder2"),t(" open, you could enter a pattern starting with "),e("code",null,"folder1"),t(" to search only in the first folder.")],-1),Mn=e("p",null,[t("Press "),e("kbd",null,"Esc"),t(" while focused on the Find and Replace panel to clear the pane from your workspace.")],-1),Rn={href:"https://github.com/pulsar-edit/find-and-replace",target:"_blank",rel:"noopener noreferrer"},Nn={href:"https://github.com/pulsar-edit/scandal",target:"_blank",rel:"noopener noreferrer"},Fn=r(`

    Snippets

    Snippets are an incredibly powerful way to quickly generate commonly needed code syntax from a shortcut.

    The idea is that you can type something like habtm and then press the Tab key and it will expand into has_and_belongs_to_many.

    Many Core and Community packages come bundled with their own snippets that are specific to it. For example, the language-html package that provides support for HTML syntax highlighting and grammar comes with dozens of snippets to create many of the various HTML tags you might want to use. If you create a new HTML file in Pulsar, you can type html and then press Tab and it will expand to:

    <!DOCTYPE html>
    +<html lang="en" dir="ltr">
    +	<head>
    +		<meta charset="utf-8" />
    +		<title></title>
    +	</head>
    +	<body></body>
    +</html>
    +

    It will also position the cursor in the lang attribute value so you can edit it if necessary. Many snippets have multiple focus points that you can move through with the Tab key as well - for instance, in the case of this HTML snippet, after the cursor is placed in the lang attribute value, you can continue pressing Tab and the cursor will move to the dir attribute value, then to the middle of the title tag, then finally to the middle of the body tag.

    To see all the available snippets for the file type that you currently have open, choose "Snippets: Available" in the Command Palette.

    View all available snippets

    You can also use fuzzy search to filter this list down by typing in the selection box. Selecting one of them will execute the snippet where your cursor is (or multiple cursors are).

    Creating Your Own Snippets

    So that's pretty cool, but what if there is something the language package didn't include or something that is custom to the code you write? Luckily it's incredibly easy to add your own snippets.

    There is a text file in your LNX/MAC: ~/.pulsar - WIN: %USERPROFILE%\\.pulsar directory called snippets.cson that contains all your custom snippets that are loaded when you launch Pulsar. You can also easily open up that file by selecting the LNX: Edit > Snippets - MAC: Pulsar > Snippets - WIN: File > Snippets menu.

    Snippet Format

    So let's look at how to write a snippet. The basic snippet format looks like this:

    '.source.js':
    +  'console.log':
    +    'prefix': 'log'
    +    'body': 'console.log(\${1:"crash"});$2'
    +

    The leftmost keys are the selectors where these snippets should be active. The easiest way to determine what this should be is to go to the language package of the language you want to add a snippet for and look for the "Scope" string.

    For example, if we wanted to add a snippet that would work for Java files, we would look up the language-java package in our Settings view and we can see the Scope is source.java. Then the top level snippet key would be that prepended by a period (like a CSS class selector would do).

    Finding the selector scope for a snippet

    The next level of keys are the snippet names. These are used for describing the snippet in a more readable way in the snippet menu. You can name them whatever you want.

    Under each snippet name is a prefix that should trigger the snippet and a body to insert when the snippet is triggered.

    Each $ followed by a number is a tab stop. Tab stops are cycled through by pressing Tab once a snippet has been triggered.

    Tab stops with the same number will create multiple cursors.

    The above example adds a log snippet to JavaScript files that would expand to:

    console.log("crash");
    +

    The string "crash" would be initially selected and pressing tab again would place the cursor after the ;

    Warning

    Snippet keys, unlike CSS selectors, can only be repeated once per level. If there are duplicate keys at the same level, then only the last one will be read. See Configuring with CSON for more information.

    Multi-line Snippet Body

    `,27),On={href:"http://coffeescript.org/#strings",target:"_blank",rel:"noopener noreferrer"},Wn=e("code",null,'"""',-1),Hn=r(`
    '.source.js':
    +  'if, else if, else':
    +    'prefix': 'ieie'
    +    'body': """
    +      if (\${1:true}) {
    +        $2
    +      } else if (\${3:false}) {
    +        $4
    +      } else {
    +        $5
    +      }
    +    """
    +

    As you might expect, there is a snippet to create snippets. If you open up a snippets file and type snip and then press Tab, you will get the following text inserted:

    '.source.js':
    +  'Snippet Name':
    +    'prefix': 'hello'
    +    'body': 'Hello World!'
    +

    \u{1F4A5} Just fill that bad boy out and you have yourself a snippet. As soon as you save the file, Pulsar should reload the snippets and you will immediately be able to try it out.

    Multiple Snippets per Source

    You can see below the format for including multiple snippets for the same scope in your snippets.cson file. Just include the snippet name, prefix, and body keys for additional snippets inside the scope key:

    '.source.gfm':
    +  'Hello World':
    +    'prefix': 'hewo'
    +    'body': 'Hello World!'
    +
    +  'Github Hello':
    +    'prefix': 'gihe'
    +    'body': 'Octocat says Hi!'
    +
    +  'Octocat Image Link':
    +    'prefix': 'octopic'
    +    'body': '![GitHub Octocat](https://assets-cdn.github.com/images/modules/logos_page/Octocat.png)'
    +

    Again, see Configuring with CSON for more information on CSON key structure and non-repeatability.

    More Info

    `,9),Gn={href:"https://github.com/pulsar-edit/snippets",target:"_blank",rel:"noopener noreferrer"},Dn={href:"https://github.com/pulsar-edit/language-html/blob/master/snippets/language-html.cson",target:"_blank",rel:"noopener noreferrer"},jn={href:"https://github.com/pulsar-edit/language-javascript/blob/master/snippets/language-javascript.cson",target:"_blank",rel:"noopener noreferrer"},Yn=r('

    Autocomplete

    If you're still looking to save some typing time, Pulsar also ships with simple autocompletion functionality.

    The autocomplete system lets you view and insert possible completions in the editor using Tab or Enter.

    Autocomplete menu

    By default, the autocomplete system will look through the current open file for strings that match what you're starting to type.

    If you want more options, in the Settings panel for the autocomplete-plus package you can toggle a setting to make autocomplete-plus look for text in all your open buffers rather than just the current file.

    ',6),En={href:"https://github.com/pulsar-edit/autocomplete-plus",target:"_blank",rel:"noopener noreferrer"},zn=e("h2",{id:"folding",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#folding","aria-hidden":"true"},"#"),t(" Folding")],-1),Un=e("p",null,"If you want to see an overview of the structure of the code file you're working on, folding can be a helpful tool. Folding hides blocks of code such as functions or looping blocks in order to simplify what is on your screen.",-1),$n=e("p",null,[t("You can fold blocks of code by clicking the arrows that appear when you hover your mouse cursor over the gutter. You can also fold and unfold from the keyboard with the "),e("kbd",null,"Alt+Ctrl+["),t(" and "),e("kbd",null,"Alt+Ctrl+]"),t(" keybindings.")],-1),Bn=e("p",null,[e("img",{src:p,alt:"Code folding example",title:"Code folding example"})],-1),Vn=e("p",null,[t("To fold everything, use "),e("kbd",null,"Alt+Ctrl+Shift+["),t(" and to unfold everything use "),e("kbd",null,"Alt+Ctrl+Shift+]"),t(". You can also fold at a specific indentation level with "),e("kbd",null,"Ctrl+K"),t(),e("kbd",null,"Ctrl+0-9"),t(" where the number is the indentation depth.")],-1),Xn=e("p",null,[t("Finally, you can fold arbitrary sections of your code or text by making a selection and then typing "),e("kbd",null,"Alt+Ctrl+F"),t(' or choosing "Fold Selection" in the Command Palette.')],-1),Kn=e("p",null,[t("You can fold blocks of code by clicking the arrows that appear when you hover your mouse cursor over the gutter. You can also fold and unfold from the keyboard with the "),e("kbd",null,"Alt+Cmd+["),t(" and "),e("kbd",null,"Alt+Cmd+]"),t(" keybindings.")],-1),Jn=e("p",null,[e("img",{src:p,alt:"Code folding example",title:"Code folding example"})],-1),Zn=e("p",null,[t("To fold everything, use "),e("kbd",null,"Alt+Cmd+Shift+["),t(" and to unfold everything use "),e("kbd",null,"Alt+Cmd+Shift+]"),t(". You can also fold at a specific indentation level with "),e("kbd",null,"Cmd+K"),t(),e("kbd",null,"Cmd+0-9"),t(" where the number is the indentation depth.")],-1),Qn=e("p",null,[t("Finally, you can fold arbitrary sections of your code or text by making a selection and then typing "),e("kbd",null,"Alt+Cmd+Ctrl+F"),t(' or choosing "Fold Selection" in the Command Palette.')],-1),ea=e("p",null,[t("You can fold blocks of code by clicking the arrows that appear when you hover your mouse cursor over the gutter. You can also fold and unfold from the keyboard with the "),e("kbd",null,"Alt+Ctrl+["),t(" and "),e("kbd",null,"Alt+Ctrl+]"),t(" keybindings.")],-1),ta=e("p",null,[e("img",{src:p,alt:"Code folding example",title:"Code folding example"})],-1),na=e("p",null,[t("To fold everything, use "),e("kbd",null,"Alt+Ctrl+Shift+["),t(" and to unfold everything use "),e("kbd",null,"Alt+Ctrl+Shift+]"),t(". You can also fold at a specific indentation level with "),e("kbd",null,"Ctrl+K"),t(),e("kbd",null,"Ctrl+0-9"),t(" where the number is the indentation depth.")],-1),aa=e("p",null,[t("Finally, you can fold arbitrary sections of your code or text by making a selection and then typing "),e("kbd",null,"Alt+Ctrl+F"),t(' or choosing "Fold Selection" in the Command Palette.')],-1),oa=e("h2",{id:"panes",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#panes","aria-hidden":"true"},"#"),t(" Panes")],-1),sa=e("p",null,[t("You can split any editor pane horizontally or vertically by using "),e("kbd",null,"Ctrl+K"),t(),e("kbd",null,"Up/Down/Left/Right"),t(" where the direction key is the direction to split the pane. Once you have a split pane, you can switch between them with "),e("kbd",null,"Ctrl+K"),t(),e("kbd",null,"Ctrl+Up/Down/Left/Right"),t(" where the direction is the direction the focus should move to.")],-1),ia=e("p",null,[e("img",{src:u,alt:"Multiple panes",title:"Multiple panes"})],-1),la=e("p",null,'Each pane has its own "pane items", which are represented by tabs. You can move the files from pane to pane by dragging them with the mouse and dropping them in the pane you want that file to be in.',-1),ra=e("p",null,[t("To close a pane, you can close all pane items with "),e("kbd",null,"Ctrl+W"),t('. You can configure whether panes auto-close when empty in the Settings View with the "Remove Empty Panes" setting under Core Settings.')],-1),ca=e("p",null,[t("You can split any editor pane horizontally or vertically by using "),e("kbd",null,"Cmd+K"),t(),e("kbd",null,"Up/Down/Left/Right"),t(" where the direction key is the direction to split the pane. Once you have a split pane, you can switch between them with "),e("kbd",null,"Cmd+K"),t(),e("kbd",null,"Cmd+Up/Down/Left/Right"),t(" where the direction is the direction the focus should move to.")],-1),da=e("p",null,[e("img",{src:u,alt:"Multiple panes",title:"Multiple panes"})],-1),pa=e("p",null,'Each pane has its own "pane items", which are represented by tabs. You can move the files from pane to pane by dragging them with the mouse and dropping them in the pane you want that file to be in.',-1),ua=e("p",null,[t("To close a pane, you can close all pane items with "),e("kbd",null,"Cmd+W"),t('. You can configure whether panes auto-close when empty in the Settings View with the "Remove Empty Panes" setting under Core Settings.')],-1),ha=e("p",null,[t("You can split any editor pane horizontally or vertically by using "),e("kbd",null,"Ctrl+K"),t(),e("kbd",null,"Up/Down/Left/Right"),t(" where the direction key is the direction to split the pane. Once you have a split pane, you can switch between them with "),e("kbd",null,"Ctrl+K"),t(),e("kbd",null,"Ctrl+Up/Down/Left/Right"),t(" where the direction is the direction the focus should move to.")],-1),ma=e("p",null,[e("img",{src:u,alt:"Multiple panes",title:"Multiple panes"})],-1),ga=e("p",null,'Each pane has its own "pane items", which are represented by tabs. You can move the files from pane to pane by dragging them with the mouse and dropping them in the pane you want that file to be in.',-1),ba=e("p",null,[t("To close a pane, you can close all pane items with "),e("kbd",null,"Ctrl+W"),t('. You can configure whether panes auto-close when empty in the Settings View with the "Remove Empty Panes" setting under Core Settings.')],-1),fa={class:"custom-container tip"},ka=e("p",{class:"custom-container-title"},"Tip",-1),ya={href:"https://github.com/pulsar-edit/tabs",target:"_blank",rel:"noopener noreferrer"},va=r('

    Pending Pane Items

    "Pending Pane Items" were formerly referred to as "Preview Tabs"

    When you open a new file by single-clicking in the Tree View, it will open in a new tab with an italic title. This indicates that the file is "pending". When a file is pending, it will be replaced by the next pending file that is opened. This allows you to click through a bunch of files to find something without having to go back and close them all.

    You can confirm a pending file by doing any of the following:

    • Double-clicking the tab of the file
    • Double-clicking the file in the tree view
    • Editing the contents of the file
    • Saving the file

    You can also open a file already confirmed by double-clicking it in the tree view instead of single-clicking it.

    Disabling Pending Pane Items

    Allow Pending Pane Items setting

    If you would prefer to not have files open in pending form, you can disable this behavior by unchecking "Allow Pending Pane Items" in the Core Settings section of the Settings View. With pending pane items disabled, single-clicking a file in the Tree View will select the file but not open it. You will have to double-click the file to open it.

    Grammar

    The "grammar" of a file is what language Pulsar has associated with that file. Types of grammars would include "Java" or "GitHub-Flavored Markdown". We looked at this a bit when we created some snippets in Snippets.

    When you load a file, Pulsar does a little work to try to figure out what type of file it is. Largely this is accomplished by looking at its file extension (.md is generally a Markdown file, etc.), though sometimes it has to inspect the content a bit to figure it out.

    When you open a file and Pulsar can't determine a grammar for the file, it will default to "Plain Text", which is the simplest one. If it does default to "Plain Text", picks the wrong grammar for the file, or if for any reason you wish to change the selected grammar, you can pull up the Grammar Selector with Ctrl+Shift+L.

    Grammar Selector

    When the grammar of a file is changed, Atom will remember that for the current session.

    ',15),wa={href:"https://github.com/pulsar-edit/pulsar/tree/master/packages/grammar-selector",target:"_blank",rel:"noopener noreferrer"},_a=e("h2",{id:"version-control-in-pulsar",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#version-control-in-pulsar","aria-hidden":"true"},"#"),t(" Version Control in Pulsar")],-1),Ca={href:"https://git-scm.com",target:"_blank",rel:"noopener noreferrer"},xa={href:"https://github.com",target:"_blank",rel:"noopener noreferrer"},Sa=r('

    In order to use version control in Pulsar, the project root needs to contain the Git repository.

    Checkout HEAD revision

    The LNX/WIN: Alt+Ctrl+Z - MAC: Alt+Cmd+Z keybinding checks out the HEAD revision of the file in the editor.

    This is a quick way to discard any saved and staged changes you've made and restore the file to the version in the HEAD commit. This is essentially the same as running git checkout HEAD -- <path> and git reset HEAD -- <path> from the command line for that path.

    Git checkout

    This command goes onto the undo stack so you can use LNX/WIN: Ctrl+Z - MAC: Cmd+Z afterwards to restore the previous contents.

    Git status list

    ',7),Pa={href:"https://github.com/pulsar-edit/fuzzy-finder",target:"_blank",rel:"noopener noreferrer"},qa=e("strong",null,[e("em",null,"LNX/WIN")],-1),Ta=e("kbd",null,"Ctrl+T",-1),Aa=e("strong",null,[e("em",null,"MAC")],-1),Ia=e("kbd",null,"Cmd+T",-1),La=e("strong",null,[e("em",null,"LNX/WIN")],-1),Ma=e("kbd",null,"Ctrl+B",-1),Ra=e("strong",null,[e("em",null,"MAC")],-1),Na=e("kbd",null,"Cmd+B",-1),Fa=e("strong",null,[e("em",null,"LNX/WIN")],-1),Oa=e("kbd",null,"Ctrl+Shift+B",-1),Wa=e("strong",null,[e("em",null,"MAC")],-1),Ha=e("kbd",null,"Cmd+Shift+B",-1),Ga=e("code",null,"git status",-1),Da=e("p",null,[e("img",{src:M,alt:"Git status list",title:"`git status` list"})],-1),ja=e("p",null,"An icon will appear to the right of each file letting you know whether it is untracked or modified.",-1),Ya=e("h3",{id:"commit-editor",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#commit-editor","aria-hidden":"true"},"#"),t(" Commit editor")],-1),Ea={href:"https://github.com/pulsar-edit/language-git",target:"_blank",rel:"noopener noreferrer"},za=e("p",null,[e("img",{src:R,alt:"Git commit message highlighting",title:"Git commit message highlighting"})],-1),Ua=e("p",null,"You can configure Pulsar to be your Git commit editor with the following command:",-1),$a=r(`
    $ git config --global core.editor "pulsar --wait"
    +
    `,1),Ba={href:"https://github.com/pulsar-edit/language-git",target:"_blank",rel:"noopener noreferrer"},Va=e("h3",{id:"status-bar-icons",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#status-bar-icons","aria-hidden":"true"},"#"),t(" Status bar icons")],-1),Xa={href:"https://github.com/pulsar-edit/status-bar",target:"_blank",rel:"noopener noreferrer"},Ka=e("p",null,[e("img",{src:N,alt:"Git Status Bar decorations",title:"Git Status Bar decorations"})],-1),Ja=e("p",null,"The currently checked out branch name is shown with the number of commits the branch is ahead of or behind its upstream branch. An icon is added if the file is untracked, modified, or ignored. The number of lines added and removed since the file was last committed will be displayed as well.",-1),Za=e("h3",{id:"line-diffs",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#line-diffs","aria-hidden":"true"},"#"),t(" Line diffs")],-1),Qa={href:"https://github.com/pulsar-edit/git-diff",target:"_blank",rel:"noopener noreferrer"},eo=r('

    Git line diff indications

    This package also adds Alt+G Down and Alt+GUp keybindings that allow you to move the cursor to the next or previous diff in the current editor.

    Open on GitHub

    If the project you're working on is on GitHub, there are also some very useful integrations you can use. Most of the commands will take the current file you're viewing and open a view of that file on GitHub - for instance, the blame or commit history of that file.

    • Alt+G O - Open file on GitHub
    • Alt+G B - Open Blame view of file on GitHub
    • Alt+G H - Open History view of file on GitHub
    • Alt+G C - Copy the URL of the current file on GitHub to the clipboard
    • Alt+G R - Branch compare on GitHub

    The branch comparison shows you the commits that are on the branch you're currently working on locally that are not on the mainline branch.

    Open Blame of file on GitHub

    GitHub package

    The GitHub package brings Git and GitHub integration right inside Pulsar.

    Most of the functionality lives within the Git and GitHub dock items.

    The Git and GitHub panels

    There are different ways to access them, probably the most common way is through their keybindings:

    • Open the Git panel: Ctrl+9
    • Open the GitHub panel: Ctrl+8

    Another way is from the menu: Packages -> GitHub -> Toggle Git Tab and Toggle GitHub Tab

    Or you can also toggle the Git panel from the Status Bar by clicking on the changed files icon:

    Open Git panel


    Initialize repositories

    In case a project doesn't have a Git repository yet, you can create one from the Git panel.

    Initialize repositories

    Clone repositories

    To clone a repository, open the GitHub panel while you have no project folders open in Pulsar and click "Clone an existing GitHub repository". In the dialog, paste the URL of a repository and click "Clone". The new project will be added to the Tree View.

    GitHub panel

    Clone dialog

    Alternately, run the GitHub: Clone command to open the Clone dialog any time.

    Branch

    To open the branch tooltip, click the branch icon in the Status Bar. From there you can create or switch branches.

    Create or switch branches

    Stage

    After making some changes, stage anything you want to be part of the next commit. Choose between staging...

    • All changes: Click the "Stage All" button in the "Unstaged Changes" bar.
    • Files: Double-click a file or select a file and press Enter.
    • Hunk: Click on the "Stage Hunk" button or select a hunk and press Enter.
    • Lines: Click on a line (or drag on multiple lines) to select, then click on the "Stage Selection" button. Or use the LNX/WIN: Ctrl+/ - MAC: Cmd-/ key to toggle from hunk mode to line mode, then press LNX/WIN: Ctrl-Enter - MAC: Cmd-Enter to stage just a single line.

    Use the LNX/WIN: Cmd-Left - MAC: Ctrl-Left or LNX/WIN: Ctrl-Right - MAC: Cmd-Right arrow key to switch between file list and the diff view. Unstaging can be done in the same way.

    Stage changes

    Discard changes

    If you no longer want to keep some changes, you can discard them. It's similar to staging, but accessible behind a context menu.

    • All changes: Click the ... menu in the "Unstaged Changes" header and choose "Discard All Changes".
    • Files: Right-click a file (or multiple) and choose "Discard Changes".
    • Hunk: Click on the trash icon in the top bar of a hunk.
    • Lines: Right-click on a line (or multiple) and choose "Discard Selection".

    Discard changes

    Commit Preview

    To double check all changes that are going into your next commit, click the "See All Staged Changes" button above the commit message box. It lets you see all of your staged changes in a single pane. This "commit preview" can also serve as an inspiration for writing the commit message.

    Commit Preview

    Commit

    Once you've staged your changes, enter a commit message. Feel free to describe the commit in more detail after leaving an empty line. Finalize by clicking the Commit button. If you need more space, click the expand icon at the bottom right. It will open a commit editor in the center.

    Commit changes

    To add multiple co-authors to a commit, click the "\u{1F464}\u2795" icon in the bottom left corner of the commit message editor. Now you can search by name, email or GitHub username to give credit to a co-author.

    Commit with co-authors

    Amend and undo

    In case you forgot to commit a change and would like to add it to your previous commit, right-click on the last commit, then choose "Amend" from the context menu.

    Amend previous commit

    If you want to edit the commit message of your last commit, or add/remove changes, click on the "Undo" button. It will roll back to the state just before you clicked on the commit button.

    Undo previous commit

    View commits

    Once you've made some commits, click on a commit message in the recent commit list to see the full diff and commit message associated with each:

    View commit detai

    Publish and push

    When you're ready to share your changes with your team members, click the Publish button in the Status Bar. It will push your local branch to the remote repository. After making more commits, you can Push them as well from the Status Bar.

    Publish and push commits

    Fetch and pull

    From time to time it's a good idea to click on the Fetch button to see if any other team member pushed changes. If so, click on Pull to merge the changes into your local branch.

    Fetch and pull commits

    If you prefer to rebase when pulling, you can configure Git to make it the default behavior:

    git config --global --bool pull.rebase true
    +
    `,62),to={href:"https://mislav.net/2013/02/merge-vs-rebase/",target:"_blank",rel:"noopener noreferrer"},no=r('

    Resolve conflicts

    Sometimes there can be conflicts when trying to merge. Files that have merge conflicts will show up in the "Merge Conflicts" list. Click on a file to open the editor. There you can resolve the conflict by picking a version or make further edits. Once done, stage the file and commit.

    Resolve conflicts

    Create a Pull Request

    When your changes are ready to be reviewed by your team members, open the "GitHub" panel Ctrl+8 and click on Open new pull request. It will open the browser where you can continue creating a pull request. If commits haven't been pushed or the branch isn't published yet, the GitHub package will do that automatically for you.

    Create a Pull Request

    View Pull Requests

    Once the pull request is created, it will appear under Current pull request at the top of the panel. Underneath is a list of Open pull requests. It lets you quickly find a pull request by avatar, title or PR number. It also lets you keep an eye on the CI status. Clicking on a pull request in the list opens a center pane with more details, the timeline and conversations.

    View Pull Requests

    Open any Issue or Pull Request

    You can open issues or pull requests from any repo on GitHub. To do so, run the GitHub: Open Issue Or Pull Request command and paste the URL from an issue or pull request. Then press the Open Issue or Pull Request button and it will open a center pane. This lets you keep an issue or pull request as a reference, when working in another repo.

    Open Issue or Pull Request

    Checkout a Pull Request

    To test a pull request locally, open it in the workspace center by clicking on the pull request in the "open pull requests" list from the GitHub tab, then click on the Checkout button. It will automatically create a local branch and pull all the changes. If you would like to contribute to that pull request, start making changes, commit and push. Your contribution is now part of that pull request.

    Checkout a pull request

    View Pull Request review comments

    To view review comments on a Pull Request, open the Reviews Tab from the See Reviews button from the footer of a Pull Request Pane. Alternatively, if the pull request has already been checked out, Reviews Tab can also be open from the same button on GitHub Tab.

    Open review tab from footer

    You can see all the review summaries and comments of a pull request in the Reviews Tab. The comment section has a progress bar to help you keep track of how close are you to finish addressing the Pull Request comments (i.e. marking all comment threads on a Pull Request as "resolved"). Comment threads are greyed out after they have been resolved.

    Review tab

    After the pull request branch has been checked out, you can click Jump To File to open the commented on file and make changes as per the review comment right in the editor. If you would like to get the full context of the review comment, click Open Diff to open the diff view with line highlighting.

    Jump to file from review tab

    Conversely, in-editor comments are indicated by the comment icon in the gutter. Clicking the icon, either from within the editor or the diff view, will take you back to the Reviews Tab.

    Open review tab from diff

    Respond to a Pull Request review comment

    To respond to a Pull Request review comment, type your message and click Comment; a single line comment will be created in the same thread as the comment you responded to. After addressing a Pull Request review comment, click Resolve conversation to mark the whole thread as "resolved". The progress bar in the "Comments" section will update accordingly.

    Respond to a Pull Request review comment

    Writing in Pulsar

    ',29),ao={href:"https://help.github.com/articles/about-writing-and-formatting-on-github/",target:"_blank",rel:"noopener noreferrer"},oo=r('

    In these docs, we'll concentrate on writing in Markdown; however, other prose markup languages like Asciidoc have packages that provide similar functionality.

    Spell Checking

    If you're working in text (which includes plain text files, GitHub markdown, and Git commit messages by default), Pulsar will automatically try to check your spelling.

    Any misspelled words will be highlighted (by default with a dashed red line beneath the word), and you can pull up a menu of possible corrections by hitting LNX/WIN: Ctrl+Shift+; - MAC: Cmd+Shift+; (or by choosing "Correct Spelling" from the right-click context menu or from the Command Palette).

    Checking your spelling

    To add more types of files to the list of what Pulsar will try to spell check, go to the Spell Check package settings in your Settings view and add any grammars you want to spell check.

    The default grammars to spell check are text.plain, source.gfm, text.git-commit, source.asciidoc, source.rst, and text.restructuredtext but you can add other grammars if you wish to check those types of files too.

    ',7),so={href:"https://github.com/pulsar-edit/spell-check",target:"_blank",rel:"noopener noreferrer"},io=r('

    Previews

    When writing prose in a markup language, it's often very useful to get an idea of what the content will look like when it's rendered. Pulsar ships with a package for previewing Markdown by default.

    • Ctrl+Shift+M - Will toggle Preview mode for Markdown.

    Preview your prose

    As you edit the text, the preview will also update automatically. This makes it fairly easy to check your syntax as you type.

    You can also copy the rendered HTML from the preview pane into your system clipboard when the preview is focused and you press LNX: Ctrl+Ins - WIN: Ctrl+C - MAC: Cmd+C or if you right-click in the preview pane and choose "Copy as HTML".

    ',6),lo={href:"https://github.com/pulsar-edit/markdown-preview",target:"_blank",rel:"noopener noreferrer"},ro=r(`

    Snippets

    There are also a number of great snippets available for writing Markdown quickly.

    If you type img and hit tab you get a Markdown-formatted image embed code like ![](). If you type table and hit tab you get a nice example table to fill out.

    | Header One | Header Two |
    +| :--------- | :--------- |
    +| Item One   | Item Two   |
    +

    Although there are only a handful of Markdown snippets (b for bold, i for italic, code for a code block, etc), they save you from having to look up the more obscure syntaxes. Again, you can easily see a list of all available snippets for the type of file you're currently in by choosing Snippets: Available in the Command Palette.

    Basic Customization

    Now that we are feeling comfortable with just about everything built into Pulsar, let's look at how to tweak it. Perhaps there is a keybinding that you use a lot but feels wrong or a color that isn't quite right for you. Pulsar is amazingly flexible, so let's go over some of the simpler flexes it can do.

    Configuring with CSON

    `,8),co=e("a",{href:"#style-tweaks"},"style sheet",-1),po=e("a",{href:"./core-hacking#the-init-file"},"Init Script",-1),uo={href:"https://github.com/bevry/cson#what-is-cson",target:"_blank",rel:"noopener noreferrer"},ho={href:"https://json.org/",target:"_blank",rel:"noopener noreferrer"},mo=r(`
    key:
    +  key: value
    +  key: value
    +  key: [value, value]
    +

    Objects are the backbone of any CSON file, and are delineated by indentation (as in the above example). A key's value can either be a String, a Number, an Object, a Boolean, null, or an Array of any of these data types.

    Warning

    Just like the more common JSON, CSON's keys can only be repeated once per object. If there are duplicate keys, then the last usage of that key overwrites all others, as if they weren't there. The same holds true for Pulsar's config files.

    Don't do this:

    # Only the second snippet will be loaded
    +'.source.js':
    +  'console.log':
    +    'prefix': 'log'
    +    'body': 'console.log(\${1:"crash"});$2'
    +'.source.js':
    +  'console.error':
    +    'prefix': 'error'
    +    'body': 'console.error(\${1:"crash"});$2'
    +

    Use this instead:

    # Both snippets will be loaded
    +'.source.js':
    +  'console.log':
    +    'prefix': 'log'
    +    'body': 'console.log(\${1:"crash"});$2'
    +  'console.error':
    +    'prefix': 'error'
    +    'body': 'console.error(\${1:"crash"});$2'
    +

    Style Tweaks

    If you want to apply quick-and-dirty personal styling changes without creating an entire theme that you intend to publish, you can add styles to the styles.less file in your LNX/MAC: ~/.pulsar - WIN: %USERPROFILE%\\.pulsar directory. You can open this file in an editor from the LNX: Edit > Stylesheet - MAC: Pulsar > Stylesheet - WIN: File > Stylesheet menu.

    For example, to change the colors of the Status Bar, you could add the following rule to your styles.less file:

    .status-bar {
    +	color: white;
    +	background-color: black;
    +}
    +

    The easiest way to see what classes are available to style is to inspect the DOM manually via the Developer Tools. We'll go over the Developer Tools in great detail in the next chapter, but for now let's take a simple look. You can open the Developer Tools by pressing LNX/WIN: Ctrl+Shift+I - MAC: Alt+Cmd+I, which will bring up the Chromium Developer Tools panel.

    Developer Tools

    With the Developer Tools, you can inspect all the elements in Pulsar. If you want to update the style of something, you can figure out what classes it has and add a Less rule to your stylesheet to modify it.

    ',10),go={class:"custom-container tip"},bo=e("p",{class:"custom-container-title"},"Tip",-1),fo={href:"http://www.lesscss.org",target:"_blank",rel:"noopener noreferrer"},ko=e("p",null,[t("If you prefer to use CSS instead, you can do that in the same "),e("code",null,"styles.less"),t(" file, since CSS is also valid in Less.")],-1),yo=r(`

    Customizing Keybindings

    Pulsar keymaps work similarly to stylesheets. Just as stylesheets use selectors to apply styles to elements, Pulsar keymaps use selectors to associate key combinations with events in specific contexts. Here's a small example, excerpted from Pulsar's built-in keymap:

    'atom-text-editor':
    +  'enter': 'editor:newline'
    +
    +'atom-text-editor[mini] input':
    +  'enter': 'core:confirm'
    +

    This keymap defines the meaning of Enter in two different contexts. In a normal editor, pressing Enter triggers the editor:newline command, which causes the editor to insert a newline. But if the same keystroke occurs inside a select list's mini-editor, it instead triggers the core:confirm command based on the binding in the more-specific selector.

    By default, keymap.cson is loaded when Pulsar is started. It will always be loaded last, giving you the chance to override bindings that are defined by Pulsar's core keymaps or third-party packages. You can open this file in an editor from the LNX: Edit > Keymap - MAC: Pulsar > Keymap - WIN: File > Keymap menu.

    You can see all the keybindings that are currently configured in your installation of Pulsar in the Keybindings tab in the Settings View.

    If you run into problems with keybindings, the Keybinding Resolver is a huge help. It can be opened with the LNX/WIN: Ctrl+. - MAC: Cmd+. key combination. It will show you what keys Pulsar saw you press and what command Pulsar executed because of that combination.

    Global Configuration Settings

    Pulsar loads configuration settings from the config.cson file in your LNX/MAC: ~/.pulsar - WIN: %USERPROFILE%\\.pulsar directory.

    '*':
    +  'core':
    +    'excludeVcsIgnoredPaths': true
    +  'editor':
    +    'fontSize': 18
    +

    The configuration is grouped into global settings under the * key and language-specific settings under scope named keys like .python.source or .html.text. Underneath that, you'll find configuration settings grouped by package name or one of the two core namespaces: core or editor.

    You can open this file in an editor from the LNX: Edit > Config - MAC: Pulsar > Config - WIN: File > Config menu.

    Configuration Key Reference

    • core
      • customFileTypes: Associations of language scope to file extensions (see Customizing Language Recognition)
      • disabledPackages: An array of package names to disable
      • excludeVcsIgnoredPaths: Don't search within files specified by .gitignore
      • ignoredNames: File names to ignore across all of Pulsar
      • projectHome: The directory where projects are assumed to be located
      • themes: An array of theme names to load, in cascading order
    • editor
      • autoIndent: Enable/disable basic auto-indent (defaults to true)
      • nonWordCharacters: A string of non-word characters to define word boundaries
      • fontSize: The editor font size
      • fontFamily: The editor font family
      • invisibles: A hash of characters Pulsar will use to render whitespace characters. Keys are whitespace character types, values are rendered characters (use value false to turn off individual whitespace character types)
        • tab: Hard tab characters
        • cr: Carriage return (for Microsoft-style line endings)
        • eol: \\n characters
        • space: Leading and trailing space characters
      • lineHeight: Height of editor lines, as a multiplier of font size
      • preferredLineLength: Identifies the length of a line (defaults to 80)
      • showInvisibles: Whether to render placeholders for invisible characters (defaults to false)
      • showIndentGuide: Show/hide indent indicators within the editor
      • showLineNumbers: Show/hide line numbers within the gutter
      • softWrap: Enable/disable soft wrapping of text within the editor
      • softWrapAtPreferredLineLength: Enable/disable soft line wrapping at preferredLineLength
      • tabLength: Number of spaces within a tab (defaults to 2)
    • fuzzyFinder
      • ignoredNames: Files to ignore only in the fuzzy-finder
    • whitespace
      • ensureSingleTrailingNewline: Whether to reduce multiple newlines to one at the end of files
      • removeTrailingWhitespace: Enable/disable stripping of whitespace at the end of lines (defaults to true)
    • wrap-guide
      • columns: Array of hashes with a pattern and column key to match the path of the current editor to a column position.

    Language Specific Configuration Settings

    You can also set several configuration settings differently for different file types. For example, you may want Pulsar to soft wrap markdown files, have two-space tabs for ruby files, and four-space tabs for python files.

    There are several settings now scoped to an editor's language. Here is the current list:

    editor.autoIndent
    +editor.autoIndentOnPaste
    +editor.invisibles
    +editor.nonWordCharacters
    +editor.preferredLineLength
    +editor.scrollPastEnd
    +editor.showIndentGuide
    +editor.showInvisibles
    +editor.softWrap
    +editor.softWrapAtPreferredLineLength
    +editor.softWrapHangingIndent
    +editor.tabLength
    +

    Language-specific Settings in the Settings View

    You can edit these config settings in the Settings View on a per-language basis. Click on "Packages" tab in the navigation bar on the left, search for the language of your choice, select it, and edit away!

    Python-specific settings

    Language-specific Settings in your Config File

    You can also edit the config.cson directly. To open your configuration file via the Command Palette, press LNX/WIN: Ctrl+Shift+P - MAC: Cmd+Shift+P type open config, and press Enter.

    Global settings are under the * key, and each language can have its own top-level key. This key is the language's scope. Language-specific settings take precedence over anything set in the global section for that language only.

    '*': # all languages unless overridden
    +  'editor':
    +    'softWrap': false
    +    'tabLength': 8
    +
    +'.source.gfm': # markdown overrides
    +  'editor':
    +    'softWrap': true
    +
    +'.source.ruby': # ruby overrides
    +  'editor':
    +    'tabLength': 2
    +
    +'.source.python': # python overrides
    +  'editor':
    +    'tabLength': 4
    +

    Finding a Language's Scope Name

    In order to write these overrides effectively, you'll need to know the scope name for the language. We've already done this for finding a scope for writing a snippet in Snippet Format, but we can quickly cover it again.

    The scope name is shown in the settings view for each language. Click on "Packages" in the navigation on the left, search for the language of your choice, select it, and you should see the scope name under the language name heading:

    Finding a language grammar

    Another way to find the scope for a specific language is to open a file of its kind and:

    • LNX/WIN: Choose "Editor: Log Cursor Scope" in the Command Palette
    • MAC: Press Alt+Cmd+P to show all scopes for the current position of the cursor. The scope mentioned top most is always the language for this kind of file, the scopes following are specific to the cursor position:

    Finding a language grammar with cursor scope

    These scopes can be especially useful to style the editor, since they can also be used as class names in your stylesheet.

    Customizing Language Recognition

    If you want Pulsar to always recognize certain file types as a specific grammar, you'll need to manually edit your config.cson file. You can open it using the Application: Open Your Config command from the Command Palette. For example, if you wanted to add the foo extension to the CoffeeScript language, you could add this to your configuration file under the *.core section:

    '*':
    +  core:
    +    customFileTypes:
    +      'source.coffee': [
    +        'foo'
    +      ]
    +

    In the example above, source.coffee is the language's scope name (see Finding a Language's Scope Name for more information) and foo is the file extension to match without the period. Adding a period to the beginning of either of these will not work.

    Controlling Where Customization is Stored to Simplify Your Workflow

    The CSON configuration files for Pulsar are stored on disk on your machine. The location for this storage is customizable. The default is to use the home directory of the user executing the application. The Pulsar Home directory will, by default, be called .pulsar and will be located in the root of the home directory of the user.

    Custom home location with an environment variable

    An environment variable can be used to make Pulsar use a different location. This can be useful for several reasons. One of these may be that multiple user accounts on a machine want to use the same Pulsar Home. The environment variable used to specify an alternate location is called ATOM_HOME. If this environment variable exists, the location specified will be used to load and store Pulsar settings.

    Taking your customization with you with Portable Mode

    In addition to using the ATOM_HOME environment variable, Pulsar can also be set to use "Portable Mode".

    Portable Mode is most useful for taking Pulsar with you, with all your custom settings and packages, from machine to machine. This may take the form of keeping Pulsar on a USB drive or a cloud storage platform that syncs folders to different machines, like Dropbox. Pulsar is in Portable Mode when there is a directory named .pulsar sibling to the directory in which the pulsar executable file lives. For example, the installed Pulsar directory can be placed into a Dropbox folder next to a .pulsar folder.

    Portable mode directory structure

    With such a setup, Pulsar will use the same Home directory with the same settings for any machine with this directory syncronized/plugged in.

    Moving to Portable Mode

    Pulsar provides a command-line parameter option for setting Portable Mode.

    $ pulsar --portable
    +

    Executing pulsar with the --portable option will take the .pulsar directory you have in the default location (~/.pulsar) and copy the relevant contents for your configuration to a new home directory in the Portable Mode location. This enables easily moving from the default location to a portable operation without losing the customization you have already set up.

    Summary

    At this point you should be something of a Pulsar master user. You should be able to navigate and manipulate your text and files like a wizard. You should also be able to customize Pulsar backwards and forwards to make it look and act just how you want it to.

    In the next section, we're going to kick it up a notch: we'll take a look at changing and adding new functionality to the core of Pulsar itself. We're going to start creating packages for Pulsar. If you can dream it, you can build it.

    `,53);function vo(wo,_o){const a=d("ExternalLinkIcon"),m=d("RouterLink"),c=d("Tabs");return ke(),ye("div",null,[we,e("p",null,[t("First we'll start with the Pulsar package system. As we mentioned previously, Pulsar itself is a very basic core of functionality that ships with a number of useful packages that add new features like the "),e("a",_e,[t("Tree View"),n(a)]),t(" and the "),e("a",Ce,[t("Settings View"),n(a)]),t(".")]),e("p",null,[t("In fact, there are more than 80 packages that comprise all of the functionality that is available in Pulsar by default. For example, the "),e("a",xe,[t("Welcome screen"),n(a)]),t(" that you see when you first start Pulsar, the "),e("a",Se,[t("spell checker"),n(a)]),t(", the "),e("a",Pe,[t("themes"),n(a)]),t(" and the "),e("a",qe,[t("Fuzzy Finder"),n(a)]),t(" are all packages that are separately maintained and all use the same APIs that you have access to, as we'll see in great detail in "),n(m,{to:"/docs/launch-manual/sections/core-hacking/"},{default:o(()=>[t("Hacking the Core")]),_:1}),t(".")]),Te,Ae,e("p",null,[t("The packages listed here have been published to "),e("a",Ie,[t("https://web.pulsar-edit.dev"),n(a)]),h("TODO:Change address to final URL (if this is not it)"),t(" which is the official registry for Pulsar packages. Searching on the Settings View will go to the Pulsar package registry and pull in anything that matches your search terms.")]),Le,e("p",null,[t("For example "),Me,t(" installs the "),Re,t(" release of the "),e("a",Ne,[t("minimap"),n(a)]),t(" package.")]),Fe,e("p",null,[t("By default ppm will be using the "),e("a",Oe,[t("Pulsar Package Repository"),n(a)]),t(". However you can also use it to install from other locations which can be useful if you are trying to install a package not published to the Pulsar Package Repository.")]),We,e("p",null,[t("For example to install the "),e("a",He,[t("Generic-LSP"),n(a)]),t(" package from GitHub you could use either:")]),Ge,n(c,{id:"154",data:[{title:"Linux"},{title:"macOS"},{title:"Windows"}],"tab-id":"using-pulsar"},{tab0:o(({title:s,value:i,isActive:l})=>[De,je,Ye]),tab1:o(({title:s,value:i,isActive:l})=>[Ee,ze,Ue]),tab2:o(({title:s,value:i,isActive:l})=>[$e,Be,Ve]),_:1}),Xe,n(c,{id:"288",data:[{title:"Linux"},{title:"macOS"},{title:"Windows"}],"tab-id":"using-pulsar "},{tab0:o(({title:s,value:i,isActive:l})=>[Ke]),tab1:o(({title:s,value:i,isActive:l})=>[Je]),tab2:o(({title:s,value:i,isActive:l})=>[Ze]),_:1}),Qe,n(c,{id:"305",data:[{title:"Linux"},{title:"macOS"},{title:"Windows"}],"tab-id":"using-pulsar"},{tab0:o(({title:s,value:i,isActive:l})=>[et]),tab1:o(({title:s,value:i,isActive:l})=>[tt]),tab2:o(({title:s,value:i,isActive:l})=>[nt]),_:1}),at,e("p",null,[t("You can generate a "),ot,t(" file by using the "),e("a",st,[t("ctags utility"),n(a)]),t(". Once it is installed, you can use it to generate a "),it,t(" file by running a command to generate it. See the "),e("a",lt,[t("ctags documentation"),n(a)]),t(" for details.")]),n(c,{id:"328",data:[{title:"Linux"},{title:"macOS"},{title:"Windows"}],"tab-id":"using-pulsar"},{tab0:o(({title:s,value:i,isActive:l})=>[rt]),tab1:o(({title:s,value:i,isActive:l})=>[ct]),tab2:o(({title:s,value:i,isActive:l})=>[dt]),_:1}),e("p",null,[t("You can customize how tags are generated by creating your own "),pt,t(" file in your home directory, "),ut,t(": "),ht,t(" - "),mt,t(": "),gt,t(". An example can be found "),e("a",bt,[t("here"),n(a)]),t(".")]),e("p",null,[t("The symbols navigation functionality is implemented in the "),e("a",ft,[t("symbols-view"),n(a)]),t(" package.")]),kt,e("p",null,[t("The bookmarks functionality is implemented in the "),e("a",yt,[t("bookmarks"),n(a)]),t(" package.")]),vt,wt,_t,n(c,{id:"383",data:[{title:"Linux"},{title:"macOS"},{title:"Windows"}],"tab-id":"using-pulsar"},{tab0:o(({title:s,value:i,isActive:l})=>[Ct,xt,St]),tab1:o(({title:s,value:i,isActive:l})=>[Pt,qt,Tt]),tab2:o(({title:s,value:i,isActive:l})=>[At,It,Lt]),_:1}),Mt,Rt,Nt,Ft,n(c,{id:"611",data:[{title:"Linux"},{title:"macOS"},{title:"Windows"}],"tab-id":"using-pulsar"},{tab0:o(({title:s,value:i,isActive:l})=>[Ot]),tab1:o(({title:s,value:i,isActive:l})=>[Wt]),tab2:o(({title:s,value:i,isActive:l})=>[Ht]),_:1}),Gt,n(c,{id:"714",data:[{title:"Linux"},{title:"macOS"},{title:"Windows"}],"tab-id":"using-pulsar"},{tab0:o(({title:s,value:i,isActive:l})=>[Dt]),tab1:o(({title:s,value:i,isActive:l})=>[jt]),tab2:o(({title:s,value:i,isActive:l})=>[Yt]),_:1}),Et,zt,n(c,{id:"794",data:[{title:"Linux"},{title:"macOS"},{title:"Windows"}],"tab-id":"using-pulsar"},{tab0:o(({title:s,value:i,isActive:l})=>[Ut]),tab1:o(({title:s,value:i,isActive:l})=>[$t]),tab2:o(({title:s,value:i,isActive:l})=>[Bt]),_:1}),Vt,e("p",null,[t("The whitespace commands are implemented in the "),e("a",Xt,[t("whitespace"),n(a)]),t(" package. The settings for the whitespace commands are managed on the page for the "),Kt,t(" package.")]),Jt,n(c,{id:"923",data:[{title:"Linux"},{title:"macOS"},{title:"Windows"}],"tab-id":"using-pulsar"},{tab0:o(({title:s,value:i,isActive:l})=>[Zt]),tab1:o(({title:s,value:i,isActive:l})=>[Qt]),tab2:o(({title:s,value:i,isActive:l})=>[en]),_:1}),e("p",null,[t("The brackets functionality is implemented in the "),e("a",tn,[t("bracket-matcher"),n(a)]),t(" package. Like all of these packages, to change defaults related to bracket handling, or to disable it entirely, you can navigate to this package in the Settings view.")]),nn,an,n(c,{id:"991",data:[{title:"Linux"},{title:"macOS"},{title:"Windows"}],"tab-id":"using-pulsar"},{tab0:o(({title:s,value:i,isActive:l})=>[on]),tab1:o(({title:s,value:i,isActive:l})=>[sn]),tab2:o(({title:s,value:i,isActive:l})=>[ln]),_:1}),rn,cn,dn,pn,e("p",null,[t("The encoding selector is implemented in the "),e("a",un,[t("encoding-selector"),n(a)]),t(" package.")]),hn,mn,n(c,{id:"1031",data:[{title:"Linux"},{title:"macOS"},{title:"Windows"}],"tab-id":"using-pulsar"},{tab0:o(({title:s,value:i,isActive:l})=>[gn]),tab1:o(({title:s,value:i,isActive:l})=>[bn]),tab2:o(({title:s,value:i,isActive:l})=>[fn]),_:1}),kn,e("div",yn,[vn,wn,e("p",null,[t("When doing a regular expression search, the replacement syntax to refer back to search groups is $1, $2, \u2026 $&. Refer to JavaScript's "),e("a",_n,[t("guide to regular expressions"),n(a)]),t(" to learn more about regular expression syntax you can use in Pulsar.")])]),Cn,e("p",null,[t("You can limit a search to a subset of the files in your project by entering a "),e("a",xn,[t("glob pattern"),n(a)]),t(' into the "File/Directory pattern" text box. For example, the pattern '),Sn,t(" would restrict the search to JavaScript files in the "),Pn,t(' directory. The "globstar" pattern ('),qn,t(") can be used to match arbitrarily many subdirectories. For example, "),Tn,t(" will match "),An,t(", "),In,t(", etc. You can enter multiple glob patterns separated by commas, which is useful for searching in multiple file types or subdirectories.")]),Ln,Mn,e("p",null,[t("The Find and Replace functionality is implemented in the "),e("a",Rn,[t("find-and-replace"),n(a)]),t(" package and uses the "),e("a",Nn,[t("scandal"),n(a)]),t(" Node module to do the actual searching.")]),Fn,e("p",null,[t("You can also use "),e("a",On,[t("CoffeeScript multi-line syntax"),n(a)]),t(" using "),Wn,t(" for larger templates:")]),Hn,e("p",null,[t("The snippets functionality is implemented in the "),e("a",Gn,[t("snippets"),n(a)]),t(" package.")]),e("p",null,[t("For more examples, see the snippets in the "),e("a",Dn,[t("language-html"),n(a)]),t(" and "),e("a",jn,[t("language-javascript"),n(a)]),t(" packages.")]),Yn,e("p",null,[t("The Autocomplete functionality is implemented in the "),e("a",En,[t("autocomplete-plus"),n(a)]),t(" package.")]),zn,Un,n(c,{id:"1256",data:[{title:"linux"},{title:"macOS"},{title:"Windows"}],"tab-id":"using-pulsar"},{tab0:o(({title:s,value:i,isActive:l})=>[$n,Bn,Vn,Xn]),tab1:o(({title:s,value:i,isActive:l})=>[Kn,Jn,Zn,Qn]),tab2:o(({title:s,value:i,isActive:l})=>[ea,ta,na,aa]),_:1}),oa,n(c,{id:"1305",data:[{title:"linux"},{title:"macOS"},{title:"Windows"}],"tab-id":"using-pulsar"},{tab0:o(({title:s,value:i,isActive:l})=>[sa,ia,la,ra]),tab1:o(({title:s,value:i,isActive:l})=>[ca,da,pa,ua]),tab2:o(({title:s,value:i,isActive:l})=>[ha,ma,ga,ba]),_:1}),e("div",fa,[ka,e("p",null,[t("If you don't like using tabs, you don't have to. You can disable the "),e("a",ya,[t("tabs package"),n(a)]),t(" and each pane will still support multiple pane items. You just won't have tabs to use to click between them.")])]),va,e("p",null,[t("The Grammar Selector functionality is implemented in the "),e("a",wa,[t("grammar-selector"),n(a)]),t(" package.")]),_a,e("p",null,[t("Version control is an important aspect of any project and Pulsar comes with basic "),e("a",Ca,[t("Git"),n(a)]),t(" and "),e("a",xa,[t("GitHub"),n(a)]),t(" integration built in.")]),Sa,e("p",null,[t("Pulsar ships with the "),e("a",Pa,[t("fuzzy-finder package"),n(a)]),t(" which provides "),qa,t(": "),Ta,t(" - "),Aa,t(": "),Ia,t(" to quickly open files in the project and "),La,t(": "),Ma,t(" - "),Ra,t(": "),Na,t(" to jump to any open editor. The package also provides "),Fa,t(": "),Oa,t(" - "),Wa,t(": "),Ha,t(" which displays a list of all the untracked and modified files in the project. These will be the same files that you would see on the command line if you ran "),Ga,t(".")]),Da,ja,Ya,e("p",null,[t("Pulsar can be used as your Git commit editor and ships with the "),e("a",Ea,[t("language-git package"),n(a)]),t(" which adds syntax highlighting to edited commit, merge, and rebase messages.")]),za,Ua,h("TODO: Check this still works in Pulsar"),$a,e("p",null,[t("The "),e("a",Ba,[t("language-git"),n(a)]),t(" package will help remind you to be brief by colorizing the first lines of commit messages when they're longer than 50 or 65 characters.")]),Va,e("p",null,[t("The "),e("a",Xa,[t("status-bar"),n(a)]),t(" package that ships with Pulsar includes several Git decorations that display on the right side of the status bar:")]),Ka,Ja,Za,e("p",null,[t("The included "),e("a",Qa,[t("git-diff"),n(a)]),t(" package colorizes the gutter next to lines that have been added, edited, or removed.")]),eo,e("p",null,[t("Learn more about "),e("a",to,[t("merge vs. rebase"),n(a)]),t(".")]),no,e("p",null,[t("Though it is probably most common to use Pulsar to write software code, Pulsar can also be used to write prose quite effectively. Most often this is done in some sort of markup language such as Asciidoc or "),e("a",ao,[t("Markdown"),n(a)]),t(" (in which this manual is written). Here we'll quickly cover a few of the tools Pulsar provides for helping you write prose.")]),oo,e("p",null,[t("The spell checking is implemented in the "),e("a",so,[t("spell-check"),n(a)]),t(" package.")]),io,e("p",null,[t("Markdown preview is implemented in the "),e("a",lo,[t("markdown-preview"),n(a)]),t(" package.")]),ro,e("p",null,[t("All of Pulsar's config files (with the exception of your "),co,t(" and your "),po,t(" are written in CSON, short for "),e("a",uo,[t("CoffeeScript Object Notation"),n(a)]),t(". Just like its namesake JSON, "),e("a",ho,[t("JavaScript Object Notation"),n(a)]),t(", CSON is a text format for storing structured data in the form of simple objects made up of key-value pairs.")]),mo,e("div",go,[bo,e("p",null,[t("If you are unfamiliar with Less, it is a basic CSS preprocessor that makes some things in CSS a bit easier. You can learn more about it at "),e("a",fo,[t("lesscss.org"),n(a)]),t(".")]),ko]),yo])}const Go=fe(ve,[["render",vo],["__file","index.html.vue"]]);export{Go as default}; diff --git a/assets/index.html.bed1e49d.js b/assets/index.html.bed1e49d.js new file mode 100644 index 0000000000..980804fd15 --- /dev/null +++ b/assets/index.html.bed1e49d.js @@ -0,0 +1 @@ +import{_ as s,o as c,c as l,a,d as t,w as n,b as e,f as d,r as i}from"./app.0e1565ce.js";const h={},u=d('

    Packages Documentation

    This aims to be a collection of information about the packages published for the Pulsar Editor. Currently, anything in Core Packages is an archive of every Wiki that was published from the upstream Atom Organization.

    This means some of this information may be old, or no longer relevant. If it is, it has been marked as so at the top of the file under a warning. None-the-less the information can still be helpful, and may prove to be valuable. Which is why it has been archived here.

    As well, we have a record of community packages as well. This is meant to be a record of these. This section should be updated as we get more community contributions of these. The process for adding this will be added at a later date.

    Core Packages

    Updated

    • TODO: Add Documentation from updated packages documentation.

    Archived Documentation

    ',8),m=a("h2",{id:"community-packages",tabindex:"-1"},[a("a",{class:"header-anchor",href:"#community-packages","aria-hidden":"true"},"#"),e(" Community Packages")],-1),p=a("p",null,"There are currently thousands of community packages published to the Pulsar Package Registry, if you are coming from Atom you should be able to find all of your familiar packages as we have archived everything that Atom once had published.",-1),f={href:"https://pulsar-edit.dev/community.html",target:"_blank",rel:"noopener noreferrer"},g={href:"https://github.com/pulsar-edit/package-backend/blob/main/docs/reference/Admin_Actions.md",target:"_blank",rel:"noopener noreferrer"},k={href:"https://web.pulsar-edit.dev/",target:"_blank",rel:"noopener noreferrer"};function b(_,y){const o=i("RouterLink"),r=i("ExternalLinkIcon");return c(),l("div",null,[u,a("ul",null,[a("li",null,[t(o,{to:"/docs/packages/core/atom-languageclient/"},{default:n(()=>[e("Atom-LanguageClient")]),_:1})]),a("li",null,[t(o,{to:"/docs/packages/core/autocomplete-plus/"},{default:n(()=>[e("Autocomplete-Plus")]),_:1})]),a("li",null,[t(o,{to:"/docs/packages/core/github/"},{default:n(()=>[e("GitHub")]),_:1})]),a("li",null,[t(o,{to:"/docs/packages/core/ide-java/"},{default:n(()=>[e("IDE-Java")]),_:1})])]),m,p,a("p",null,[e("If one of your favorite packages that you had on Atom is missing feel free to "),a("a",f,[e("contact us"),t(r)]),e(" or take a look through the "),a("a",g,[e("Pulsar Package Registry Administrative Actions Log"),t(r)]),e(" to see if it's been officially removed.")]),a("p",null,[e("Otherwise to browse the vast collection of packages available to install on Pulsar you can open up the Pulsar Settings Package page, or browse them on the "),a("a",k,[e("Pulsar Package Registry Website"),t(r)]),e(".")])])}const w=s(h,[["render",b],["__file","index.html.vue"]]);export{w as default}; diff --git a/assets/index.html.bfae9c6a.js b/assets/index.html.bfae9c6a.js new file mode 100644 index 0000000000..8a47902127 --- /dev/null +++ b/assets/index.html.bfae9c6a.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-734cbc8c","path":"/docs/resources/glossary/","title":"Glossary","lang":"en-US","frontmatter":{"title":"Glossary"},"excerpt":"","headers":[],"git":{"updatedTime":1663961800000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":0.01,"words":3},"filePathRelative":"docs/resources/glossary/index.md"}');export{e as data}; diff --git a/assets/index.html.c211f99a.js b/assets/index.html.c211f99a.js new file mode 100644 index 0000000000..a654578b47 --- /dev/null +++ b/assets/index.html.c211f99a.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-a6cdf728","path":"/docs/atom-archive/shadow-dom/","title":"Appendix C : Shadow DOM","lang":"en-US","frontmatter":{"title":"Appendix C : Shadow DOM","sitemap":{"priority":0.1}},"excerpt":"","headers":[{"level":2,"title":"Shadow DOM","slug":"shadow-dom","link":"#shadow-dom","children":[{"level":3,"title":"Removing Shadow DOM styles","slug":"removing-shadow-dom-styles","link":"#removing-shadow-dom-styles","children":[]}]}],"git":{"updatedTime":1669438016000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":2},{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":1},{"name":"confused-Techie","email":"dev@lhbasics.com","commits":1}]},"readingTime":{"minutes":0.28,"words":85},"filePathRelative":"docs/atom-archive/shadow-dom/index.md"}');export{e as data}; diff --git a/assets/index.html.c48759b6.js b/assets/index.html.c48759b6.js new file mode 100644 index 0000000000..dafd6829c3 --- /dev/null +++ b/assets/index.html.c48759b6.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-44f94232","path":"/docs/launch-manual/sections/using-pulsar/","title":"Using Pulsar","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Pulsar Packages","slug":"pulsar-packages","link":"#pulsar-packages","children":[{"level":3,"title":"Package Settings","slug":"package-settings","link":"#package-settings","children":[]},{"level":3,"title":"Pulsar Themes","slug":"pulsar-themes","link":"#pulsar-themes","children":[]},{"level":3,"title":"Command Line","slug":"command-line","link":"#command-line","children":[]}]},{"level":2,"title":"Moving in Pulsar","slug":"moving-in-pulsar","link":"#moving-in-pulsar","children":[{"level":3,"title":"Additional Movement and Selection Commands","slug":"additional-movement-and-selection-commands","link":"#additional-movement-and-selection-commands","children":[]},{"level":3,"title":"Navigating by Symbols","slug":"navigating-by-symbols","link":"#navigating-by-symbols","children":[]},{"level":3,"title":"Bookmarks","slug":"bookmarks","link":"#bookmarks","children":[]}]},{"level":2,"title":"Pulsar Selections","slug":"pulsar-selections","link":"#pulsar-selections","children":[]},{"level":2,"title":"Editing and Deleting Text","slug":"editing-and-deleting-text","link":"#editing-and-deleting-text","children":[{"level":3,"title":"Basic Manipulation","slug":"basic-manipulation","link":"#basic-manipulation","children":[]},{"level":3,"title":"Deleting and Cutting","slug":"deleting-and-cutting","link":"#deleting-and-cutting","children":[]},{"level":3,"title":"Multiple Cursors and Selections","slug":"multiple-cursors-and-selections","link":"#multiple-cursors-and-selections","children":[]},{"level":3,"title":"Whitespace","slug":"whitespace","link":"#whitespace","children":[]},{"level":3,"title":"Brackets","slug":"brackets","link":"#brackets","children":[]},{"level":3,"title":"Encoding","slug":"encoding","link":"#encoding","children":[]}]},{"level":2,"title":"Find and Replace","slug":"find-and-replace","link":"#find-and-replace","children":[]},{"level":2,"title":"Snippets","slug":"snippets","link":"#snippets","children":[{"level":3,"title":"Creating Your Own Snippets","slug":"creating-your-own-snippets","link":"#creating-your-own-snippets","children":[]},{"level":3,"title":"More Info","slug":"more-info","link":"#more-info","children":[]}]},{"level":2,"title":"Autocomplete","slug":"autocomplete","link":"#autocomplete","children":[]},{"level":2,"title":"Folding","slug":"folding","link":"#folding","children":[]},{"level":2,"title":"Panes","slug":"panes","link":"#panes","children":[]},{"level":2,"title":"Pending Pane Items","slug":"pending-pane-items","link":"#pending-pane-items","children":[{"level":3,"title":"Disabling Pending Pane Items","slug":"disabling-pending-pane-items","link":"#disabling-pending-pane-items","children":[]}]},{"level":2,"title":"Grammar","slug":"grammar","link":"#grammar","children":[]},{"level":2,"title":"Version Control in Pulsar","slug":"version-control-in-pulsar","link":"#version-control-in-pulsar","children":[{"level":3,"title":"Checkout HEAD revision","slug":"checkout-head-revision","link":"#checkout-head-revision","children":[]},{"level":3,"title":"Git status list","slug":"git-status-list","link":"#git-status-list","children":[]},{"level":3,"title":"Commit editor","slug":"commit-editor","link":"#commit-editor","children":[]},{"level":3,"title":"Status bar icons","slug":"status-bar-icons","link":"#status-bar-icons","children":[]},{"level":3,"title":"Line diffs","slug":"line-diffs","link":"#line-diffs","children":[]},{"level":3,"title":"Open on GitHub","slug":"open-on-github","link":"#open-on-github","children":[]}]},{"level":2,"title":"GitHub package","slug":"github-package","link":"#github-package","children":[{"level":3,"title":"Initialize repositories","slug":"initialize-repositories","link":"#initialize-repositories","children":[]},{"level":3,"title":"Clone repositories","slug":"clone-repositories","link":"#clone-repositories","children":[]},{"level":3,"title":"Branch","slug":"branch","link":"#branch","children":[]},{"level":3,"title":"Stage","slug":"stage","link":"#stage","children":[]},{"level":3,"title":"Discard changes","slug":"discard-changes","link":"#discard-changes","children":[]},{"level":3,"title":"Commit Preview","slug":"commit-preview","link":"#commit-preview","children":[]},{"level":3,"title":"Commit","slug":"commit","link":"#commit","children":[]},{"level":3,"title":"Amend and undo","slug":"amend-and-undo","link":"#amend-and-undo","children":[]},{"level":3,"title":"View commits","slug":"view-commits","link":"#view-commits","children":[]},{"level":3,"title":"Publish and push","slug":"publish-and-push","link":"#publish-and-push","children":[]},{"level":3,"title":"Fetch and pull","slug":"fetch-and-pull","link":"#fetch-and-pull","children":[]},{"level":3,"title":"Resolve conflicts","slug":"resolve-conflicts","link":"#resolve-conflicts","children":[]},{"level":3,"title":"Create a Pull Request","slug":"create-a-pull-request","link":"#create-a-pull-request","children":[]},{"level":3,"title":"View Pull Requests","slug":"view-pull-requests","link":"#view-pull-requests","children":[]},{"level":3,"title":"Open any Issue or Pull Request","slug":"open-any-issue-or-pull-request","link":"#open-any-issue-or-pull-request","children":[]},{"level":3,"title":"Checkout a Pull Request","slug":"checkout-a-pull-request","link":"#checkout-a-pull-request","children":[]},{"level":3,"title":"View Pull Request review comments","slug":"view-pull-request-review-comments","link":"#view-pull-request-review-comments","children":[]},{"level":3,"title":"Navigate Pull Request review comments","slug":"navigate-pull-request-review-comments","link":"#navigate-pull-request-review-comments","children":[]},{"level":3,"title":"Respond to a Pull Request review comment","slug":"respond-to-a-pull-request-review-comment","link":"#respond-to-a-pull-request-review-comment","children":[]}]},{"level":2,"title":"Writing in Pulsar","slug":"writing-in-pulsar","link":"#writing-in-pulsar","children":[{"level":3,"title":"Spell Checking","slug":"spell-checking","link":"#spell-checking","children":[]},{"level":3,"title":"Previews","slug":"previews","link":"#previews","children":[]},{"level":3,"title":"Snippets","slug":"snippets-1","link":"#snippets-1","children":[]}]},{"level":2,"title":"Basic Customization","slug":"basic-customization","link":"#basic-customization","children":[{"level":3,"title":"Configuring with CSON","slug":"configuring-with-cson","link":"#configuring-with-cson","children":[]},{"level":3,"title":"Style Tweaks","slug":"style-tweaks","link":"#style-tweaks","children":[]},{"level":3,"title":"Customizing Keybindings","slug":"customizing-keybindings","link":"#customizing-keybindings","children":[]},{"level":3,"title":"Global Configuration Settings","slug":"global-configuration-settings","link":"#global-configuration-settings","children":[]},{"level":3,"title":"Language Specific Configuration Settings","slug":"language-specific-configuration-settings","link":"#language-specific-configuration-settings","children":[]},{"level":3,"title":"Customizing Language Recognition","slug":"customizing-language-recognition","link":"#customizing-language-recognition","children":[]},{"level":3,"title":"Controlling Where Customization is Stored to Simplify Your Workflow","slug":"controlling-where-customization-is-stored-to-simplify-your-workflow","link":"#controlling-where-customization-is-stored-to-simplify-your-workflow","children":[]}]},{"level":2,"title":"Summary","slug":"summary","link":"#summary","children":[]}],"git":{"updatedTime":1694141555000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":3}]},"readingTime":{"minutes":0.35,"words":104},"filePathRelative":"docs/launch-manual/sections/using-pulsar/index.md"}');export{e as data}; diff --git a/assets/index.html.c5e5eb68.js b/assets/index.html.c5e5eb68.js new file mode 100644 index 0000000000..960bc63812 --- /dev/null +++ b/assets/index.html.c5e5eb68.js @@ -0,0 +1 @@ +import{_ as a,o as e,c as o,f as s}from"./app.0e1565ce.js";const r={},i=s('

    A collection of miscellaneous resources around Pulsar and the project.

    Glossary

    A list of terms and their meanings used by the application and community.

    Pulsar API

    The Pulsar API reference.

    Project tooling

    A list of tools and services used by the Pulsar team and information about them.

    ',7),t=[i];function n(l,c){return e(),o("div",null,t)}const h=a(r,[["render",n],["__file","index.html.vue"]]);export{h as default}; diff --git a/assets/index.html.d36c0486.js b/assets/index.html.d36c0486.js new file mode 100644 index 0000000000..aee64951f7 --- /dev/null +++ b/assets/index.html.d36c0486.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-6c28ad56","path":"/tag/release/","title":"release Tag","lang":"en-US","frontmatter":{"title":"release Tag","blog":{"type":"category","name":"release","key":"tag"},"layout":"Blog"},"excerpt":"","headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null}');export{e as data}; diff --git a/assets/index.html.d51edc97.js b/assets/index.html.d51edc97.js new file mode 100644 index 0000000000..4a1b99f402 --- /dev/null +++ b/assets/index.html.d51edc97.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-4368211c","path":"/docs/atom-archive/atom-server-side-apis/","title":"Appendix E : Atom server-side APIs","lang":"en-US","frontmatter":{"title":"Appendix E : Atom server-side APIs","sitemap":{"priority":0.1}},"excerpt":"","headers":[{"level":2,"title":"Atom server-side APIs","slug":"atom-server-side-apis","link":"#atom-server-side-apis","children":[{"level":3,"title":"Atom package server API","slug":"atom-package-server-api","link":"#atom-package-server-api","children":[]},{"level":3,"title":"Atom update server API","slug":"atom-update-server-api","link":"#atom-update-server-api","children":[]}]}],"git":{"updatedTime":1669438016000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":5},{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1},{"name":"confused-Techie","email":"dev@lhbasics.com","commits":1}]},"readingTime":{"minutes":0.34,"words":102},"filePathRelative":"docs/atom-archive/atom-server-side-apis/index.md"}');export{e as data}; diff --git a/assets/index.html.d872f358.js b/assets/index.html.d872f358.js new file mode 100644 index 0000000000..f3635b121e --- /dev/null +++ b/assets/index.html.d872f358.js @@ -0,0 +1 @@ +const e=JSON.parse(`{"key":"v-278e8fc5","path":"/docs/launch-manual/sections/core-hacking/","title":"Hacking the Core","lang":"en-US","frontmatter":{"lang":"en-US","title":"Hacking the Core","description":"Info on building from source + hacking on Pulsar's core"},"excerpt":"","headers":[{"level":2,"title":"Building Pulsar","slug":"building-pulsar","link":"#building-pulsar","children":[{"level":3,"title":"Requirements and dependencies","slug":"requirements-and-dependencies","link":"#requirements-and-dependencies","children":[]},{"level":3,"title":"Building and running the application","slug":"building-and-running-the-application","link":"#building-and-running-the-application","children":[]},{"level":3,"title":"Building binaries","slug":"building-binaries","link":"#building-binaries","children":[]}]},{"level":2,"title":"Using ppm (Pulsar Package Manager)","slug":"using-ppm-pulsar-package-manager","link":"#using-ppm-pulsar-package-manager","children":[]},{"level":2,"title":"Hacking on the Core","slug":"hacking-on-the-core","link":"#hacking-on-the-core","children":[{"level":3,"title":"Running in Development Mode","slug":"running-in-development-mode","link":"#running-in-development-mode","children":[]},{"level":3,"title":"Running Pulsar Core Tests Locally","slug":"running-pulsar-core-tests-locally","link":"#running-pulsar-core-tests-locally","children":[]}]},{"level":2,"title":"Tools of the Trade","slug":"tools-of-the-trade","link":"#tools-of-the-trade","children":[]},{"level":2,"title":"The Init File","slug":"the-init-file","link":"#the-init-file","children":[]},{"level":2,"title":"Package: Word Count","slug":"package-word-count","link":"#package-word-count","children":[{"level":3,"title":"Package Generator","slug":"package-generator","link":"#package-generator","children":[]},{"level":3,"title":"Developing Our Package","slug":"developing-our-package","link":"#developing-our-package","children":[]},{"level":3,"title":"Basic Debugging","slug":"basic-debugging","link":"#basic-debugging","children":[]},{"level":3,"title":"Testing","slug":"testing","link":"#testing","children":[]},{"level":3,"title":"Summary","slug":"summary","link":"#summary","children":[]}]},{"level":2,"title":"Package: Modifying Text","slug":"package-modifying-text","link":"#package-modifying-text","children":[{"level":3,"title":"Basic Text Insertion","slug":"basic-text-insertion","link":"#basic-text-insertion","children":[]},{"level":3,"title":"Add the ASCII Art","slug":"add-the-ascii-art","link":"#add-the-ascii-art","children":[]},{"level":3,"title":"Summary","slug":"summary-1","link":"#summary-1","children":[]}]},{"level":2,"title":"Package: Active Editor Info","slug":"package-active-editor-info","link":"#package-active-editor-info","children":[{"level":3,"title":"Create the Package","slug":"create-the-package","link":"#create-the-package","children":[]},{"level":3,"title":"Add an Opener","slug":"add-an-opener","link":"#add-an-opener","children":[]},{"level":3,"title":"Updating the View","slug":"updating-the-view","link":"#updating-the-view","children":[]},{"level":3,"title":"Constraining Our Item's Locations","slug":"constraining-our-item-s-locations","link":"#constraining-our-item-s-locations","children":[]},{"level":3,"title":"Show Active Editor Info","slug":"show-active-editor-info","link":"#show-active-editor-info","children":[]},{"level":3,"title":"Serialization","slug":"serialization","link":"#serialization","children":[]},{"level":3,"title":"Summary","slug":"summary-2","link":"#summary-2","children":[]}]},{"level":2,"title":"Creating a Theme","slug":"creating-a-theme","link":"#creating-a-theme","children":[{"level":3,"title":"Getting Started","slug":"getting-started","link":"#getting-started","children":[]},{"level":3,"title":"Creating a Syntax Theme","slug":"creating-a-syntax-theme","link":"#creating-a-syntax-theme","children":[]},{"level":3,"title":"Creating a UI Theme","slug":"creating-a-ui-theme","link":"#creating-a-ui-theme","children":[]},{"level":3,"title":"Theme Variables","slug":"theme-variables","link":"#theme-variables","children":[]},{"level":3,"title":"Development workflow","slug":"development-workflow","link":"#development-workflow","children":[]},{"level":3,"title":"Publish your theme","slug":"publish-your-theme","link":"#publish-your-theme","children":[]}]},{"level":2,"title":"Creating a Grammar","slug":"creating-a-grammar","link":"#creating-a-grammar","children":[{"level":3,"title":"Getting Started","slug":"getting-started-1","link":"#getting-started-1","children":[]},{"level":3,"title":"The Parser","slug":"the-parser","link":"#the-parser","children":[]},{"level":3,"title":"The Package","slug":"the-package","link":"#the-package","children":[]},{"level":3,"title":"The Grammar File","slug":"the-grammar-file","link":"#the-grammar-file","children":[]},{"level":3,"title":"Basic Fields","slug":"basic-fields","link":"#basic-fields","children":[]},{"level":3,"title":"Language Recognition","slug":"language-recognition","link":"#language-recognition","children":[]},{"level":3,"title":"Syntax Highlighting","slug":"syntax-highlighting","link":"#syntax-highlighting","children":[]},{"level":3,"title":"Language Injection","slug":"language-injection","link":"#language-injection","children":[]},{"level":3,"title":"Code Folding","slug":"code-folding","link":"#code-folding","children":[]},{"level":3,"title":"Comments","slug":"comments","link":"#comments","children":[]},{"level":3,"title":"Example Packages","slug":"example-packages","link":"#example-packages","children":[]}]},{"level":2,"title":"Creating a Legacy TextMate Grammar","slug":"creating-a-legacy-textmate-grammar","link":"#creating-a-legacy-textmate-grammar","children":[{"level":3,"title":"Getting Started","slug":"getting-started-2","link":"#getting-started-2","children":[]},{"level":3,"title":"Create the Package","slug":"create-the-package-1","link":"#create-the-package-1","children":[]},{"level":3,"title":"Adding Patterns","slug":"adding-patterns","link":"#adding-patterns","children":[]},{"level":3,"title":"Begin/End Patterns","slug":"begin-end-patterns","link":"#begin-end-patterns","children":[]},{"level":3,"title":"Repositories and the Include keyword, or how to avoid duplication","slug":"repositories-and-the-include-keyword-or-how-to-avoid-duplication","link":"#repositories-and-the-include-keyword-or-how-to-avoid-duplication","children":[]},{"level":3,"title":"Where to Go from Here","slug":"where-to-go-from-here","link":"#where-to-go-from-here","children":[]}]},{"level":2,"title":"Converting from TextMate","slug":"converting-from-textmate","link":"#converting-from-textmate","children":[{"level":3,"title":"Converting a TextMate Grammar Bundle","slug":"converting-a-textmate-grammar-bundle","link":"#converting-a-textmate-grammar-bundle","children":[]},{"level":3,"title":"Converting a TextMate Syntax Theme","slug":"converting-a-textmate-syntax-theme","link":"#converting-a-textmate-syntax-theme","children":[]}]},{"level":2,"title":"Publishing","slug":"publishing","link":"#publishing","children":[{"level":3,"title":"Prepare Your Package","slug":"prepare-your-package","link":"#prepare-your-package","children":[]},{"level":3,"title":"Publish Your Package","slug":"publish-your-package","link":"#publish-your-package","children":[]}]},{"level":2,"title":"Iconography","slug":"iconography","link":"#iconography","children":[{"level":3,"title":"Overview","slug":"overview","link":"#overview","children":[]},{"level":3,"title":"Usage","slug":"usage","link":"#usage","children":[]},{"level":3,"title":"Size","slug":"size","link":"#size","children":[]},{"level":3,"title":"Usability","slug":"usability","link":"#usability","children":[]}]},{"level":2,"title":"Debugging","slug":"debugging","link":"#debugging","children":[{"level":3,"title":"Update to the Latest Version","slug":"update-to-the-latest-version","link":"#update-to-the-latest-version","children":[]},{"level":3,"title":"Using Safe Mode","slug":"using-safe-mode","link":"#using-safe-mode","children":[]},{"level":3,"title":"Clearing Saved State","slug":"clearing-saved-state","link":"#clearing-saved-state","children":[]},{"level":3,"title":"Reset to Factory Defaults","slug":"reset-to-factory-defaults","link":"#reset-to-factory-defaults","children":[]},{"level":3,"title":"Check for Linked Packages","slug":"check-for-linked-packages","link":"#check-for-linked-packages","children":[]},{"level":3,"title":"Check for Incompatible Packages","slug":"check-for-incompatible-packages","link":"#check-for-incompatible-packages","children":[]},{"level":3,"title":"Check Pulsar and Package Settings","slug":"check-pulsar-and-package-settings","link":"#check-pulsar-and-package-settings","children":[]},{"level":3,"title":"Check Your Configuration","slug":"check-your-configuration","link":"#check-your-configuration","children":[]},{"level":3,"title":"Check Your Keybindings","slug":"check-your-keybindings","link":"#check-your-keybindings","children":[]},{"level":3,"title":"Check Font Rendering Issues","slug":"check-font-rendering-issues","link":"#check-font-rendering-issues","children":[]},{"level":3,"title":"Check for Errors in the Developer Tools","slug":"check-for-errors-in-the-developer-tools","link":"#check-for-errors-in-the-developer-tools","children":[]},{"level":3,"title":"Find Crash Logs","slug":"find-crash-logs","link":"#find-crash-logs","children":[]},{"level":3,"title":"Diagnose Startup Performance","slug":"diagnose-startup-performance","link":"#diagnose-startup-performance","children":[]},{"level":3,"title":"Diagnose Runtime Performance","slug":"diagnose-runtime-performance","link":"#diagnose-runtime-performance","children":[]},{"level":3,"title":"Profiling Startup Performance","slug":"profiling-startup-performance","link":"#profiling-startup-performance","children":[]},{"level":3,"title":"Check Your Build Tools","slug":"check-your-build-tools","link":"#check-your-build-tools","children":[]},{"level":3,"title":"Check if your GPU is causing the problem","slug":"check-if-your-gpu-is-causing-the-problem","link":"#check-if-your-gpu-is-causing-the-problem","children":[]}]},{"level":2,"title":"Writing Specs","slug":"writing-specs","link":"#writing-specs","children":[{"level":3,"title":"Create a New Spec","slug":"create-a-new-spec","link":"#create-a-new-spec","children":[]},{"level":3,"title":"Asynchronous Specs","slug":"asynchronous-specs","link":"#asynchronous-specs","children":[]},{"level":3,"title":"Running Specs","slug":"running-specs","link":"#running-specs","children":[]},{"level":3,"title":"Customizing your test runner","slug":"customizing-your-test-runner","link":"#customizing-your-test-runner","children":[]}]},{"level":2,"title":"Handling URIs","slug":"handling-uris","link":"#handling-uris","children":[{"level":3,"title":"Modifying your package.json","slug":"modifying-your-package-json","link":"#modifying-your-package-json","children":[]},{"level":3,"title":"Modifying your Main Module","slug":"modifying-your-main-module","link":"#modifying-your-main-module","children":[]},{"level":3,"title":"Controlling Activation Deferral","slug":"controlling-activation-deferral","link":"#controlling-activation-deferral","children":[]},{"level":3,"title":"Linux Support","slug":"linux-support","link":"#linux-support","children":[]}]},{"level":2,"title":"Core URIs","slug":"core-uris","link":"#core-uris","children":[{"level":3,"title":"Cross-Platform Compatibility","slug":"cross-platform-compatibility","link":"#cross-platform-compatibility","children":[]}]},{"level":2,"title":"Contributing to Official Pulsar Packages","slug":"contributing-to-official-pulsar-packages","link":"#contributing-to-official-pulsar-packages","children":[{"level":3,"title":"Hacking on Packages","slug":"hacking-on-packages","link":"#hacking-on-packages","children":[]}]},{"level":2,"title":"Creating a Fork of a Core Package","slug":"creating-a-fork-of-a-core-package","link":"#creating-a-fork-of-a-core-package","children":[{"level":3,"title":"Creating Your New Package","slug":"creating-your-new-package","link":"#creating-your-new-package","children":[]},{"level":3,"title":"Merging Upstream Changes into Your Package","slug":"merging-upstream-changes-into-your-package","link":"#merging-upstream-changes-into-your-package","children":[]}]},{"level":2,"title":"Maintaining a Fork of a Core Package","slug":"maintaining-a-fork-of-a-core-package","link":"#maintaining-a-fork-of-a-core-package","children":[{"level":3,"title":"Step-by-step guide","slug":"step-by-step-guide","link":"#step-by-step-guide","children":[]}]},{"level":2,"title":"Summary","slug":"summary-3","link":"#summary-3","children":[]},{"level":2,"title":"Having trouble?","slug":"having-trouble","link":"#having-trouble","children":[]}],"git":{"updatedTime":1694141555000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":4},{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":1.73,"words":520},"filePathRelative":"docs/launch-manual/sections/core-hacking/index.md"}`);export{e as data}; diff --git a/assets/index.html.d8f16f60.js b/assets/index.html.d8f16f60.js new file mode 100644 index 0000000000..ab7749c9c1 --- /dev/null +++ b/assets/index.html.d8f16f60.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-9c858a88","path":"/tag/feedback/","title":"feedback Tag","lang":"en-US","frontmatter":{"title":"feedback Tag","blog":{"type":"category","name":"feedback","key":"tag"},"layout":"Blog"},"excerpt":"","headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null}');export{e as data}; diff --git a/assets/index.html.d9369373.js b/assets/index.html.d9369373.js new file mode 100644 index 0000000000..7c4b92a4d3 --- /dev/null +++ b/assets/index.html.d9369373.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-181c8802","path":"/tag/electron/","title":"electron Tag","lang":"en-US","frontmatter":{"title":"electron Tag","blog":{"type":"category","name":"electron","key":"tag"},"layout":"Blog"},"excerpt":"","headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null}');export{e as data}; diff --git a/assets/index.html.db4b9cf5.js b/assets/index.html.db4b9cf5.js new file mode 100644 index 0000000000..3e7a25d31d --- /dev/null +++ b/assets/index.html.db4b9cf5.js @@ -0,0 +1 @@ +const i=JSON.parse('{"key":"v-a73e70e8","path":"/docs/resources/privacy/","title":"Privacy Policy","lang":"en-US","frontmatter":{"title":"Privacy Policy"},"excerpt":"","headers":[{"level":2,"title":"Pulsar Backend (https://api.pulsar-edit.dev)","slug":"pulsar-backend-https-api-pulsar-edit-dev","link":"#pulsar-backend-https-api-pulsar-edit-dev","children":[{"level":3,"title":"What Information is Collected","slug":"what-information-is-collected","link":"#what-information-is-collected","children":[]},{"level":3,"title":"How is this Information Collected","slug":"how-is-this-information-collected","link":"#how-is-this-information-collected","children":[]},{"level":3,"title":"What is Done with this Information","slug":"what-is-done-with-this-information","link":"#what-is-done-with-this-information","children":[]},{"level":3,"title":"Who has Access to this Information","slug":"who-has-access-to-this-information","link":"#who-has-access-to-this-information","children":[]},{"level":3,"title":"How Long is this information Kept","slug":"how-long-is-this-information-kept","link":"#how-long-is-this-information-kept","children":[]},{"level":3,"title":"TLDR","slug":"tldr","link":"#tldr","children":[]}]},{"level":2,"title":"Pulsar User Account","slug":"pulsar-user-account","link":"#pulsar-user-account","children":[{"level":3,"title":"What Information is Collected","slug":"what-information-is-collected-1","link":"#what-information-is-collected-1","children":[]},{"level":3,"title":"How is this Information Collected","slug":"how-is-this-information-collected-1","link":"#how-is-this-information-collected-1","children":[]},{"level":3,"title":"What is Done with this Information","slug":"what-is-done-with-this-information-1","link":"#what-is-done-with-this-information-1","children":[]},{"level":3,"title":"Who has Access to this Information","slug":"who-has-access-to-this-information-1","link":"#who-has-access-to-this-information-1","children":[]},{"level":3,"title":"How Long is this Information Kept","slug":"how-long-is-this-information-kept-1","link":"#how-long-is-this-information-kept-1","children":[]},{"level":3,"title":"How do I have this Information Deleted","slug":"how-do-i-have-this-information-deleted","link":"#how-do-i-have-this-information-deleted","children":[]},{"level":3,"title":"TLDR","slug":"tldr-1","link":"#tldr-1","children":[]}]},{"level":2,"title":"Pulsar Website (https://pulsar-edit.dev)","slug":"pulsar-website-https-pulsar-edit-dev","link":"#pulsar-website-https-pulsar-edit-dev","children":[{"level":3,"title":"TLDR","slug":"tldr-2","link":"#tldr-2","children":[]}]},{"level":2,"title":"Pulsar Editor","slug":"pulsar-editor","link":"#pulsar-editor","children":[{"level":3,"title":"TLDR","slug":"tldr-3","link":"#tldr-3","children":[]}]}],"git":{"updatedTime":1671043464000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":2}]},"readingTime":{"minutes":0.08,"words":25},"filePathRelative":"docs/resources/privacy/index.md"}');export{i as data}; diff --git a/assets/index.html.dcdc22a2.js b/assets/index.html.dcdc22a2.js new file mode 100644 index 0000000000..7363ef7d0c --- /dev/null +++ b/assets/index.html.dcdc22a2.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-01560935","path":"/timeline/","title":"Timeline","lang":"en-US","frontmatter":{"title":"Timeline","blog":{"type":"type","key":"timeline"},"layout":"Blog"},"excerpt":"","headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null}');export{e as data}; diff --git a/assets/index.html.de5ae0cd.js b/assets/index.html.de5ae0cd.js new file mode 100644 index 0000000000..fe016d4581 --- /dev/null +++ b/assets/index.html.de5ae0cd.js @@ -0,0 +1 @@ +import{_ as e,o as c,c as t}from"./app.0e1565ce.js";const n={};function _(o,r){return c(),t("div")}const a=e(n,[["render",_],["__file","index.html.vue"]]);export{a as default}; diff --git a/assets/index.html.e11796f0.js b/assets/index.html.e11796f0.js new file mode 100644 index 0000000000..e8615ec09b --- /dev/null +++ b/assets/index.html.e11796f0.js @@ -0,0 +1 @@ +import{_ as r,o as n,c as l,a as t,b as e,d as a,r as s}from"./app.0e1565ce.js";const c={},i=t("h1",{id:"autocomplete-plus",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#autocomplete-plus","aria-hidden":"true"},"#"),e(" Autocomplete-Plus")],-1),u=t("p",null,[e("Welcome to the "),t("code",null,"autocomplete-plus"),e(" wiki!")],-1),p={href:"https://github.com/atom/autocomplete-plus/wiki",target:"_blank",rel:"noopener noreferrer"},h={href:"https://github.com/pulsar-edit/autocomplete-plus",target:"_blank",rel:"noopener noreferrer"};function m(_,d){const o=s("ExternalLinkIcon");return n(),l("div",null,[i,u,t("p",null,[e("This wiki has been nearly directly taken from the taken from the "),t("a",p,[e("upstream Atom Wiki"),a(o)]),e(". That may mean that parts of it are out of date, but otherwise hopefully is a helpful resource relating to our "),t("a",h,[e("Autocomplete-Plus Repo"),a(o)]),e(".")])])}const k=r(c,[["render",m],["__file","index.html.vue"]]);export{k as default}; diff --git a/assets/index.html.e54b2982.js b/assets/index.html.e54b2982.js new file mode 100644 index 0000000000..2cb1c4e503 --- /dev/null +++ b/assets/index.html.e54b2982.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-ed2312e4","path":"/docs/atom-archive/","title":"Atom Documentation Archive","lang":"en-us","frontmatter":{"lang":"en-us","title":"Atom Documentation Archive","description":"Archived Atom Documentation in its original form","sitemap":{"priority":0.2}},"excerpt":"","headers":[{"level":2,"title":"Chapters","slug":"chapters","link":"#chapters","children":[]}],"git":{"updatedTime":1669438016000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":4},{"name":"confused-Techie","email":"dev@lhbasics.com","commits":2},{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1},{"name":"Joshua MIller","email":"16845458+kaosine@users.noreply.github.com","commits":1},{"name":"softcode","email":"108250871+softcode589@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":0.7,"words":210},"filePathRelative":"docs/atom-archive/index.md"}');export{e as data}; diff --git a/assets/index.html.e66450b6.js b/assets/index.html.e66450b6.js new file mode 100644 index 0000000000..e5da6b0d1d --- /dev/null +++ b/assets/index.html.e66450b6.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-ad7b6e3a","path":"/docs/atom-archive/api/","title":"Reference : API","lang":"en-US","frontmatter":{"title":"Reference : API","sitemap":{"priority":0.1}},"excerpt":"","headers":[],"git":{"updatedTime":1669438016000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":1},{"name":"confused-Techie","email":"dev@lhbasics.com","commits":1}]},"readingTime":{"minutes":0.02,"words":6},"filePathRelative":"docs/atom-archive/api/index.md"}');export{e as data}; diff --git a/assets/index.html.e66a9e6f.js b/assets/index.html.e66a9e6f.js new file mode 100644 index 0000000000..1fdad0b8b5 --- /dev/null +++ b/assets/index.html.e66a9e6f.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-744d024e","path":"/tag/","title":"Tag","lang":"en-US","frontmatter":{"title":"Tag","blog":{"type":"category","key":"tag"},"layout":"Blog"},"excerpt":"","headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null}');export{e as data}; diff --git a/assets/index.html.e9497bdd.js b/assets/index.html.e9497bdd.js new file mode 100644 index 0000000000..fe016d4581 --- /dev/null +++ b/assets/index.html.e9497bdd.js @@ -0,0 +1 @@ +import{_ as e,o as c,c as t}from"./app.0e1565ce.js";const n={};function _(o,r){return c(),t("div")}const a=e(n,[["render",_],["__file","index.html.vue"]]);export{a as default}; diff --git a/assets/index.html.ebeb7c37.js b/assets/index.html.ebeb7c37.js new file mode 100644 index 0000000000..05001df7ec --- /dev/null +++ b/assets/index.html.ebeb7c37.js @@ -0,0 +1 @@ +const e=JSON.parse(`{"key":"v-ad775132","path":"/docs/atom-archive/faq/","title":"Appendix B : FAQ","lang":"en-US","frontmatter":{"title":"Appendix B : FAQ","sitemap":{"priority":0.1}},"excerpt":"","headers":[{"level":2,"title":"FAQ","slug":"faq","link":"#faq","children":[{"level":3,"title":"Is Atom open source?","slug":"is-atom-open-source","link":"#is-atom-open-source","children":[]},{"level":3,"title":"What does Atom cost?","slug":"what-does-atom-cost","link":"#what-does-atom-cost","children":[]},{"level":3,"title":"What platforms does Atom run on?","slug":"what-platforms-does-atom-run-on","link":"#what-platforms-does-atom-run-on","children":[]},{"level":3,"title":"How can I contribute to Atom?","slug":"how-can-i-contribute-to-atom","link":"#how-can-i-contribute-to-atom","children":[]},{"level":3,"title":"Why does Atom collect usage data?","slug":"why-does-atom-collect-usage-data","link":"#why-does-atom-collect-usage-data","children":[]},{"level":3,"title":"Atom in the cloud?","slug":"atom-in-the-cloud","link":"#atom-in-the-cloud","children":[]},{"level":3,"title":"What's the difference between an IDE and an editor?","slug":"what-s-the-difference-between-an-ide-and-an-editor","link":"#what-s-the-difference-between-an-ide-and-an-editor","children":[]},{"level":3,"title":"How can I tell if subpixel antialiasing is working?","slug":"how-can-i-tell-if-subpixel-antialiasing-is-working","link":"#how-can-i-tell-if-subpixel-antialiasing-is-working","children":[]},{"level":3,"title":"Why is Atom deleting trailing whitespace? Why is there a newline at the end of the file?","slug":"why-is-atom-deleting-trailing-whitespace-why-is-there-a-newline-at-the-end-of-the-file","link":"#why-is-atom-deleting-trailing-whitespace-why-is-there-a-newline-at-the-end-of-the-file","children":[]},{"level":3,"title":"What does Safe Mode do?","slug":"what-does-safe-mode-do","link":"#what-does-safe-mode-do","children":[]},{"level":3,"title":"I have a question about a specific Atom community package. Where is the best place to ask it?","slug":"i-have-a-question-about-a-specific-atom-community-package-where-is-the-best-place-to-ask-it","link":"#i-have-a-question-about-a-specific-atom-community-package-where-is-the-best-place-to-ask-it","children":[]},{"level":3,"title":"I\u2019m using an international keyboard and keys that use AltGr or Ctrl+Alt aren\u2019t working","slug":"i-m-using-an-international-keyboard-and-keys-that-use-altgr-or-ctrl-alt-aren-t-working","link":"#i-m-using-an-international-keyboard-and-keys-that-use-altgr-or-ctrl-alt-aren-t-working","children":[]},{"level":3,"title":"I\u2019m having a problem with Julia! What do I do?","slug":"i-m-having-a-problem-with-julia-what-do-i-do","link":"#i-m-having-a-problem-with-julia-what-do-i-do","children":[]},{"level":3,"title":"I\u2019m getting an error about a \u201Cself-signed certificate\u201D. What do I do?","slug":"i-m-getting-an-error-about-a-self-signed-certificate-what-do-i-do","link":"#i-m-getting-an-error-about-a-self-signed-certificate-what-do-i-do","children":[]},{"level":3,"title":"I\u2019m having a problem with PlatformIO! What do I do?","slug":"i-m-having-a-problem-with-platformio-what-do-i-do","link":"#i-m-having-a-problem-with-platformio-what-do-i-do","children":[]},{"level":3,"title":"How do I make Atom recognize a file with extension X as language Y?","slug":"how-do-i-make-atom-recognize-a-file-with-extension-x-as-language-y","link":"#how-do-i-make-atom-recognize-a-file-with-extension-x-as-language-y","children":[]},{"level":3,"title":"How do I make the Welcome screen stop showing up?","slug":"how-do-i-make-the-welcome-screen-stop-showing-up","link":"#how-do-i-make-the-welcome-screen-stop-showing-up","children":[]},{"level":3,"title":"How do I preview web page changes automatically?","slug":"how-do-i-preview-web-page-changes-automatically","link":"#how-do-i-preview-web-page-changes-automatically","children":[]},{"level":3,"title":"How do I accept input from my program or script when using the script package?","slug":"how-do-i-accept-input-from-my-program-or-script-when-using-the-script-package","link":"#how-do-i-accept-input-from-my-program-or-script-when-using-the-script-package","children":[]},{"level":3,"title":"I am unable to update to the latest version of Atom on macOS. How do I fix this?","slug":"i-am-unable-to-update-to-the-latest-version-of-atom-on-macos-how-do-i-fix-this","link":"#i-am-unable-to-update-to-the-latest-version-of-atom-on-macos-how-do-i-fix-this","children":[]},{"level":3,"title":"I\u2019m trying to change my syntax colors from styles.less, but it isn\u2019t working!","slug":"i-m-trying-to-change-my-syntax-colors-from-styles-less-but-it-isn-t-working","link":"#i-m-trying-to-change-my-syntax-colors-from-styles-less-but-it-isn-t-working","children":[]},{"level":3,"title":"How do I build or execute code I've written in Atom?","slug":"how-do-i-build-or-execute-code-i-ve-written-in-atom","link":"#how-do-i-build-or-execute-code-i-ve-written-in-atom","children":[]},{"level":3,"title":"How do I uninstall Atom on macOS?","slug":"how-do-i-uninstall-atom-on-macos","link":"#how-do-i-uninstall-atom-on-macos","children":[]},{"level":3,"title":"MacOS Mojave font rendering change","slug":"macos-mojave-font-rendering-change","link":"#macos-mojave-font-rendering-change","children":[]},{"level":3,"title":"Why does macOS say that Atom wants to access my calendar, contacts, photos, etc.?","slug":"why-does-macos-say-that-atom-wants-to-access-my-calendar-contacts-photos-etc","link":"#why-does-macos-say-that-atom-wants-to-access-my-calendar-contacts-photos-etc","children":[]},{"level":3,"title":"How do I turn on line wrap?","slug":"how-do-i-turn-on-line-wrap","link":"#how-do-i-turn-on-line-wrap","children":[]},{"level":3,"title":"The menu bar disappeared, how do I get it back?","slug":"the-menu-bar-disappeared-how-do-i-get-it-back","link":"#the-menu-bar-disappeared-how-do-i-get-it-back","children":[]},{"level":3,"title":"How do I use a newline in the result of find and replace?","slug":"how-do-i-use-a-newline-in-the-result-of-find-and-replace","link":"#how-do-i-use-a-newline-in-the-result-of-find-and-replace","children":[]},{"level":3,"title":"What is this line on the right in the editor view?","slug":"what-is-this-line-on-the-right-in-the-editor-view","link":"#what-is-this-line-on-the-right-in-the-editor-view","children":[]}]}],"git":{"updatedTime":1669438016000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":3},{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1},{"name":"confused-Techie","email":"dev@lhbasics.com","commits":1}]},"readingTime":{"minutes":1.5,"words":449},"filePathRelative":"docs/atom-archive/faq/index.md"}`);export{e as data}; diff --git a/assets/index.html.f1050665.js b/assets/index.html.f1050665.js new file mode 100644 index 0000000000..a8344cc5fa --- /dev/null +++ b/assets/index.html.f1050665.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-04523903","path":"/docs/packages/core/github/","title":"GitHub","lang":"en-us","frontmatter":{"lang":"en-us","title":"GitHub"},"excerpt":"","headers":[{"level":2,"title":"Roadmaps","slug":"roadmaps","link":"#roadmaps","children":[]},{"level":2,"title":"Monthly Planning","slug":"monthly-planning","link":"#monthly-planning","children":[]}],"git":{"updatedTime":1664411672000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":4},{"name":"Joshua MIller","email":"16845458+kaosine@users.noreply.github.com","commits":2}]},"readingTime":{"minutes":0.34,"words":101},"filePathRelative":"docs/packages/core/github/index.md"}');export{e as data}; diff --git a/assets/index.html.f2a9140b.js b/assets/index.html.f2a9140b.js new file mode 100644 index 0000000000..f02d9a5c91 --- /dev/null +++ b/assets/index.html.f2a9140b.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-625b0d60","path":"/docs/resources/conduct/","title":"Code of Conduct","lang":"en-US","frontmatter":{"title":"Code of Conduct"},"excerpt":"","headers":[{"level":2,"title":"Our Pledge","slug":"our-pledge","link":"#our-pledge","children":[]},{"level":2,"title":"Our Standards","slug":"our-standards","link":"#our-standards","children":[]},{"level":2,"title":"Our Responsibilities","slug":"our-responsibilities","link":"#our-responsibilities","children":[]},{"level":2,"title":"Scope","slug":"scope","link":"#scope","children":[]},{"level":2,"title":"Enforcement","slug":"enforcement","link":"#enforcement","children":[]},{"level":2,"title":"Attribution","slug":"attribution","link":"#attribution","children":[]}],"git":{"updatedTime":1669429078000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":0.09,"words":26},"filePathRelative":"docs/resources/conduct/index.md"}');export{e as data}; diff --git a/assets/index.html.f2ffe19c.js b/assets/index.html.f2ffe19c.js new file mode 100644 index 0000000000..2a18e22780 --- /dev/null +++ b/assets/index.html.f2ffe19c.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-6566cb89","path":"/docs/packages/core/","title":"Core Packages Documentation","lang":"en-us","frontmatter":{"lang":"en-us","title":"Core Packages Documentation","description":"Wiki Resource for many First Party Packages"},"excerpt":"","headers":[{"level":2,"title":"Archived Wikis","slug":"archived-wikis","link":"#archived-wikis","children":[]}],"git":{"updatedTime":1663961800000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":2}]},"readingTime":{"minutes":0.46,"words":139},"filePathRelative":"docs/packages/core/index.md"}');export{e as data}; diff --git a/assets/index.html.f452234d.js b/assets/index.html.f452234d.js new file mode 100644 index 0000000000..4b38958a89 --- /dev/null +++ b/assets/index.html.f452234d.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-6df8120e","path":"/docs/packages/core/autocomplete-plus/","title":"Autocomplete-Plus","lang":"en-us","frontmatter":{"lang":"en-us","title":"Autocomplete-Plus"},"excerpt":"","headers":[],"git":{"updatedTime":1664399092000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":2}]},"readingTime":{"minutes":0.22,"words":67},"filePathRelative":"docs/packages/core/autocomplete-plus/index.md"}');export{e as data}; diff --git a/assets/index.html.f7363831.js b/assets/index.html.f7363831.js new file mode 100644 index 0000000000..df99f5c2cd --- /dev/null +++ b/assets/index.html.f7363831.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-923287ec","path":"/docs/resources/tooling/","title":"Tooling","lang":"en-US","frontmatter":{"title":"Tooling"},"excerpt":"","headers":[{"level":2,"title":"Continuous Integration","slug":"continuous-integration","link":"#continuous-integration","children":[{"level":3,"title":"Cirrus CI","slug":"cirrus-ci","link":"#cirrus-ci","children":[]},{"level":3,"title":"Codacy","slug":"codacy","link":"#codacy","children":[]}]},{"level":2,"title":"i18n (Internationalization)","slug":"i18n-internationalization","link":"#i18n-internationalization","children":[{"level":3,"title":"Crowdin","slug":"crowdin","link":"#crowdin","children":[]}]},{"level":2,"title":"Package Managers","slug":"package-managers","link":"#package-managers","children":[]},{"level":2,"title":"Cloud Database","slug":"cloud-database","link":"#cloud-database","children":[]},{"level":2,"title":"Cloud Compute","slug":"cloud-compute","link":"#cloud-compute","children":[]},{"level":2,"title":"Additional Testing tools","slug":"additional-testing-tools","link":"#additional-testing-tools","children":[{"level":3,"title":"Action Pulsar Dependency Tester","slug":"action-pulsar-dependency-tester","link":"#action-pulsar-dependency-tester","children":[]}]}],"git":{"updatedTime":1667932127000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":0.08,"words":24},"filePathRelative":"docs/resources/tooling/index.md"}');export{e as data}; diff --git a/assets/index.html.f7de1c88.js b/assets/index.html.f7de1c88.js new file mode 100644 index 0000000000..7aa3b14197 --- /dev/null +++ b/assets/index.html.f7de1c88.js @@ -0,0 +1,188 @@ +import{_ as h,a as u,b as m,c as f}from"./unity-theme.516a5ae9.js";import{_ as g,a as b}from"./symbol.b88bf5a9.js";import{_ as k,a as y,b as v}from"./encodings.f93acf64.js";import{_ as w,a as _}from"./find-replace-project.ada882a7.js";import{_ as x,a as C}from"./snippet-scope.1f381b52.js";import{_ as A}from"./autocomplete.b0bae406.js";import{_ as S}from"./folding.6d09f8df.js";import{_ as q}from"./panes.a7cec271.js";import{_ as T}from"./allow-pending-pane-items.0fe3dd4a.js";import{_ as I}from"./grammar.c16b8cd6.js";import{_ as P,a as F,b as R,c as L,d as G,e as H}from"./open-on-github.6fa9b166.js";import{_ as O,a as M,b as j,c as z,d as E,e as D,f as Y,g as $,h as W,i as U,j as B,k as N,l as V,m as J,n as K,o as Z,p as Q,q as X,r as ee,s as te,t as ae,u as oe,v as ne,w as se,x as ie,y as le}from"./github-review-reply.6d405e4b.js";import{_ as re,a as ce}from"./preview.2eaf146a.js";import{_ as de}from"./menubar.df752f94.js";import{_ as pe,a as he,b as ue,c as me,d as fe}from"./portable-mode-folder.90669a9a.js";import{_ as ge,o as be,c as ke,a as e,b as t,d as a,w as s,f as n,r as p}from"./app.0e1565ce.js";const ye={},ve=n('

    STOP

    This is being kept for archival purposes only from the original Atom documentation. As this may no longer be relevant to Pulsar, you use this at your own risk. Current Pulsar documentation for this section is found at the documentation home.

    Using Atom

    Now that we've covered the very basics of Atom, we are ready to see how to really start getting the most out of using it. In this chapter we'll look at how to find and install new packages in order to add new functionality, how to find and install new themes, how to work with and manipulate text in a more advanced way, how to customize the editor in any way you wish, how to work with Git for version control and more.

    At the end of this chapter, you should have a customized environment that you're comfortable in and you should be able to change, create and move through your text like a master.

    Atom Packages

    ',5),we={href:"https://github.com/atom/tree-view",target:"_blank",rel:"noopener noreferrer"},_e={href:"https://github.com/atom/settings-view",target:"_blank",rel:"noopener noreferrer"},xe={href:"https://github.com/atom/welcome",target:"_blank",rel:"noopener noreferrer"},Ce={href:"https://github.com/atom/spell-check",target:"_blank",rel:"noopener noreferrer"},Ae={href:"https://github.com/atom/one-dark-ui",target:"_blank",rel:"noopener noreferrer"},Se={href:"https://github.com/atom/fuzzy-finder",target:"_blank",rel:"noopener noreferrer"},qe=n('

    This means that packages can be incredibly powerful and can change everything from the very look and feel of the entire interface to the basic operation of even core functionality.

    In order to install a new package, you can use the Install tab in the now familiar Settings View. Open up the Settings View using Cmd+,Ctrl+,, click on the "Install" tab and type your search query into the box under Install Packages.

    The packages listed here have been published to https://atom.io/packages which is the official registry for Atom packages. Searching on the Settings View will go to the Atom package registry and pull in anything that matches your search terms.

    Package install screen

    All of the packages will come up with an "Install" button. Clicking that will download the package and install it. Your editor will now have the functionality that the package provides.

    Package Settings

    Once a package is installed in Atom, it will show up in the Settings View under the "Packages" tab, along with all the preinstalled packages that come with Atom. To filter the list in order to find one, you can type into search box directly under the "Installed Packages" heading.

    Package settings screen

    Clicking on the "Settings" button for a package will give you the settings screen for that package specifically. Here you have the option of changing some of the default variables for the package, seeing what all the command keybindings are, disabling the package temporarily, looking at the source code, seeing the current version of the package, reporting issues and uninstalling the package.

    If a new version of any of your packages is released, Atom will automatically detect it and you can upgrade the package from either this screen or from the "Updates" tab. This helps you easily keep all your installed packages up to date.

    Atom Themes

    You can also find and install new themes for Atom from the Settings View. These can be either UI themes or syntax themes and you can search for them from the "Install" tab, just like searching for new packages. Make sure to press the "Themes" toggle next to the search box.

    Theme search screen

    Clicking on the theme title will take you to a profile page for the theme on atom.io, which often has a screenshot of the theme. This way you can see what it looks like before installing it.

    ',14),Te=n('

    Example of the Unity UI theme with Monokai syntax theme

    Command Line

    You can also install packages or themes from the command line using apm.

    Tip

    Check that you have apm installed by running the following command in your terminal:

    $ apm help install
    +

    You should see a message print out with details about the apm install command.

    If you do not, see the Installing Atom section for instructions on how to install the atom and apm commands for your system.

    You can also install packages by using the apm install command:

    • apm install <package_name> to install the latest version.
    • apm install <package_name>@<package_version> to install a specific version.
    `,6),Ie=e("code",null,"apm install emmet@0.1.5",-1),Pe=e("code",null,"0.1.5",-1),Fe={href:"https://github.com/atom/emmet",target:"_blank",rel:"noopener noreferrer"},Re=n(`

    You can also use apm to find new packages to install. If you run apm search, you can search the package registry for a search term.

    $ apm search coffee
    +> Search Results For 'coffee' (29)
    +> \u251C\u2500\u2500 build-coffee Atom Build provider for coffee, compiles CoffeeScript (1160 downloads, 2 stars)
    +> \u251C\u2500\u2500 scallahan-coffee-syntax A coffee inspired theme from the guys over at S.CALLAHAN (183 downloads, 0 stars)
    +> \u251C\u2500\u2500 coffee-paste Copy/Paste As : Js \u27A4 Coffee / Coffee \u27A4 Js (902 downloads, 4 stars)
    +> \u251C\u2500\u2500 atom-coffee-repl Coffee REPL for Atom Editor (894 downloads, 2 stars)
    +> \u251C\u2500\u2500 coffee-navigator Code navigation panel for Coffee Script (3493 downloads, 22 stars)
    +> ...
    +> \u251C\u2500\u2500 language-iced-coffeescript Iced coffeescript for atom (202 downloads, 1 star)
    +> \u2514\u2500\u2500 slontech-syntax Dark theme for web developers ( HTML, CSS/LESS, PHP, MYSQL, javascript, AJAX, coffee, JSON ) (2018 downloads, 3 stars)
    +

    You can use apm view to see more information about a specific package.

    $ apm view build-coffee
    +> build-coffee
    +> \u251C\u2500\u2500 0.6.4
    +> \u251C\u2500\u2500 https://github.com/idleberg/atom-build-coffee
    +> \u251C\u2500\u2500 Atom Build provider for coffee, compiles CoffeeScript
    +> \u251C\u2500\u2500 1152 downloads
    +> \u2514\u2500\u2500 2 stars
    +>
    +> Run \`apm install build-coffee\` to install this package.
    +

    Moving in Atom

    While it's pretty easy to move around Atom by clicking with the mouse or using the arrow keys, there are some keybindings that may help you keep your hands on the keyboard and navigate around a little faster.

    `,6),Le=e("p",null,"Atom has support for all the standard Linux cursor movement key combinations. To go up, down, left or right a single character you can use the arrow keys.",-1),Ge=e("p",null,"In addition to single character movement, there are a number of other movement keybindings:",-1),He=e("p",null,[t("Atom ships with many of the basic Emacs keybindings for navigating a document. To go up and down a single character, you can use "),e("kbd",{class:"platform-mac"},"Ctrl+P"),t(" and "),e("kbd",{class:"platform-mac"},"Ctrl+N"),t(". To go left and right a single character, you can use "),e("kbd",{class:"platform-mac"},"Ctrl+B"),t(" and "),e("kbd",{class:"platform-mac"},"Ctrl+F"),t(". These are the equivalent of using the arrow keys, though some people prefer not having to move their hands to where the arrow keys are located on their keyboard.")],-1),Oe=e("p",null,"In addition to single character movement, there are a number of other movement keybindings:",-1),Me=e("p",null,"Atom has support for all the standard Windows cursor movement key combinations. To go up, down, left or right a single character you can use the arrow keys.",-1),je=e("p",null,"In addition to single character movement, there are a number of other movement keybindings:",-1),ze=n('
    • Alt+Left or Alt+BCtrl+Left - Move to the beginning of word
    • Alt+Right or Alt+FCtrl+Right - Move to the end of word
    • Cmd+Left or Ctrl+AHome - Move to the first character of the current line
    • Cmd+Right or Ctrl+EEnd - Move to the end of the line
    • Cmd+UpCtrl+Home - Move to the top of the file
    • Cmd+DownCtrl+End - Move to the bottom of the file

    You can also move directly to a specific line (and column) number with Ctrl+G. This will bring up a dialog that asks which line you would like to jump to. You can also use the row:column syntax to jump to a character in that line as well.

    Go directly to a line

    Additional Movement and Selection Commands

    ',4),Ee=e("code",null,"keymap.cson",-1),De=e("code",null,"keymap.cson",-1),Ye=e("span",{class:"platform-mac"},[e("em",null,"Atom > Keymap")],-1),$e=e("span",{class:"platform-windows"},[e("em",null,"File > Keymap")],-1),We=e("span",{class:"platform-linux"},[e("em",null,"Edit > Keymap")],-1),Ue=e("p",null,[t("For example, the command "),e("code",null,"editor:move-to-beginning-of-screen-line"),t(" is available in the command palette, but it's not bound to any key combination. To create a key combination you need to add an entry in your "),e("code",null,"keymap.cson"),t(" file. For "),e("code",null,"editor:select-to-previous-word-boundary"),t(", you can add the following to your "),e("code",null,"keymap.cson"),t(":")],-1),Be=e("div",{class:"language-coffee ext-coffee line-numbers-mode"},[e("pre",{class:"language-coffee"},[e("code",null,[e("span",{class:"token string-property property"},"'atom-text-editor'"),e("span",{class:"token operator"},":"),t(` + `),e("span",{class:"token string-property property"},"'ctrl-shift-e'"),e("span",{class:"token operator"},":"),t(),e("span",{class:"token string"},"'editor:select-to-previous-word-boundary'"),t(` +`)])]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),Ne=e("div",{class:"language-coffee ext-coffee line-numbers-mode"},[e("pre",{class:"language-coffee"},[e("code",null,[e("span",{class:"token string-property property"},"'atom-text-editor'"),e("span",{class:"token operator"},":"),t(` + `),e("span",{class:"token string-property property"},"'cmd-shift-e'"),e("span",{class:"token operator"},":"),t(),e("span",{class:"token string"},"'editor:select-to-previous-word-boundary'"),t(` +`)])]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),Ve=e("div",{class:"language-coffee ext-coffee line-numbers-mode"},[e("pre",{class:"language-coffee"},[e("code",null,[e("span",{class:"token string-property property"},"'atom-text-editor'"),e("span",{class:"token operator"},":"),t(` + `),e("span",{class:"token string-property property"},"'ctrl-shift-e'"),e("span",{class:"token operator"},":"),t(),e("span",{class:"token string"},"'editor:select-to-previous-word-boundary'"),t(` +`)])]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),Je=e("code",null,"editor:select-to-previous-word-boundary",-1),Ke=e("kbd",{class:"platform-mac"},"Cmd+Shift+E",-1),Ze=e("kbd",{class:"platform-windows platform-linux"},"Ctrl+Shift+E",-1),Qe=e("p",null,"Here's a list of Movement and Selection Commands that do not have a keyboard shortcut by default:",-1),Xe=e("div",{class:"language-text ext-text line-numbers-mode"},[e("pre",{class:"language-text"},[e("code",null,`editor:move-to-beginning-of-next-paragraph +editor:move-to-beginning-of-previous-paragraph +editor:move-to-beginning-of-screen-line +editor:move-to-beginning-of-line +editor:move-to-end-of-line +editor:move-to-first-character-of-line +editor:move-to-beginning-of-next-word +editor:move-to-previous-word-boundary +editor:move-to-next-word-boundary +editor:select-to-beginning-of-next-paragraph +editor:select-to-beginning-of-previous-paragraph +editor:select-to-end-of-line +editor:select-to-beginning-of-line +editor:select-to-beginning-of-next-word +editor:select-to-next-word-boundary +editor:select-to-previous-word-boundary +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),et=e("div",{class:"language-text ext-text line-numbers-mode"},[e("pre",{class:"language-text"},[e("code",null,`editor:move-to-beginning-of-next-paragraph +editor:move-to-beginning-of-previous-paragraph +editor:move-to-beginning-of-screen-line +editor:move-to-beginning-of-line +editor:move-to-beginning-of-next-word +editor:move-to-previous-word-boundary +editor:move-to-next-word-boundary +editor:select-to-beginning-of-next-paragraph +editor:select-to-beginning-of-previous-paragraph +editor:select-to-beginning-of-line +editor:select-to-beginning-of-next-word +editor:select-to-next-word-boundary +editor:select-to-previous-word-boundary +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),tt=e("div",{class:"language-text ext-text line-numbers-mode"},[e("pre",{class:"language-text"},[e("code",null,`editor:move-to-beginning-of-next-paragraph +editor:move-to-beginning-of-previous-paragraph +editor:move-to-beginning-of-screen-line +editor:move-to-beginning-of-line +editor:move-to-end-of-line +editor:move-to-first-character-of-line +editor:move-to-beginning-of-next-word +editor:move-to-previous-word-boundary +editor:move-to-next-word-boundary +editor:select-to-beginning-of-next-paragraph +editor:select-to-beginning-of-previous-paragraph +editor:select-to-end-of-line +editor:select-to-beginning-of-line +editor:select-to-beginning-of-next-word +editor:select-to-next-word-boundary +editor:select-to-previous-word-boundary +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),at=n('

    You can also jump around a little more informatively with the Symbols View. To jump to a symbol such as a method definition, press Cmd+RCtrl+R. This opens a list of all symbols in the current file, which you can fuzzy filter similarly to Cmd+TCtrl+T. You can also search for symbols across your project but it requires a tags file.

    Search by symbol across your project

    ',3),ot=e("code",null,"tags",-1),nt={href:"https://ctags.io/",target:"_blank",rel:"noopener noreferrer"},st=e("code",null,"tags",-1),it={href:"https://docs.ctags.io/en/latest/",target:"_blank",rel:"noopener noreferrer"},lt=e("p",null,[t("Once you have your "),e("code",null,"tags"),t(" file generated, you can use it to search for symbols across your project by pressing "),e("kbd",{class:"platform-windows platform-linux"},"Ctrl+Shift+R"),t(". This also enables you to use "),e("kbd",{class:"platform-linux"},"Alt+Ctrl+Down"),t(" to go to and "),e("kbd",{class:"platform-linux"},"Alt+Ctrl+Up"),t(" to return from the declaration of the symbol under the cursor.")],-1),rt=e("p",null,[t("Once you have your "),e("code",null,"tags"),t(" file generated, you can use it to search for symbols across your project by pressing "),e("kbd",{class:"platform-mac"},"Cmd+Shift+R"),t(". This also enables you to use "),e("kbd",{class:"platform-mac"},"Alt+Cmd+Down"),t(" to go to and "),e("kbd",{class:"platform-mac"},"Alt+Cmd+Up"),t(" to return from the declaration of the symbol under the cursor.")],-1),ct=e("p",null,[t("Once you have your "),e("code",null,"tags"),t(" file generated, you can use it to search for symbols across your project by pressing "),e("kbd",{class:"platform-mac"},"Cmd+Shift+R"),e("kbd",{class:"platform-windows platform-linux"},"Ctrl+Shift+R"),t(".")],-1),dt=e("code",null,".ctags",-1),pt=e("span",{class:"platform-mac platform-linux"},[e("code",null,"~/.ctags")],-1),ht=e("span",{class:"platform-windows"},[e("code",null,"%USERPROFILE%\\.ctags")],-1),ut={href:"https://github.com/atom/symbols-view/blob/master/lib/ctags-config",target:"_blank",rel:"noopener noreferrer"},mt={href:"https://github.com/atom/symbols-view",target:"_blank",rel:"noopener noreferrer"},ft=n('

    Bookmarks

    Atom also has a great way to bookmark specific lines in your project so you can jump back to them quickly.

    If you press Cmd+F2Alt+Ctrl+F2Ctrl+Shift+F2, Atom will toggle a "bookmark" on the current line. You can set these throughout your project and use them to quickly find and jump to important lines of your project. A small bookmark symbol is added to the line gutter, like on line 22 of the image below.

    If you hit F2, Atom will jump to the next bookmark in the file you currently have focused. If you use Shift+F2 it will cycle backwards through them instead.

    You can also see a list of all your project's current bookmarks and quickly filter them and jump to any of them by hitting Ctrl+F2.

    ',6),gt={href:"https://github.com/atom/bookmarks",target:"_blank",rel:"noopener noreferrer"},bt=n('

    Atom Selections

    Text selections in Atom support a number of actions, such as scoping deletion, indentation and search actions, and marking text for actions such as quoting and bracketing.

    Selections mirror many of the movement commands. They're actually exactly the same keybindings as the movement commands, but with a Shift key added in.

    • Shift+Up or Ctrl+Shift+P - Select up
    • Shift+Down or Ctrl+Shift+N - Select down
    • Shift+Left or Ctrl+Shift+B - Select previous character
    • Shift+Right or Ctrl+Shift+F - Select next character
    • Alt+Shift+LeftCtrl+Shift+Left or Alt+Shift+B - Select to beginning of word
    • Alt+Shift+RightCtrl+Shift+Right or Alt+Shift+F - Select to end of word
    • Cmd+Shift+RightShift+End or Ctrl+Shift+E - Select to end of line
    • Cmd+Shift+LeftShift+Home or Ctrl+Shift+A - Select to first character of line
    • Cmd+Shift+UpCtrl+Shift+Home - Select to top of file
    • Cmd+Shift+DownCtrl+Shift+End - Select to bottom of file

    In addition to the cursor movement selection commands, there are also a few commands that help with selecting specific areas of content.

    • Cmd+ACtrl+A - Select the entire contents of the file
    • Cmd+LCtrl+L - Select the entire line
    ',6),kt=e("ul",null,[e("li",null,[e("kbd",{class:"platform-mac"},"Ctrl+Shift+W"),t(" - Select the current word")])],-1),yt=n('

    Editing and Deleting Text

    So far we've looked at a number of ways to move around and select regions of a file, so now let's actually change some of that text. Obviously you can type in order to insert characters, but there are also a number of ways to delete and manipulate text that could come in handy.

    Basic Manipulation

    There are a handful of cool keybindings for basic text manipulation that might come in handy. These range from moving around lines of text and duplicating lines to changing the case.

    • Cmd+JCtrl+J - Join the next line to the end of the current line
    • Cmd+Ctrl+Up/DownCtrl+Up/Down - Move the current line up or down
    • Cmd+Shift+DCtrl+Shift+D - Duplicate the current line
    • Cmd+K Cmd+UCtrl+K Ctrl+U - Upper case the current word
    • Cmd+K Cmd+LCtrl+K Ctrl+L - Lower case the current word
    ',5),vt=e("ul",null,[e("li",null,[e("kbd",{class:"platform-mac"},"Ctrl+T"),t(" - Transpose characters. This swaps the two characters on either side of the cursor.")])],-1),wt=n('

    Atom also has built in functionality to re-flow a paragraph to hard-wrap at a given maximum line length. You can format the current selection to have lines no longer than 80 (or whatever number editor.preferredLineLength is set to) characters using Alt+Cmd+QAlt+Ctrl+Q. If nothing is selected, the current paragraph will be reflowed.

    Deleting and Cutting

    You can also delete or cut text out of your buffer with some shortcuts. Be ruthless.

    • Ctrl+Shift+K - Delete current line
    • Alt+Backspace or Alt+HCtrl+Backspace - Delete to beginning of word
    • Alt+Delete or Alt+DCtrl+Delete - Delete to end of word
    ',4),_t=e("ul",null,[e("li",null,[e("kbd",{class:"platform-mac"},"Cmd+Delete"),t(" - Delete to end of line")]),e("li",null,[e("kbd",{class:"platform-mac"},"Ctrl+K"),t(" - Cut to end of line")]),e("li",null,[e("kbd",{class:"platform-mac"},"Cmd+Backspace"),t(" - Delete to beginning of line")])],-1),xt=n('

    Multiple Cursors and Selections

    One of the cool things that Atom can do out of the box is support multiple cursors. This can be incredibly helpful in manipulating long lists of text.

    • Cmd+ClickCtrl+Click - Add a new cursor at the clicked location
    • Ctrl+Shift+Up/DownAlt+Ctrl+Up/DownAlt+Shift+Up/Down - Add another cursor above/below the current cursor
    • Cmd+DCtrl+D - Select the next word in the document that is the same as the currently selected word
    • Cmd+Ctrl+GAlt+F3 - Select all words in the document that are the same as the currently selected word
    ',3),Ct=e("ul",null,[e("li",null,[e("kbd",{class:"platform-mac"},"Cmd+Shift+L"),t(" - Convert a multi-line selection into multiple cursors")])],-1),At=n('

    Using these commands you can place cursors in multiple places in your document and effectively execute the same commands in multiple places at once.

    Using multiple cursors

    This can be incredibly helpful in doing many type of repetitive tasks such as renaming variables or changing the format of some text. You can use this with almost any plugin or command - for example, changing case and moving or duplicating lines.

    You can also use the mouse to select text with the CmdCtrl key pressed down to select multiple regions of your text simultaneously.

    Whitespace

    Atom comes with several commands to help you manage the whitespace in your document. One very useful pair of commands converts leading spaces into tabs and converts leading tabs into spaces. If you're working with a document that has mixed whitespace, these commands are great for helping to normalize the file. There are no keybindings for the whitespace commands, so you will have to search your command palette for "Convert Spaces to Tabs" (or vice versa) to run one of these commands.

    ',6),St={href:"https://github.com/atom/whitespace",target:"_blank",rel:"noopener noreferrer"},qt=e("code",null,"whitespace",-1),Tt=n('

    Managing your whitespace settings

    Note

    The "Remove Trailing Whitespace" option is on by default. This means that every time you save any file opened in Atom, it will strip all trailing whitespace from the file. If you want to disable this, go to the whitespace package in your settings panel and uncheck that option.

    Atom will also by default ensure that your file has a trailing newline. You can also disable this option on that screen.

    Brackets

    Atom ships with intelligent and easy to use bracket handling.

    It will by default highlight [], (), and {} style brackets when your cursor is over them. It will also highlight matching XML and HTML tags.

    Atom will also automatically autocomplete [], (), and {}, "", '', \u201C\u201D, \u2018\u2019, \xAB\xBB, \u2039\u203A, and backticks when you type the leading one. If you have a selection and you type any of these opening brackets or quotes, Atom will enclose the selection with the opening and closing brackets or quotes.

    There are a few other interesting bracket related commands that you can use.

    • Ctrl+M - Jump to the bracket matching the one adjacent to the cursor. It jumps to the nearest enclosing bracket when there's no adjacent bracket.
    • Cmd+Ctrl+MAlt+Ctrl+, - Select all the text inside the current brackets
    • Alt+Cmd+.Alt+Ctrl+. - Close the current XML/HTML tag
    ',9),It={href:"https://github.com/atom/bracket-matcher",target:"_blank",rel:"noopener noreferrer"},Pt=n('

    Encoding

    Atom also ships with some basic file encoding support should you find yourself working with non-UTF-8 encoded files, or should you wish to create one.

    • Ctrl+Shift+UAlt+U - Toggle menu to change file encoding

    If you pull up the file encoding dialog, you can choose an alternate file encoding to save your file in.

    When you open a file, Atom will try to auto-detect the encoding. If Atom can't identify the encoding, the encoding will default to UTF-8, which is also the default encoding for new files.

    Changing your file encoding

    If you pull up the encoding menu and change the active encoding to something else, the file will be written out in that encoding the next time you save the file.

    ',7),Ft={href:"https://github.com/atom/encoding-selector",target:"_blank",rel:"noopener noreferrer"},Rt=n('

    Find and Replace

    Finding and replacing text in your file or project is quick and easy in Atom.

    • Cmd+FCtrl+F - Search within a buffer
    • Cmd+Shift+FCtrl+Shift+F - Search the entire project

    If you launch either of those commands, you'll be greeted with the Find and Replace panel at the bottom of your screen.

    Find and replace text in the current file

    To search within your current file you can press Cmd+FCtrl+F, type in a search string and press Enter (or Cmd+GF3 or the "Find Next" button) multiple times to cycle through all the matches in that file. Alt+Enter will find all occurences of the search string. The Find and Replace panel also contains buttons for toggling case sensitivity, performing regular expression matching, scoping the search to selections, and performing whole word search.

    If you type a string in the replacement text box, you can replace matches with a different string. For example, if you wanted to replace every instance of the string "Scott" with the string "Dragon", you would enter those values in the two text boxes and press the "Replace All" button to perform the replacements.

    ',7),Lt={class:"custom-container note"},Gt=e("p",{class:"custom-container-title"},"Note",-1),Ht=e("p",null,[e("strong",null,"Note:"),t(" Atom uses JavaScript regular expressions to perform regular expression searches.")],-1),Ot={href:"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions",target:"_blank",rel:"noopener noreferrer"},Mt=e("p",null,[t("You can also find and replace throughout your entire project if you invoke the panel with "),e("kbd",{class:"platform-mac"},"Cmd+Shift+F"),e("kbd",{class:"platform-windows platform-linux"},"Ctrl+Shift+F"),t(".")],-1),jt=e("p",null,[e("img",{src:w,alt:"Find and replace text in your project",title:"Find and replace text in your project"})],-1),zt=e("p",null,"This is a great way to find out where in your project a function is called, an anchor is linked to or a specific misspelling is located. Click on the matching line to jump to that location in that file.",-1),Et={href:"https://en.wikipedia.org/wiki/Glob_%28programming%29",target:"_blank",rel:"noopener noreferrer"},Dt=e("code",null,"src/*.js",-1),Yt=e("code",null,"src",-1),$t=e("code",null,"**",-1),Wt=e("code",null,"docs/**/*.md",-1),Ut=e("code",null,"docs/a/foo.md",-1),Bt=e("code",null,"docs/a/b/foo.md",-1),Nt=e("p",null,[t("When you have multiple project folders open, this feature can also be used to search in only one of those folders. For example, if you had the folders "),e("code",null,"/path1/folder1"),t(" and "),e("code",null,"/path2/folder2"),t(" open, you could enter a pattern starting with "),e("code",null,"folder1"),t(" to search only in the first folder.")],-1),Vt=e("p",null,[t("Press "),e("kbd",{class:"platform-all"},"Esc"),t(" while focused on the Find and Replace panel to clear the pane from your workspace.")],-1),Jt={href:"https://github.com/atom/find-and-replace",target:"_blank",rel:"noopener noreferrer"},Kt={href:"https://github.com/atom/scandal",target:"_blank",rel:"noopener noreferrer"},Zt=n(`

    Snippets

    Snippets are an incredibly powerful way to quickly generate commonly needed code syntax from a shortcut.

    The idea is that you can type something like habtm and then press the Tab key and it will expand into has_and_belongs_to_many.

    Many Core and Community packages come bundled with their own snippets that are specific to it. For example, the language-html package that provides support for HTML syntax highlighting and grammar comes with dozens of snippets to create many of the various HTML tags you might want to use. If you create a new HTML file in Atom, you can type html and then press Tab and it will expand to:

    <!DOCTYPE html>
    +<html lang="en" dir="ltr">
    +	<head>
    +		<meta charset="utf-8" />
    +		<title></title>
    +	</head>
    +	<body></body>
    +</html>
    +

    It will also position the cursor in the lang attribute value so you can edit it if necessary. Many snippets have multiple focus points that you can move through with the Tab key as well - for instance, in the case of this HTML snippet, after the cursor is placed in the lang attribute value, you can continue pressing Tab and the cursor will move to the dir attribute value, then to the middle of the title tag, then finally to the middle of the body tag.

    To see all the available snippets for the file type that you currently have open, choose "Snippets: Available" in the Command Palette.

    View all available snippets

    You can also use fuzzy search to filter this list down by typing in the selection box. Selecting one of them will execute the snippet where your cursor is (or multiple cursors are).

    Creating Your Own Snippets

    So that's pretty cool, but what if there is something the language package didn't include or something that is custom to the code you write? Luckily it's incredibly easy to add your own snippets.

    There is a text file in your ~/.atom%USERPROFILE%\\.atom directory called snippets.cson that contains all your custom snippets that are loaded when you launch Atom. You can also easily open up that file by selecting the Atom > SnippetsEdit > SnippetsFile > Snippets menu.

    Snippet Format

    So let's look at how to write a snippet. The basic snippet format looks like this:

    '.source.js':
    +  'console.log':
    +    'prefix': 'log'
    +    'body': 'console.log(\${1:"crash"});$2'
    +

    The leftmost keys are the selectors where these snippets should be active. The easiest way to determine what this should be is to go to the language package of the language you want to add a snippet for and look for the "Scope" string.

    For example, if we wanted to add a snippet that would work for Java files, we would look up the language-java package in our Settings view and we can see the Scope is source.java. Then the top level snippet key would be that prepended by a period (like a CSS class selector would do).

    Finding the selector scope for a snippet

    The next level of keys are the snippet names. These are used for describing the snippet in a more readable way in the snippet menu. You can name them whatever you want.

    Under each snippet name is a prefix that should trigger the snippet and a body to insert when the snippet is triggered.

    Each $ followed by a number is a tab stop. Tab stops are cycled through by pressing Tab once a snippet has been triggered.

    Tab stops with the same number will create multiple cursors.

    The above example adds a log snippet to JavaScript files that would expand to:

    console.log("crash");
    +

    The string "crash" would be initially selected and pressing tab again would place the cursor after the ;

    `,25),Qt={class:"custom-container warning"},Xt=e("p",{class:"custom-container-title"},"Warning",-1),ea=e("h5",{id:"multi-line-snippet-body",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#multi-line-snippet-body","aria-hidden":"true"},"#"),t(" Multi-line Snippet Body")],-1),ta={href:"http://coffeescript.org/#strings",target:"_blank",rel:"noopener noreferrer"},aa=e("code",null,'"""',-1),oa=n(`
    '.source.js':
    +  'if, else if, else':
    +    'prefix': 'ieie'
    +    'body': """
    +      if (\${1:true}) {
    +        $2
    +      } else if (\${3:false}) {
    +        $4
    +      } else {
    +        $5
    +      }
    +    """
    +

    As you might expect, there is a snippet to create snippets. If you open up a snippets file and type snip and then press Tab, you will get the following text inserted:

    '.source.js':
    +  'Snippet Name':
    +    'prefix': 'hello'
    +    'body': 'Hello World!'
    +

    \u{1F4A5} just fill that bad boy out and you have yourself a snippet. As soon as you save the file, Atom should reload the snippets and you will immediately be able to try it out.

    Multiple Snippets per Source

    You can see below the format for including multiple snippets for the same scope in your snippets.cson file. Just include the snippet name, prefix, and body keys for additional snippets inside the scope key:

    '.source.gfm':
    +  'Hello World':
    +    'prefix': 'hewo'
    +    'body': 'Hello World!'
    +
    +  'Github Hello':
    +    'prefix': 'gihe'
    +    'body': 'Octocat says Hi!'
    +
    +  'Octocat Image Link':
    +    'prefix': 'octopic'
    +    'body': '![GitHub Octocat](https://assets-cdn.github.com/images/modules/logos_page/Octocat.png)'
    +
    `,7),na=e("h4",{id:"more-info",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#more-info","aria-hidden":"true"},"#"),t(" More Info")],-1),sa={href:"https://github.com/atom/snippets",target:"_blank",rel:"noopener noreferrer"},ia={href:"https://github.com/atom/language-html/blob/master/snippets/language-html.cson",target:"_blank",rel:"noopener noreferrer"},la={href:"https://github.com/atom/language-javascript/blob/master/snippets/language-javascript.cson",target:"_blank",rel:"noopener noreferrer"},ra=n('

    Autocomplete

    If you're still looking to save some typing time, Atom also ships with simple autocompletion functionality.

    The autocomplete system lets you view and insert possible completions in the editor using Tab or Enter.

    Autocomplete menu

    By default, the autocomplete system will look through the current open file for strings that match what you're starting to type.

    If you want more options, in the Settings panel for the autocomplete-plus package you can toggle a setting to make autocomplete-plus look for text in all your open buffers rather than just the current file.

    ',6),ca={href:"https://github.com/atom/autocomplete-plus",target:"_blank",rel:"noopener noreferrer"},da=n('

    Folding

    If you want to see an overview of the structure of the code file you're working on, folding can be a helpful tool. Folding hides blocks of code such as functions or looping blocks in order to simplify what is on your screen.

    You can fold blocks of code by clicking the arrows that appear when you hover your mouse cursor over the gutter. You can also fold and unfold from the keyboard with the Alt+Cmd+[Alt+Ctrl+[ and Alt+Cmd+]Alt+Ctrl+] keybindings.

    Code folding example

    To fold everything, use Alt+Cmd+Shift+[Alt+Ctrl+Shift+[ and to unfold everything use Alt+Cmd+Shift+]Alt+Ctrl+Shift+]. You can also fold at a specific indentation level with Cmd+KCtrl+K Cmd+0-9Ctrl+0-9 where the number is the indentation depth.

    Finally, you can fold arbitrary sections of your code or text by making a selection and then typing Alt+Cmd+Ctrl+FAlt+Ctrl+F or choosing "Fold Selection" in the Command Palette.

    Panes

    You can split any editor pane horizontally or vertically by using Cmd+KCtrl+K Up/Down/Left/Right where the direction key is the direction to split the pane. Once you have a split pane, you can switch between them with Cmd+KCtrl+K Cmd+Up/Down/Left/RightCtrl+Up/Down/Left/Right where the direction is the direction the focus should move to.

    Multiple panes

    Each pane has its own "pane items", which are represented by tabs. You can move the files from pane to pane by dragging them with the mouse and dropping them in the pane you want that file to be in.

    ',10),pa={class:"custom-container tip"},ha=e("p",{class:"custom-container-title"},"Tip",-1),ua={href:"https://github.com/atom/tabs",target:"_blank",rel:"noopener noreferrer"},ma=n('

    To close a pane, you can close all pane items with Cmd+WCtrl+W. You can configure whether panes auto-close when empty in the Settings View with the "Remove Empty Panes" setting under Core Settings.

    Pending Pane Items

    "Pending Pane Items" were formerly referred to as "Preview Tabs"

    When you open a new file by single-clicking in the Tree View, it will open in a new tab with an italic title. This indicates that the file is "pending". When a file is pending, it will be replaced by the next pending file that is opened. This allows you to click through a bunch of files to find something without having to go back and close them all.

    You can confirm a pending file by doing any of the following:

    • Double-clicking the tab of the file
    • Double-clicking the file in the tree view
    • Editing the contents of the file
    • Saving the file

    You can also open a file already confirmed by double-clicking it in the tree view instead of single-clicking it.

    Disabling Pending Pane Items

    Allow Pending Pane Items setting

    If you would prefer to not have files open in pending form, you can disable this behavior by unchecking "Allow Pending Pane Items" in the Core Settings section of the Settings View. With pending pane items disabled, single-clicking a file in the Tree View will select the file but not open it. You will have to double-click the file to open it.

    Grammar

    ',11),fa=e("p",null,[t("When you load a file, Atom does a little work to try to figure out what type of file it is. Largely this is accomplished by looking at its file extension ("),e("code",null,".md"),t(" is generally a Markdown file, etc), though sometimes it has to inspect the content a bit to figure it out.")],-1),ga=e("p",null,[t(`When you open a file and Atom can't determine a grammar for the file, it will default to "Plain Text", which is the simplest one. If it does default to "Plain Text", picks the wrong grammar for the file, or if for any reason you wish to change the selected grammar, you can pull up the Grammar Selector with `),e("kbd",{class:"platform-all"},"Ctrl+Shift+L"),t(".")],-1),ba=e("p",null,[e("img",{src:I,alt:"Grammar Selector",title:"Grammar Selector"})],-1),ka=e("p",null,"When the grammar of a file is changed, Atom will remember that for the current session.",-1),ya={href:"https://github.com/atom/atom/tree/master/packages/grammar-selector",target:"_blank",rel:"noopener noreferrer"},va=e("h3",{id:"version-control-in-atom",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#version-control-in-atom","aria-hidden":"true"},"#"),t(" Version Control in Atom")],-1),wa={href:"https://git-scm.com",target:"_blank",rel:"noopener noreferrer"},_a={href:"https://github.com",target:"_blank",rel:"noopener noreferrer"},xa=n('

    In order to use version control in Atom, the project root needs to contain the Git repository.

    Checkout HEAD revision

    The Alt+Cmd+ZAlt+Ctrl+Z keybinding checks out the HEAD revision of the file in the editor.

    This is a quick way to discard any saved and staged changes you've made and restore the file to the version in the HEAD commit. This is essentially the same as running git checkout HEAD -- <path> and git reset HEAD -- <path> from the command line for that path.

    Git checkout

    This command goes onto the undo stack so you can use Cmd+ZCtrl+Z afterwards to restore the previous contents.

    Git status list

    ',7),Ca={href:"https://github.com/atom/fuzzy-finder",target:"_blank",rel:"noopener noreferrer"},Aa=e("kbd",{class:"platform-mac"},"Cmd+T",-1),Sa=e("kbd",{class:"platform-windows platform-linux"},"Ctrl+T",-1),qa=e("kbd",{class:"platform-mac"},"Cmd+B",-1),Ta=e("kbd",{class:"platform-windows platform-linux"},"Ctrl+B",-1),Ia=e("kbd",{class:"platform-mac"},"Cmd+Shift+B",-1),Pa=e("kbd",{class:"platform-windows platform-linux"},"Ctrl+Shift+B",-1),Fa=e("code",null,"git status",-1),Ra=e("p",null,[e("img",{src:P,alt:"Git status list",title:"`git status` list"})],-1),La=e("p",null,"An icon will appear to the right of each file letting you know whether it is untracked or modified.",-1),Ga=e("h4",{id:"commit-editor",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#commit-editor","aria-hidden":"true"},"#"),t(" Commit editor")],-1),Ha={href:"https://github.com/atom/language-git",target:"_blank",rel:"noopener noreferrer"},Oa=n('

    Git commit message highlighting

    You can configure Atom to be your Git commit editor with the following command:

    $ git config --global core.editor "atom --wait"
    +
    `,3),Ma={href:"https://github.com/atom/language-git",target:"_blank",rel:"noopener noreferrer"},ja=e("h4",{id:"status-bar-icons",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#status-bar-icons","aria-hidden":"true"},"#"),t(" Status bar icons")],-1),za={href:"https://github.com/atom/status-bar",target:"_blank",rel:"noopener noreferrer"},Ea=e("p",null,[e("img",{src:F,alt:"Git Status Bar decorations",title:"Git Status Bar decorations"})],-1),Da=e("p",null,"The currently checked out branch name is shown with the number of commits the branch is ahead of or behind its upstream branch. An icon is added if the file is untracked, modified, or ignored. The number of lines added and removed since the file was last committed will be displayed as well.",-1),Ya=e("h4",{id:"line-diffs",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#line-diffs","aria-hidden":"true"},"#"),t(" Line diffs")],-1),$a={href:"https://github.com/atom/git-diff",target:"_blank",rel:"noopener noreferrer"},Wa=n('

    Git line diff indications

    This package also adds Alt+G Down and Alt+G Up keybindings that allow you to move the cursor to the next or previous diff in the current editor.

    Open on GitHub

    If the project you're working on is on GitHub, there are also some very useful integrations you can use. Most of the commands will take the current file you're viewing and open a view of that file on GitHub - for instance, the blame or commit history of that file.

    • Alt+G O - Open file on GitHub
    • Alt+G B - Open Blame view of file on GitHub
    • Alt+G H - Open History view of file on GitHub
    • Alt+G C - Copy the URL of the current file on GitHub to the clipboard
    • Alt+G R - Branch compare on GitHub

    The branch comparison shows you the commits that are on the branch you're currently working on locally that are not on the mainline branch.

    Open Blame of file on GitHub

    GitHub package

    The github package brings Git and GitHub integration right inside Atom.

    Most of the functionality lives within the Git and GitHub dock items.

    The Git and GitHub panels

    There are different ways to access them, probably the most common way is through their keybindings:

    • Open the Git panel: Ctrl+9
    • Open the GitHub panel: Ctrl+8

    Another way is from the menu: Packages -> GitHub -> Toggle Git Tab and Toggle GitHub Tab

    Or you can also toggle the Git panel from the Status Bar by clicking on the changed files icon:

    Open Git panel


    Initialize repositories

    In case a project doesn't have a Git repository yet, you can create one from the Git panel.

    Initialize repositories

    Clone repositories

    To clone a repository, open the GitHub panel while you have no project folders open in Atom and click "Clone an existing GitHub repository". In the dialog, paste the URL of a repository and click "Clone". The new project will be added to the Tree View.

    GitHub panel

    Clone dialog

    Alternately, run the GitHub: Clone command to open the Clone dialog any time.

    Branch

    To open the branch tooltip, click the branch icon in the Status Bar. From there you can create or switch branches.

    Create or switch branches

    Stage

    After making some changes, stage anything you want to be part of the next commit. Choose between staging...

    • All changes: Click the "Stage All" button in the "Unstaged Changes" bar.
    • Files: Double-click a file or select a file and press Enter.
    • Hunk: Click on the "Stage Hunk" button or select a hunk and press Enter.
    • Lines: Click on a line (or drag on multiple lines) to select, then click on the "Stage Selection" button. Or use the Cmd-/Cmd-/ key to toggle from hunk mode to line mode, then press Cmd-EnterCtrl-Enter to stage just a single line.

    Use the Cmd-LeftCtrl-Left or Cmd-RightCtrl-Right arrow key to switch between file list and the diff view. Unstaging can be done in the same way.

    Stage changes

    Discard changes

    If you no longer want to keep some changes, you can discard them. It's similar to staging, but accessible behind a context menu.

    • All changes: Click the ... menu in the "Unstaged Changes" header and choose "Discard All Changes".
    • Files: Right-click a file (or multiple) and choose "Discard Changes".
    • Hunk: Click on the trash icon in the top bar of a hunk.
    • Lines: Right-click on a line (or multiple) and choose "Discard Selection".

    Discard changes

    Commit Preview

    To double check all changes that are going into your next commit, click the "See All Staged Changes" button above the commit message box. It lets you see all of your staged changes in a single pane. This "commit preview" can also serve as an inspiration for writing the commit message.

    Commit Preview

    Commit

    Once you've staged your changes, enter a commit message. Feel free to describe the commit in more detail after leaving an empty line. Finalize by clicking the Commit button. If you need more space, click the expand icon at the bottom right. It will open a commit editor in the center.

    Commit changes

    To add multiple co-authors to a commit, click the "\u{1F464}\u2795" icon in the bottom left corner of the commit message editor. Now you can search by name, email or GitHub username to give credit to a co-author.

    Commit with co-authors

    Amend and undo

    In case you forgot to commit a change and would like to add it to your previous commit, right-click on the last commit, then choose "Amend" from the context menu.

    Amend previous commit

    If you want to edit the commit message of your last commit, or add/remove changes, click on the "Undo" button. It will roll back to the state just before you clicked on the commit button.

    Undo previous commit

    View commits

    Once you've made some commits, click on a commit message in the recent commit list to see the full diff and commit message associated with each:

    View commit detai

    Publish and push

    When you're ready to share your changes with your team members, click the Publish button in the Status Bar. It will push your local branch to the remote repository. After making more commits, you can Push them as well from the Status Bar.

    Publish and push commits

    Fetch and pull

    From time to time it's a good idea to click on the Fetch button to see if any other team member pushed changes. If so, click on Pull to merge the changes into your local branch.

    Fetch and pull commits

    If you prefer to rebase when pulling, you can configure Git to make it the default behavior:

    git config --global --bool pull.rebase true
    +
    `,62),Ua={href:"https://mislav.net/2013/02/merge-vs-rebase/",target:"_blank",rel:"noopener noreferrer"},Ba=n('

    Resolve conflicts

    Sometimes there can be conflicts when trying to merge. Files that have merge conflicts will show up in the "Merge Conflicts" list. Click on a file to open the editor. There you can resolve the conflict by picking a version or make further edits. Once done, stage the file and commit.

    Resolve conflicts

    Create a Pull Request

    When your changes are ready to be reviewed by your team members, open the "GitHub" panel Ctrl+8 and click on Open new pull request. It will open the browser where you can continue creating a pull request. If commits haven't been pushed or the branch isn't published yet, the GitHub package will do that automatically for you.

    Create a Pull Request

    View Pull Requests

    Once the pull request is created, it will appear under Current pull request at the top of the panel. Underneath is a list of Open pull requests. It lets you quickly find a pull request by avatar, title or PR number. It also lets you keep an eye on the CI status. Clicking on a pull request in the list opens a center pane with more details, the timeline and conversations.

    View Pull Requests

    Open any Issue or Pull Request

    You can open issues or pull requests from any repo on GitHub. To do so, run the GitHub: Open Issue Or Pull Request command and paste the URL from an issue or pull request. Then press the Open Issue or Pull Request button and it will open a center pane. This lets you keep an issue or pull request as a reference, when working in another repo.

    Open Issue or Pull Request

    Checkout a Pull Request

    To test a pull request locally, open it in the workspace center by clicking on the pull request in the "open pull requests" list from the GitHub tab, then click on the Checkout button. It will automatically create a local branch and pull all the changes. If you would like to contribute to that pull request, start making changes, commit and push. Your contribution is now part of that pull request.

    Checkout a pull request

    View Pull Request review comments

    To view review comments on a Pull Request, open the Reviews Tab from the See Reviews button from the footer of a Pull Request Pane. Alternatively, if the pull request has already been checked out, Reviews Tab can also be open from the same button on GitHub Tab.

    Open review tab from footer

    You can see all the review summaries and comments of a pull request in the Reviews Tab. The comment section has a progress bar to help you keep track of how close are you to finish addressing the Pull Request comments (i.e. marking all comment threads on a Pull Request as "resolved"). Comment threads are greyed out after they have been resolved.

    Review tab

    After the pull request branch has been checked out, you can click Jump To File to open the commented on file and make changes as per the review comment right in the editor. If you would like to get the full context of the review comment, click Open Diff to open the diff view with line highlighting.

    Jump to file from review tab

    Conversely, in-editor comments are indicated by the comment icon in the gutter. Clicking the icon, either from within the editor or the diff view, will take you back to the Reviews Tab.

    Open review tab from diff

    Respond to a Pull Request review comment

    To respond to a Pull Request review comment, type your message and click Comment; a single line comment will be created in the same thread as the comment you responded to. After addressing a Pull Request review comment, click Resolve conversation to mark the whole thread as "resolved". The progress bar in the "Comments" section will update accordingly.

    Respond to a Pull Request review comment

    Writing in Atom

    ',29),Na={href:"https://help.github.com/articles/about-writing-and-formatting-on-github/",target:"_blank",rel:"noopener noreferrer"},Va=n('

    In these docs, we'll concentrate on writing in Markdown; however, other prose markup languages like Asciidoc have packages that provide similar functionality.

    Spell Checking

    If you're working in text (which includes plain text files, GitHub markdown, and Git commit messages by default), Atom will automatically try to check your spelling.

    Any misspelled words will be highlighted (by default with a dashed red line beneath the word), and you can pull up a menu of possible corrections by hitting Cmd+Shift+;Ctrl+Shift+; (or by choosing "Correct Spelling" from the right-click context menu or from the Command Palette).

    Checking your spelling

    To add more types of files to the list of what Atom will try to spell check, go to the Spell Check package settings in your Settings view and add any grammars you want to spell check.

    The default grammars to spell check are text.plain, source.gfm, text.git-commit, source.asciidoc, source.rst, and text.restructuredtext but you can add other grammars if you wish to check those types of files too.

    ',7),Ja={href:"https://github.com/atom/spell-check",target:"_blank",rel:"noopener noreferrer"},Ka=n('

    Previews

    When writing prose in a markup language, it's often very useful to get an idea of what the content will look like when it's rendered. Atom ships with a package for previewing Markdown by default.

    • Ctrl+Shift+M - Will toggle Preview mode for Markdown.

    Preview your prose

    As you edit the text, the preview will also update automatically. This makes it fairly easy to check your syntax as you type.

    You can also copy the rendered HTML from the preview pane into your system clipboard when the preview is focused and you press Cmd+CCtrl+CCtrl+Ins or if you right-click in the preview pane and choose "Copy as HTML".

    ',6),Za={href:"https://github.com/atom/markdown-preview",target:"_blank",rel:"noopener noreferrer"},Qa=n(`

    Snippets

    There are also a number of great snippets available for writing Markdown quickly.

    If you type img and hit tab you get a Markdown-formatted image embed code like ![](). If you type table and hit tab you get a nice example table to fill out.

    | Header One | Header Two |
    +| :--------- | :--------- |
    +| Item One   | Item Two   |
    +

    Although there are only a handful of Markdown snippets (b for bold, i for italic, code for a code block, etc), they save you from having to look up the more obscure syntaxes. Again, you can easily see a list of all available snippets for the type of file you're currently in by choosing "Snippets: Available" in the Command Palette.

    Basic Customization

    Now that we are feeling comfortable with just about everything built into Atom, let's look at how to tweak it. Perhaps there is a keybinding that you use a lot but feels wrong or a color that isn't quite right for you. Atom is amazingly flexible, so let's go over some of the simpler flexes it can do.

    Configuring with CSON

    `,8),Xa=e("a",{href:"#style-tweaks"},"style sheet",-1),eo=e("a",{href:"/hacking-atom/sections/the-init-file"},"Init Script",-1),to={href:"https://github.com/bevry/cson#what-is-cson",target:"_blank",rel:"noopener noreferrer"},ao={href:"https://json.org/",target:"_blank",rel:"noopener noreferrer"},oo=n(`
    key:
    +  key: value
    +  key: value
    +  key: [value, value]
    +

    Objects are the backbone of any CSON file, and are delineated by indentation (as in the above example). A key's value can either be a String, a Number, an Object, a Boolean, null, or an Array of any of these data types.

    Warning

    Just like the more common JSON, CSON's keys can only be repeated once per object. If there are duplicate keys, then the last usage of that key overwrites all others, as if they weren't there. The same holds true for Atom's config files.

    Don't do this:

    # Only the second snippet will be loaded
    +'.source.js':
    +  'console.log':
    +    'prefix': 'log'
    +    'body': 'console.log(\${1:"crash"});$2'
    +'.source.js':
    +  'console.error':
    +    'prefix': 'error'
    +    'body': 'console.error(\${1:"crash"});$2'
    +

    Use this instead:

    # Both snippets will be loaded
    +'.source.js':
    +  'console.log':
    +    'prefix': 'log'
    +    'body': 'console.log(\${1:"crash"});$2'
    +  'console.error':
    +    'prefix': 'error'
    +    'body': 'console.error(\${1:"crash"});$2'
    +

    Style Tweaks

    If you want to apply quick-and-dirty personal styling changes without creating an entire theme that you intend to publish, you can add styles to the styles.less file in your ~/.atom%USERPROFILE%\\.atom directory. You can open this file in an editor from the Atom > StylesheetFile > StylesheetEdit > Stylesheet menu.

    `,5),no=e("p",null,[e("img",{src:de,alt:"Stylesheet",title:"Stylesheet..."})],-1),so=n(`

    For example, to change the colors of the Status Bar, you could add the following rule to your styles.less file:

    .status-bar {
    +	color: white;
    +	background-color: black;
    +}
    +

    The easiest way to see what classes are available to style is to inspect the DOM manually via the Developer Tools. We'll go over the Developer Tools in great detail in the next chapter, but for now let's take a simple look. You can open the Developer Tools by pressing Alt+Cmd+ICtrl+Shift+I, which will bring up the Chromium Developer Tools panel.

    Developer Tools

    With the Developer Tools, you can inspect all the elements in Atom. If you want to update the style of something, you can figure out what classes it has and add a Less rule to your stylesheet to modify it.

    ',5),io={class:"custom-container tip"},lo=e("p",{class:"custom-container-title"},"Tip",-1),ro={href:"http://www.lesscss.org",target:"_blank",rel:"noopener noreferrer"},co=e("p",null,[t("If you prefer to use CSS instead, you can do that in the same "),e("code",null,"styles.less"),t(" file, since CSS is also valid in Less.")],-1),po=n(`

    Customizing Keybindings

    Atom keymaps work similarly to stylesheets. Just as stylesheets use selectors to apply styles to elements, Atom keymaps use selectors to associate key combinations with events in specific contexts. Here's a small example, excerpted from Atom's built-in keymap:

    'atom-text-editor':
    +  'enter': 'editor:newline'
    +
    +'atom-text-editor[mini] input':
    +  'enter': 'core:confirm'
    +

    This keymap defines the meaning of Enter in two different contexts. In a normal editor, pressing Enter triggers the editor:newline command, which causes the editor to insert a newline. But if the same keystroke occurs inside a select list's mini-editor, it instead triggers the core:confirm command based on the binding in the more-specific selector.

    By default, keymap.cson is loaded when Atom is started. It will always be loaded last, giving you the chance to override bindings that are defined by Atom's core keymaps or third-party packages. You can open this file in an editor from the Atom > KeymapFile > KeymapEdit > Keymap menu.

    You can see all the keybindings that are currently configured in your installation of Atom in the Keybindings tab in the Settings View.

    If you run into problems with keybindings, the Keybinding Resolver is a huge help. It can be opened with the Cmd+.Ctrl+. key combination. It will show you what keys Atom saw you press and what command Atom executed because of that combination.

    Global Configuration Settings

    Atom loads configuration settings from the config.cson file in your ~/.atom%USERPROFILE%\\.atom directory.

    '*':
    +  'core':
    +    'excludeVcsIgnoredPaths': true
    +  'editor':
    +    'fontSize': 18
    +

    The configuration is grouped into global settings under the * key and language-specific settings under scope named keys like .python.source or .html.text. Underneath that, you'll find configuration settings grouped by package name or one of the two core namespaces: core or editor.

    You can open this file in an editor from the Atom > ConfigFile > ConfigEdit > Config menu.

    Configuration Key Reference
    • core
      • customFileTypes: Associations of language scope to file extensions (see Customizing Language Recognition)
      • disabledPackages: An array of package names to disable
      • excludeVcsIgnoredPaths: Don't search within files specified by .gitignore
      • ignoredNames: File names to ignore across all of Atom
      • projectHome: The directory where projects are assumed to be located
      • themes: An array of theme names to load, in cascading order
    • editor
      • autoIndent: Enable/disable basic auto-indent (defaults to true)
      • nonWordCharacters: A string of non-word characters to define word boundaries
      • fontSize: The editor font size
      • fontFamily: The editor font family
      • invisibles: A hash of characters Atom will use to render whitespace characters. Keys are whitespace character types, values are rendered characters (use value false to turn off individual whitespace character types)
        • tab: Hard tab characters
        • cr: Carriage return (for Microsoft-style line endings)
        • eol: \\n characters
        • space: Leading and trailing space characters
      • lineHeight: Height of editor lines, as a multiplier of font size
      • preferredLineLength: Identifies the length of a line (defaults to 80)
      • showInvisibles: Whether to render placeholders for invisible characters (defaults to false)
      • showIndentGuide: Show/hide indent indicators within the editor
      • showLineNumbers: Show/hide line numbers within the gutter
      • softWrap: Enable/disable soft wrapping of text within the editor
      • softWrapAtPreferredLineLength: Enable/disable soft line wrapping at preferredLineLength
      • tabLength: Number of spaces within a tab (defaults to 2)
    • fuzzyFinder
      • ignoredNames: Files to ignore only in the fuzzy-finder
    • whitespace
      • ensureSingleTrailingNewline: Whether to reduce multiple newlines to one at the end of files
      • removeTrailingWhitespace: Enable/disable stripping of whitespace at the end of lines (defaults to true)
    • wrap-guide
      • columns: Array of hashes with a pattern and column key to match the path of the current editor to a column position.

    Language Specific Configuration Settings

    You can also set several configuration settings differently for different file types. For example, you may want Atom to soft wrap markdown files, have two-space tabs for ruby files, and four-space tabs for python files.

    There are several settings now scoped to an editor's language. Here is the current list:

    editor.autoIndent
    +editor.autoIndentOnPaste
    +editor.invisibles
    +editor.nonWordCharacters
    +editor.preferredLineLength
    +editor.scrollPastEnd
    +editor.showIndentGuide
    +editor.showInvisibles
    +editor.softWrap
    +editor.softWrapAtPreferredLineLength
    +editor.softWrapHangingIndent
    +editor.tabLength
    +
    Language-specific Settings in the Settings View

    You can edit these config settings in the Settings View on a per-language basis. Click on "Packages" tab in the navigation bar on the left, search for the language of your choice, select it, and edit away!

    Python-specific settings

    Language-specific Settings in your Config File

    You can also edit the config.cson directly. To open your configuration file via the Command Palette, press Cmd+Shift+PCtrl+Shift+P type open config, and press Enter.

    Global settings are under the * key, and each language can have its own top-level key. This key is the language's scope. Language-specific settings take precedence over anything set in the global section for that language only.

    '*': # all languages unless overridden
    +  'editor':
    +    'softWrap': false
    +    'tabLength': 8
    +
    +'.source.gfm': # markdown overrides
    +  'editor':
    +    'softWrap': true
    +
    +'.source.ruby': # ruby overrides
    +  'editor':
    +    'tabLength': 2
    +
    +'.source.python': # python overrides
    +  'editor':
    +    'tabLength': 4
    +
    Finding a Language's Scope Name

    In order to write these overrides effectively, you'll need to know the scope name for the language. We've already done this for finding a scope for writing a snippet in Snippet Format, but we can quickly cover it again.

    The scope name is shown in the settings view for each language. Click on "Packages" in the navigation on the left, search for the language of your choice, select it, and you should see the scope name under the language name heading:

    Finding a language grammar

    Another way to find the scope for a specific language is to open a file of its kind and press Alt+Cmd+Pchoose "Editor: Log Cursor Scope" in the Command Palette to show all scopes for the current position of the cursor. The scope mentioned top most is always the language for this kind of file, the scopes following are specific to the cursor position:

    Finding a language grammar with cursor scope

    These scopes can be especially useful to style the editor, since they can also be used as class names in your stylesheet.

    Customizing Language Recognition

    If you want Atom to always recognize certain file types as a specific grammar, you'll need to manually edit your config.cson file. You can open it using the Application: Open Your Config command from the Command Palette. For example, if you wanted to add the foo extension to the CoffeeScript language, you could add this to your configuration file under the *.core section:

    '*':
    +  core:
    +    customFileTypes:
    +      'source.coffee': [
    +        'foo'
    +      ]
    +

    In the example above, source.coffee is the language's scope name (see Finding a Language's Scope Name for more information) and foo is the file extension to match without the period. Adding a period to the beginning of either of these will not work.

    Controlling Where Customization is Stored to Simplify Your Workflow

    The CSON configuration files for Atom are stored on disk on your machine. The location for this storage is customizable. The default is to use the home directory of the user executing the application. The Atom Home directory will, by default, be called .atom and will be located in the root of the home directory of the user.

    Custom home location with an environment variable

    An environment variable can be used to make Atom use a different location. This can be useful for several reasons. One of these may be that multiple user accounts on a machine want to use the same Atom Home. The environment variable used to specify an alternate location is called ATOM_HOME. If this environment variable exists, the location specified will be used to load and store Atom settings.

    Taking your customization with you with Portable Mode

    In addition to using the ATOM_HOME environment variable, Atom can also be set to use "Portable Mode".

    Portable Mode is most useful for taking Atom with you, with all your custom setting and packages, from machine to machine. This may take the form of keeping Atom on a USB drive or a cloud storage platform that syncs folders to different machines, like Dropbox. Atom is in Portable Mode when there is a directory named .atom sibling to the directory in which the atom executable file lives. For example, the installed Atom directory can be placed into a Dropbox folder next to a .atom folder.

    Portable mode directory structure

    With such a setup, Atom will use the same Home directory with the same settings for any machine with this directory syncronized/plugged in.

    Moving to Portable Mode

    Atom provides a command-line parameter option for setting Portable Mode.

    $ atom --portable
    +

    Executing atom with the --portable option will take the .atom directory you have in the default location (~/.atom) and copy the relevant contents for your configuration to a new home directory in the Portable Mode location. This enables easily moving from the default location to a portable operation without losing the customization you have already set up.

    Summary

    At this point you should be something of an Atom master user. You should be able to navigate and manipulate your text and files like a wizard. You should also be able to customize Atom backwards and forwards to make it look and act just how you want it to.

    In the next chapter, we're going to kick it up a notch: we'll take a look at changing and adding new functionality to the core of Atom itself. We're going to start creating packages for Atom. If you can dream it, you can build it.

    `,52);function ho(uo,mo){const o=p("ExternalLinkIcon"),d=p("RouterLink"),c=p("Tabs");return be(),ke("div",null,[ve,e("p",null,[t("First we'll start with the Atom package system. As we mentioned previously, Atom itself is a very basic core of functionality that ships with a number of useful packages that add new features like the "),e("a",we,[t("Tree View"),a(o)]),t(" and the "),e("a",_e,[t("Settings View"),a(o)]),t(".")]),e("p",null,[t("In fact, there are more than 80 packages that comprise all of the functionality that is available in Atom by default. For example, the "),e("a",xe,[t("Welcome screen"),a(o)]),t(" that you see when you first start Atom, the "),e("a",Ce,[t("spell checker"),a(o)]),t(", the "),e("a",Ae,[t("themes"),a(o)]),t(" and the "),e("a",Se,[t("Fuzzy Finder"),a(o)]),t(" are all packages that are separately maintained and all use the same APIs that you have access to, as we'll see in great detail in "),a(d,{to:"/hacking-atom/"},{default:s(()=>[t("Hacking Atom")]),_:1}),t(".")]),qe,e("p",null,[t('Clicking on "Install" will install the theme and make it available in the Theme dropdowns as we saw in '),a(d,{to:"/getting-started/sections/atom-basics/#changing-the-theme"},{default:s(()=>[t("Changing the Theme")]),_:1}),t(".")]),Te,e("p",null,[t("For example "),Ie,t(" installs the "),Pe,t(" release of the "),e("a",Fe,[t("Emmet"),a(o)]),t(" package.")]),Re,a(c,{id:"124",data:[{title:"Linux"},{title:"Mac"},{title:"Windows"}],"tab-id":"moving-in-atom"},{tab0:s(({title:i,value:l,isActive:r})=>[Le,Ge]),tab1:s(({title:i,value:l,isActive:r})=>[He,Oe]),tab2:s(({title:i,value:l,isActive:r})=>[Me,je]),_:1}),ze,e("p",null,[t("Atom also has a few movement and selection commands that don't have keybindings by default. You can access these commands from the "),a(d,{to:"/getting-started/sections/atom-basics/#command-palette"},{default:s(()=>[t("Command Palette")]),_:1}),t(", but if you find yourself using commands that don't have a keybinding often, have no fear! You can easily add an entry to your "),Ee,t(" to create a key combination. You can open "),De,t(" file in an editor from the "),Ye,$e,We,t(" menu.")]),Ue,a(c,{id:"197",data:[{title:"Linux"},{title:"Mac"},{title:"Windows"}],"tab-id":"moving-in-atom"},{tab0:s(({title:i,value:l,isActive:r})=>[Be]),tab1:s(({title:i,value:l,isActive:r})=>[Ne]),tab2:s(({title:i,value:l,isActive:r})=>[Ve]),_:1}),e("p",null,[t("This will bind the command "),Je,t(" to "),Ke,Ze,t(". For more information on customizing your keybindings, see "),a(d,{to:"/using-atom/sections/basic-customization/#customizing-keybindings"},{default:s(()=>[t("Customizing Keybindings")]),_:1}),t(".")]),Qe,a(c,{id:"214",data:[{title:"Linux"},{title:"Mac"},{title:"Windows"}],"tab-id":"moving-in-atom"},{tab0:s(({title:i,value:l,isActive:r})=>[Xe]),tab1:s(({title:i,value:l,isActive:r})=>[et]),tab2:s(({title:i,value:l,isActive:r})=>[tt]),_:1}),at,e("p",null,[t("You can generate a "),ot,t(" file by using the "),e("a",nt,[t("ctags utility"),a(o)]),t(". Once it is installed, you can use it to generate a "),st,t(" file by running a command to generate it. See the "),e("a",it,[t("ctags documentation"),a(o)]),t(" for details.")]),a(c,{id:"237",data:[{title:"Linux"},{title:"Mac"},{title:"Windows"}],"tab-id":"moving-in-atom"},{tab0:s(({title:i,value:l,isActive:r})=>[lt]),tab1:s(({title:i,value:l,isActive:r})=>[rt]),tab2:s(({title:i,value:l,isActive:r})=>[ct]),_:1}),e("p",null,[t("You can customize how tags are generated by creating your own "),dt,t(" file in your home directory, "),pt,ht,t(". An example can be found "),e("a",ut,[t("here"),a(o)]),t(".")]),e("p",null,[t("The symbols navigation functionality is implemented in the "),e("a",mt,[t("symbols-view"),a(o)]),t(" package.")]),ft,t(' ![View and filter bookmarks](@images/atom/bookmarks.png "View and filter bookmarks") '),e("p",null,[t("The bookmarks functionality is implemented in the "),e("a",gt,[t("bookmarks"),a(o)]),t(" package.")]),bt,a(c,{id:"357",data:[{title:"Mac"}],"tab-id":"atom-selections"},{tab0:s(({title:i,value:l,isActive:r})=>[kt]),_:1}),yt,a(c,{id:"409",data:[{title:"Mac"}],"tab-id":"editing-and-deleting"},{tab0:s(({title:i,value:l,isActive:r})=>[vt]),_:1}),wt,a(c,{id:"446",data:[{title:"Mac"}],"tab-id":"editing-and-deleting"},{tab0:s(({title:i,value:l,isActive:r})=>[_t]),_:1}),xt,a(c,{id:"495",data:[{title:"Mac"}],"tab-id":"editing-and-deleting"},{tab0:s(({title:i,value:l,isActive:r})=>[Ct]),_:1}),At,e("p",null,[t("The whitespace commands are implemented in the "),e("a",St,[t("atom/whitespace"),a(o)]),t(" package. The settings for the whitespace commands are managed on the page for the "),qt,t(" package.")]),Tt,e("p",null,[t("The brackets functionality is implemented in the "),e("a",It,[t("bracket-matcher"),a(o)]),t(" package. Like all of these packages, to change defaults related to bracket handling, or to disable it entirely, you can navigate to this package in the Settings view.")]),Pt,e("p",null,[t("The encoding selector is implemented in the "),e("a",Ft,[t("encoding-selector"),a(o)]),t(" package.")]),Rt,e("div",Lt,[Gt,Ht,e("p",null,[t("When doing a regular expression search, the replacement syntax to refer back to search groups is $1, $2, \u2026 $&. Refer to JavaScript's "),e("a",Ot,[t("guide to regular expressions"),a(o)]),t(" to learn more about regular expression syntax you can use in Atom.")])]),Mt,jt,zt,e("p",null,[t("You can limit a search to a subset of the files in your project by entering a "),e("a",Et,[t("glob pattern"),a(o)]),t(' into the "File/Directory pattern" text box. For example, the pattern '),Dt,t(" would restrict the search to JavaScript files in the "),Yt,t(' directory. The "globstar" pattern ('),$t,t(") can be used to match arbitrarily many subdirectories. For example, "),Wt,t(" will match "),Ut,t(", "),Bt,t(", etc. You can enter multiple glob patterns separated by commas, which is useful for searching in multiple file types or subdirectories.")]),Nt,Vt,e("p",null,[t("The Find and Replace functionality is implemented in the "),e("a",Jt,[t("find-and-replace"),a(o)]),t(" package and uses the "),e("a",Kt,[t("scandal"),a(o)]),t(" Node module to do the actual searching.")]),Zt,e("div",Qt,[Xt,e("p",null,[t("Snippet keys, unlike CSS selectors, can only be repeated once per level. If there are duplicate keys at the same level, then only the last one will be read. See "),a(d,{to:"/using-atom/sections/basic-customization/#configuring-with-cson"},{default:s(()=>[t("Configuring with CSON")]),_:1}),t(" for more information.")])]),ea,e("p",null,[t("You can also use "),e("a",ta,[t("CoffeeScript multi-line syntax"),a(o)]),t(" using "),aa,t(" for larger templates:")]),oa,e("p",null,[t("Again, see "),a(d,{to:"/using-atom/sections/basic-customization/#configuring-with-cson"},{default:s(()=>[t("Configuring with CSON")]),_:1}),t(" for more information on CSON key structure and non-repeatability.")]),na,e("p",null,[t("The snippets functionality is implemented in the "),e("a",sa,[t("snippets"),a(o)]),t(" package.")]),e("p",null,[t("For more examples, see the snippets in the "),e("a",ia,[t("language-html"),a(o)]),t(" and "),e("a",la,[t("language-javascript"),a(o)]),t(" packages.")]),ra,e("p",null,[t("The Autocomplete functionality is implemented in the "),e("a",ca,[t("autocomplete-plus"),a(o)]),t(" package.")]),da,e("div",pa,[ha,e("p",null,[t("If you don't like using tabs, you don't have to. You can disable the "),e("a",ua,[t("tabs package"),a(o)]),t(" and each pane will still support multiple pane items. You just won't have tabs to use to click between them.")])]),ma,e("p",null,[t('The "grammar" of a file is what language Atom has associated with that file. Types of grammars would include "Java" or "GitHub-Flavored Markdown". We looked at this a bit when we created some snippets in '),a(d,{to:"/using-atom/sections/snippets/"},{default:s(()=>[t("Snippets")]),_:1}),t(".")]),fa,ga,ba,ka,e("p",null,[t("The Grammar Selector functionality is implemented in the "),e("a",ya,[t("grammar-selector"),a(o)]),t(" package.")]),va,e("p",null,[t("Version control is an important aspect of any project and Atom comes with basic "),e("a",wa,[t("Git"),a(o)]),t(" and "),e("a",_a,[t("GitHub"),a(o)]),t(" integration built in.")]),xa,e("p",null,[t("Atom ships with the "),e("a",Ca,[t("fuzzy-finder package"),a(o)]),t(" which provides "),Aa,Sa,t(" to quickly open files in the project and "),qa,Ta,t(" to jump to any open editor. The package also provides "),Ia,Pa,t(" which displays a list of all the untracked and modified files in the project. These will be the same files that you would see on the command line if you ran "),Fa,t(".")]),Ra,La,Ga,e("p",null,[t("Atom can be used as your Git commit editor and ships with the "),e("a",Ha,[t("language-git package"),a(o)]),t(" which adds syntax highlighting to edited commit, merge, and rebase messages.")]),Oa,e("p",null,[t("The "),e("a",Ma,[t("language-git"),a(o)]),t(" package will help remind you to be brief by colorizing the first lines of commit messages when they're longer than 50 or 65 characters.")]),ja,e("p",null,[t("The "),e("a",za,[t("status-bar"),a(o)]),t(" package that ships with Atom includes several Git decorations that display on the right side of the status bar:")]),Ea,Da,Ya,e("p",null,[t("The included "),e("a",$a,[t("git-diff"),a(o)]),t(" package colorizes the gutter next to lines that have been added, edited, or removed.")]),Wa,e("p",null,[t("Learn more about "),e("a",Ua,[t("merge vs. rebase"),a(o)]),t(".")]),Ba,e("p",null,[t("Though it is probably most common to use Atom to write software code, Atom can also be used to write prose quite effectively. Most often this is done in some sort of markup language such as Asciidoc or "),e("a",Na,[t("Markdown"),a(o)]),t(" (in which this manual is written). Here we'll quickly cover a few of the tools Atom provides for helping you write prose.")]),Va,e("p",null,[t("The spell checking is implemented in the "),e("a",Ja,[t("spell-check"),a(o)]),t(" package.")]),Ka,e("p",null,[t("Markdown preview is implemented in the "),e("a",Za,[t("markdown-preview"),a(o)]),t(" package.")]),Qa,e("p",null,[t("All of Atom's config files (with the exception of your "),Xa,t(" and your "),eo,t(") are written in CSON, short for "),e("a",to,[t("CoffeeScript Object Notation"),a(o)]),t(". Just like its namesake JSON, "),e("a",ao,[t("JavaScript Object Notation"),a(o)]),t(", CSON is a text format for storing structured data in the form of simple objects made up of key-value pairs.")]),oo,a(c,{id:"1512",data:[{title:"Mac"}],"tab-id":"basic-customization"},{tab0:s(({title:i,value:l,isActive:r})=>[no]),_:1}),so,e("div",io,[lo,e("p",null,[t("If you are unfamiliar with Less, it is a basic CSS preprocessor that makes some things in CSS a bit easier. You can learn more about it at "),e("a",ro,[t("lesscss.org"),a(o)]),t(".")]),co]),po])}const Fo=ge(ye,[["render",ho],["__file","index.html.vue"]]);export{Fo as default}; diff --git a/assets/index.html.f8b7ceda.js b/assets/index.html.f8b7ceda.js new file mode 100644 index 0000000000..fe016d4581 --- /dev/null +++ b/assets/index.html.f8b7ceda.js @@ -0,0 +1 @@ +import{_ as e,o as c,c as t}from"./app.0e1565ce.js";const n={};function _(o,r){return c(),t("div")}const a=e(n,[["render",_],["__file","index.html.vue"]]);export{a as default}; diff --git a/assets/index.html.fa010113.js b/assets/index.html.fa010113.js new file mode 100644 index 0000000000..736426b5bd --- /dev/null +++ b/assets/index.html.fa010113.js @@ -0,0 +1 @@ +import{_ as t,o,c,a as e,b as s}from"./app.0e1565ce.js";const n={},a=e("div",{class:"custom-container warning"},[e("p",{class:"custom-container-title"},"Under Construction"),e("p",null,[s("This document is under construction, please check back soon for updates. Please see "),e("a",{href:"/docs/community"},"our socials"),s(" and feel free to ask for assistance or inquire as to the status of this document.")])],-1),r=[a];function i(d,l){return o(),c("div",null,r)}const _=t(n,[["render",i],["__file","index.html.vue"]]);export{_ as default}; diff --git a/assets/index.html.fb13f8c6.js b/assets/index.html.fb13f8c6.js new file mode 100644 index 0000000000..218d5c4b47 --- /dev/null +++ b/assets/index.html.fb13f8c6.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-6486a861","path":"/tag/regular/","title":"regular Tag","lang":"en-US","frontmatter":{"title":"regular Tag","blog":{"type":"category","name":"regular","key":"tag"},"layout":"Blog"},"excerpt":"","headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null}');export{e as data}; diff --git a/assets/index.html.fb3f4ca6.js b/assets/index.html.fb3f4ca6.js new file mode 100644 index 0000000000..be8c9472cd --- /dev/null +++ b/assets/index.html.fb3f4ca6.js @@ -0,0 +1,292 @@ +import{_ as d}from"./keybinding.964a4e0d.js";import{_ as u}from"./markup.30f112ce.js";import{_ as m,o as k,c as h,a as n,b as s,d as e,w as o,f as t,r as l}from"./app.0e1565ce.js";const v={},g=t(`

    STOP

    This is being kept for archival purposes only from the original Atom documentation. As this may no longer be relevant to Pulsar, use this at your own risk. Current Pulsar documentation is found at documentation home.

    Behind Atom

    Now that we've written a number of packages and themes, let's take minute to take a closer look at some of the ways that Atom works in greater depth. Here we'll go into more of a deep dive on individual internal APIs and systems of Atom, even looking at some Atom source to see how things are really getting done.

    Configuration API

    Reading Config Settings

    If you are writing a package that you want to make configurable, you'll need to read config settings via the atom.config global. You can read the current value of a namespaced config key with atom.config.get:

    // read a value with \`config.get\`
    +if (atom.config.get("editor.showInvisibles")) {
    +	this.showInvisibles();
    +}
    +

    Or you can subscribe via atom.config.observe to track changes from any view object.

    const {View} = require('space-pen')
    +
    +class MyView extends View {
    +  function attached() {
    +    this.fontSizeObserveSubscription =
    +      atom.config.observe('editor.fontSize', (newValue, {previous}) => {
    +        this.adjustFontSize(newValue)
    +      })
    +  }
    +
    +  function detached() {
    +    this.fontSizeObserveSubscription.dispose()
    +  }
    +}
    +

    The atom.config.observe method will call the given callback immediately with the current value for the specified key path, and it will also call it in the future whenever the value of that key path changes. If you only want to invoke the callback the next time the value changes, use atom.config.onDidChange instead.

    `,10),b={href:"https://atom.io/docs/api/latest/Disposable",target:"_blank",rel:"noopener noreferrer"},f=n("code",null,"Disposable",-1),y=n("code",null,"@fontSizeObserveSubscription",-1),w={href:"https://atom.io/docs/api/latest/CompositeDisposable",target:"_blank",rel:"noopener noreferrer"},_=n("code",null,"CompositeDisposable",-1),x=t(`

    Writing Config Settings

    The atom.config database is populated on startup from ~/.atom/config.cson%USERPROFILE%\\.atom\\config.cson, but you can programmatically write to it with atom.config.set:

    // basic key update
    +atom.config.set("core.showInvisibles", true);
    +
    `,3),j={href:"https://atom.io/docs/api/latest/Config",target:"_blank",rel:"noopener noreferrer"},q=n("h3",{id:"keymaps-in-depth",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#keymaps-in-depth","aria-hidden":"true"},"#"),s(" Keymaps In-Depth")],-1),S=n("h4",{id:"structure-of-a-keymap-file",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#structure-of-a-keymap-file","aria-hidden":"true"},"#"),s(" Structure of a Keymap File")],-1),z=n("p",null,[s("Keymap files are encoded as JSON or CSON files containing nested hashes. They work much like style sheets, but instead of applying style properties to elements matching the selector, they specify the meaning of keystrokes on elements matching the selector. Here is an example of some bindings that apply when keystrokes pass through "),n("code",null,"atom-text-editor"),s(" elements:")],-1),A=n("div",{class:"language-coffee ext-coffee line-numbers-mode"},[n("pre",{class:"language-coffee"},[n("code",null,[n("span",{class:"token string-property property"},"'atom-text-editor'"),n("span",{class:"token operator"},":"),s(` + `),n("span",{class:"token string-property property"},"'ctrl-left'"),n("span",{class:"token operator"},":"),s(),n("span",{class:"token string"},"'editor:move-to-beginning-of-word'"),s(` + `),n("span",{class:"token string-property property"},"'ctrl-right'"),n("span",{class:"token operator"},":"),s(),n("span",{class:"token string"},"'editor:move-to-end-of-word'"),s(` + `),n("span",{class:"token string-property property"},"'ctrl-shift-left'"),n("span",{class:"token operator"},":"),s(),n("span",{class:"token string"},"'editor:select-to-beginning-of-word'"),s(` + `),n("span",{class:"token string-property property"},"'ctrl-shift-right'"),n("span",{class:"token operator"},":"),s(),n("span",{class:"token string"},"'editor:select-to-end-of-word'"),s(` + `),n("span",{class:"token string-property property"},"'ctrl-backspace'"),n("span",{class:"token operator"},":"),s(),n("span",{class:"token string"},"'editor:delete-to-beginning-of-word'"),s(` + `),n("span",{class:"token string-property property"},"'ctrl-delete'"),n("span",{class:"token operator"},":"),s(),n("span",{class:"token string"},"'editor:delete-to-end-of-word'"),s(` + +`),n("span",{class:"token string-property property"},"'atom-text-editor:not([mini])'"),n("span",{class:"token operator"},":"),s(` + `),n("span",{class:"token string-property property"},"'ctrl-alt-['"),n("span",{class:"token operator"},":"),s(),n("span",{class:"token string"},"'editor:fold-current-row'"),s(` + `),n("span",{class:"token string-property property"},"'ctrl-alt-]'"),n("span",{class:"token operator"},":"),s(),n("span",{class:"token string"},"'editor:unfold-current-row'"),s(` +`)])]),n("div",{class:"line-numbers","aria-hidden":"true"},[n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"})])],-1),O=n("div",{class:"language-coffee ext-coffee line-numbers-mode"},[n("pre",{class:"language-coffee"},[n("code",null,[n("span",{class:"token string-property property"},"'atom-text-editor'"),n("span",{class:"token operator"},":"),s(` + `),n("span",{class:"token string-property property"},"'cmd-delete'"),n("span",{class:"token operator"},":"),s(),n("span",{class:"token string"},"'editor:delete-to-beginning-of-line'"),s(` + `),n("span",{class:"token string-property property"},"'alt-backspace'"),n("span",{class:"token operator"},":"),s(),n("span",{class:"token string"},"'editor:delete-to-beginning-of-word'"),s(` + `),n("span",{class:"token string-property property"},"'ctrl-A'"),n("span",{class:"token operator"},":"),s(),n("span",{class:"token string"},"'editor:select-to-first-character-of-line'"),s(` + `),n("span",{class:"token string-property property"},"'ctrl-shift-e'"),n("span",{class:"token operator"},":"),s(),n("span",{class:"token string"},"'editor:select-to-end-of-line'"),s(` + `),n("span",{class:"token string-property property"},"'cmd-left'"),n("span",{class:"token operator"},":"),s(),n("span",{class:"token string"},"'editor:move-to-first-character-of-line'"),s(` + +`),n("span",{class:"token string-property property"},"'atom-text-editor:not([mini])'"),n("span",{class:"token operator"},":"),s(` + `),n("span",{class:"token string-property property"},"'cmd-alt-['"),n("span",{class:"token operator"},":"),s(),n("span",{class:"token string"},"'editor:fold-current-row'"),s(` + `),n("span",{class:"token string-property property"},"'cmd-alt-]'"),n("span",{class:"token operator"},":"),s(),n("span",{class:"token string"},"'editor:unfold-current-row'"),s(` +`)])]),n("div",{class:"line-numbers","aria-hidden":"true"},[n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"})])],-1),C=n("div",{class:"language-coffee ext-coffee line-numbers-mode"},[n("pre",{class:"language-coffee"},[n("code",null,[n("span",{class:"token string-property property"},"'atom-text-editor'"),n("span",{class:"token operator"},":"),s(` + `),n("span",{class:"token string-property property"},"'ctrl-left'"),n("span",{class:"token operator"},":"),s(),n("span",{class:"token string"},"'editor:move-to-beginning-of-word'"),s(` + `),n("span",{class:"token string-property property"},"'ctrl-right'"),n("span",{class:"token operator"},":"),s(),n("span",{class:"token string"},"'editor:move-to-end-of-word'"),s(` + `),n("span",{class:"token string-property property"},"'ctrl-shift-left'"),n("span",{class:"token operator"},":"),s(),n("span",{class:"token string"},"'editor:select-to-beginning-of-word'"),s(` + `),n("span",{class:"token string-property property"},"'ctrl-shift-right'"),n("span",{class:"token operator"},":"),s(),n("span",{class:"token string"},"'editor:select-to-end-of-word'"),s(` + `),n("span",{class:"token string-property property"},"'ctrl-backspace'"),n("span",{class:"token operator"},":"),s(),n("span",{class:"token string"},"'editor:delete-to-beginning-of-word'"),s(` + `),n("span",{class:"token string-property property"},"'ctrl-delete'"),n("span",{class:"token operator"},":"),s(),n("span",{class:"token string"},"'editor:delete-to-end-of-word'"),s(` + +`),n("span",{class:"token string-property property"},"'atom-text-editor:not([mini])'"),n("span",{class:"token operator"},":"),s(` + `),n("span",{class:"token string-property property"},"'ctrl-alt-['"),n("span",{class:"token operator"},":"),s(),n("span",{class:"token string"},"'editor:fold-current-row'"),s(` + `),n("span",{class:"token string-property property"},"'ctrl-alt-]'"),n("span",{class:"token operator"},":"),s(),n("span",{class:"token string"},"'editor:unfold-current-row'"),s(` +`)])]),n("div",{class:"line-numbers","aria-hidden":"true"},[n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"})])],-1),T=t(`

    Beneath the first selector are several keybindings, mapping specific key combinations to commands. When an element with the atom-text-editor class is focused and Alt+BackspaceCtrl+Backspace is pressed, a custom DOM event called editor:delete-to-beginning-of-word is emitted on the atom-text-editor element.

    The second selector group also targets editors, but only if they don't have the mini attribute. In this example, the commands for code folding don't really make sense on mini-editors, so the selector restricts them to regular editors.

    Key Combinations

    Key combinations express one or more keys combined with optional modifier keys. For example: ctrl-w v, or cmd-shift-up. A key combination is composed of the following symbols, separated by a -. A key sequence can be expressed as key combinations separated by spaces.

    TypeExamples
    Character literalsa 4 $
    Modifier keyscmd ctrl alt shift
    Special keysenter escape backspace delete tab home end pageup pagedown left right up down space
    Commands

    Commands are custom DOM events that are triggered when a key combination or sequence matches a binding. This allows user interface code to listen for named commands without specifying the specific keybinding that triggers it. For example, the following code creates a command to insert the current date in an editor:

    atom.commands.add("atom-text-editor", {
    +	"user:insert-date": function (event) {
    +		const editor = this.getModel();
    +		return editor.insertText(new Date().toLocaleString());
    +	},
    +});
    +

    atom.commands refers to the global CommandRegistry instance where all commands are set and consequently picked up by the command palette.

    When you are looking to bind new keys, it is often useful to use the Command Palette (Cmd+Shift+PCtrl+Shift+P) to discover what commands are being listened for in a given focus context. Commands are "humanized" following a simple algorithm, so a command like editor:fold-current-row would appear as "Editor: Fold Current Row".

    "Composed" Commands

    A common question is, "How do I make a single keybinding execute two or more commands?" There isn't any direct support for this in Atom, but it can be achieved by creating a custom command that performs the multiple actions you desire and then creating a keybinding for that command. For example, let's say I want to create a "composed" command that performs a Select Line followed by Cut. You could add the following to your init.coffee:

    atom.commands.add("atom-text-editor", "custom:cut-line", function () {
    +	const editor = this.getModel();
    +	editor.selectLinesContainingCursors();
    +	editor.cutSelectedText();
    +});
    +

    Then let's say we want to map this custom command to alt-ctrl-z, you could add the following to your keymap:

    'atom-text-editor':
    +  'alt-ctrl-z': 'custom:cut-line'
    +
    Specificity and Cascade Order

    As is the case with CSS applying styles, when multiple bindings match for a single element, the conflict is resolved by choosing the most specific selector. If two matching selectors have the same specificity, the binding for the selector appearing later in the cascade takes precedence.

    Currently, there's no way to specify selector ordering within a single keymap, because JSON objects do not preserve order. We handle cases where selector ordering is critical by breaking the keymap into separate files, such as snippets-1.cson and snippets-2.cson.

    Selectors and Custom Packages

    If a keybinding should only apply to a specific grammar, you can limit bindings to that grammar using the data-grammar attribute on the atom-text-editor element:

    "atom-text-editor[data-grammar='source example']":
    +  'ctrl-.': 'custom:custom-command'
    +

    While selectors can be applied to the entire editor by what grammar is associated with it, they cannot be applied to scopes defined within the grammar or to sub-elements of atom-text-editor.

    Removing Bindings

    When the keymap system encounters a binding with the unset! directive as its command, it will treat the current element as if it had no key bindings matching the current keystroke sequence and continue searching from its parent. For example, the following code removes the keybinding for a in the Tree View, which is normally used to trigger the tree-view:add-file command:

    '.tree-view':
    +  'a': 'unset!'
    +

    Keybinding Resolver

    But if some element above the Tree View had a keybinding for a, that keybinding would still execute even when the focus is inside the Tree View.

    When the keymap system encounters a binding with the abort! directive as its command, it will stop searching for a keybinding. For example, the following code removes the keybinding for Cmd+OCtrl+O when the selection is inside an editor pane:

    ::: codetabs#keymaps-in-depth

    `,31),E=t(`
    'atom-text-editor':
    +  'ctrl-o': 'abort!'
    +

    :::

    But if you click inside the Tree View and press Cmd+OCtrl+O, it will work.

    Forcing Chromium's Native Keystroke Handling

    If you want to force the native browser behavior for a given keystroke, use the native! directive as the command of a binding. This can be useful to enable the correct behavior in native input elements. If you apply the .native-key-bindings class to an element, all the keystrokes typically handled by the browser will be assigned the native! directive.

    Tips

    Tip: Components and input elements may not correctly handle backspace and arrow keys without forcing this behavior. If your backspace isn't working correctly inside of a component, add either the directive or the .native-key-bindings class.

    Overloading Key Bindings

    Occasionally, it makes sense to layer multiple actions on top of the same key binding. An example of this is the snippets package. Snippets are inserted by typing a snippet prefix such as for and then pressing Tab. Every time Tab is pressed, we want to execute code attempting to expand a snippet if one exists for the text preceding the cursor. If a snippet doesn't exist, we want Tab to actually insert whitespace.

    To achieve this, the snippets package makes use of the .abortKeyBinding() method on the event object representing the snippets:expand command.

    // pseudo-code
    +editor.command("snippets:expand", (e) => {
    +	if (this.cursorFollowsValidPrefix()) {
    +		this.expandSnippet();
    +	} else {
    +		e.abortKeyBinding();
    +	}
    +});
    +

    When the event handler observes that the cursor does not follow a valid prefix, it calls e.abortKeyBinding(), telling the keymap system to continue searching for another matching binding.

    Step-by-Step: How Keydown Events are Mapped to Commands

    • A keydown event occurs on a focused element.
    • Starting at the focused element, the keymap walks upward towards the root of the document, searching for the most specific CSS selector that matches the current DOM element and also contains a keystroke pattern matching the keydown event.
    • When a matching keystroke pattern is found, the search is terminated and the pattern's corresponding command is triggered on the current element.
    • If .abortKeyBinding() is called on the triggered event object, the search is resumed, triggering a binding on the next-most-specific CSS selector for the same element or continuing upward to parent elements.
    • If no bindings are found, the event is handled by Chromium normally.

    Overriding Atom's Keyboard Layout Recognition

    `,14),I={href:"https://blog.atom.io/2016/10/17/the-wonderful-world-of-keyboards.html",target:"_blank",rel:"noopener noreferrer"},M=t(`

    You can add the following to your init.coffee to send Ctrl+@ when you press Ctrl+Alt+G:

    atom.keymaps.addKeystrokeResolver ({event}) ->
    +  if event.code is 'KeyG' and event.altKey and event.ctrlKey and event.type isnt 'keyup'
    +    return 'ctrl-@'
    +

    Or if you've converted your init script to JavaScript:

    atom.keymaps.addKeystrokeResolver(({ event }) => {
    +	if (
    +		event.code === "KeyG" &&
    +		event.altKey &&
    +		event.ctrlKey &&
    +		event.type !== "keyup"
    +	) {
    +		return "ctrl-@";
    +	}
    +});
    +
    `,4),D=n("code",null,"event",-1),N={href:"https://flight-manual.atom.io/hacking-atom/sections/debugging/#check-for-errors-in-the-developer-tools",target:"_blank",rel:"noopener noreferrer"},P=t(`
    document.addEventListener("keydown", (e) => console.log(e), true);
    +

    This will print every keypress event in Atom to the console so you can inspect KeyboardEvent.key and KeyboardEvent.code.

    Scoped Settings, Scopes and Scope Descriptors

    Atom supports language-specific settings. You can soft wrap only Markdown files, or set the tab length to 4 in Python files.

    Language-specific settings are a subset of something more general we call "scoped settings". Scoped settings allow targeting down to a specific syntax token type. For example, you could conceivably set a setting to target only Ruby comments, only code inside Markdown files, or even only JavaScript function names.

    Scope Names in Syntax Tokens

    Each token in the editor has a collection of scope names. For example, the aforementioned JavaScript function name might have the scope names function and name. An open paren might have the scope names punctuation, parameters, begin.

    Scope names work just like CSS classes. In fact, in the editor, scope names are attached to a token's DOM node as CSS classes.

    Take this piece of JavaScript:

    function functionName() {
    +	console.log("Log it out");
    +}
    +

    In the dev tools, the first line's markup looks like this.

    Markup

    All the class names on the spans are scope names. Any scope name can be used to target a setting's value.

    Scope Selectors

    Scope selectors allow you to target specific tokens just like a CSS selector targets specific nodes in the DOM. Some examples:

    '.source.js' # selects all javascript tokens
    +'.source.js .function.name' # selects all javascript function names
    +'.function.name' # selects all function names in any language
    +
    `,16),L={href:"https://atom.io/docs/api/latest/Config#instance-set",target:"_blank",rel:"noopener noreferrer"},R=n("code",null,"Config::set",-1),V=n("code",null,"scopeSelector",-1),$=n("code",null,"scopeSelector",-1),W=t(`
    atom.config.set("my-package.my-setting", "special value", {
    +	scopeSelector: ".source.js .function.name",
    +});
    +

    Scope Descriptors

    `,2),K={href:"https://atom.io/docs/api/latest/ScopeDescriptor",target:"_blank",rel:"noopener noreferrer"},F=n("code",null,"Array",-1),Y=n("code",null,"String",-1),H=n("em",null,"all",-1),B=t(`

    In our JavaScript example above, a scope descriptor for the function name token would be:

    ["source.js", "meta.function.js", "entity.name.function.js"];
    +
    `,2),U={href:"https://atom.io/docs/api/latest/Config#instance-get",target:"_blank",rel:"noopener noreferrer"},J=n("code",null,"Config::get",-1),G=n("code",null,"scopeDescriptor",-1),Q=t(`
    const scopeDescriptor = [
    +	"source.js",
    +	"meta.function.js",
    +	"entity.name.function.js",
    +];
    +const value = atom.config.get("my-package.my-setting", {
    +	scope: scopeDescriptor,
    +});
    +

    But, you do not need to generate scope descriptors by hand. There are a couple methods available to get the scope descriptor from the editor:

    `,2),X={href:"https://atom.io/docs/api/latest/TextEditor#instance-getRootScopeDescriptor",target:"_blank",rel:"noopener noreferrer"},Z=n("code",null,"Editor::getRootScopeDescriptor",-1),nn=n("code",null,'[".source.js"]',-1),sn={href:"https://atom.io/docs/api/latest/TextEditor#instance-scopeDescriptorForBufferPosition",target:"_blank",rel:"noopener noreferrer"},en=n("code",null,"Editor::scopeDescriptorForBufferPosition",-1),an={href:"https://atom.io/docs/api/latest/Cursor#instance-getScopeDescriptor",target:"_blank",rel:"noopener noreferrer"},tn=n("code",null,"Cursor::getScopeDescriptor",-1),on=n("code",null,'["source.js", "meta.function.js", "entity.name.function.js"]',-1),pn=t(`

    Let's revisit our example using these methods:

    const editor = atom.workspace.getActiveTextEditor();
    +const cursor = editor.getLastCursor();
    +const valueAtCursor = atom.config.get("my-package.my-setting", {
    +	scope: cursor.getScopeDescriptor(),
    +});
    +const valueForLanguage = atom.config.get("my-package.my-setting", {
    +	scope: editor.getRootScopeDescriptor(),
    +});
    +

    Serialization in Atom

    When a window is refreshed or restored from a previous session, the view and its associated objects are deserialized from a JSON representation that was stored during the window's previous shutdown. For your own views and objects to be compatible with refreshing, you'll need to make them play nicely with the serializing and deserializing.

    Package Serialization Hook

    Your package's main module can optionally include a serialize method, which will be called before your package is deactivated. You should return a JSON-serializable object, which will be handed back to you as an object argument to activate next time it is called. In the following example, the package keeps an instance of MyObject in the same state across refreshes.

    module.exports = {
    +	activate(state) {
    +		this.myObject = state
    +			? atom.deserializers.deserialize(state)
    +			: new MyObject("Hello");
    +	},
    +
    +	serialize() {
    +		return this.myObject.serialize();
    +	},
    +};
    +

    Serialization Methods

    class MyObject {
    +	constructor(data) {
    +		this.data = data;
    +	}
    +
    +	serialize() {
    +		return {
    +			deserializer: "MyObject",
    +			data: this.data,
    +		};
    +	}
    +}
    +
    serialize()

    Objects that you want to serialize should implement .serialize(). This method should return a serializable object, and it must contain a key named deserializer whose value is the name of a registered deserializer that can convert the rest of the data to an object. It's usually just the name of the class itself.

    Registering Deserializers

    The other side of the coin is deserializers, whose job is to convert a state object returned from a previous call to serialize back into a genuine object.

    deserializers in package.json

    The preferred way to register deserializers is via your package's package.json file:

    {
    +  "name": "wordcount",
    +  ...
    +  "deserializers": {
    +    "MyObject": "deserializeMyObject"
    +  }
    +}
    +

    Here, the key ("MyObject") is the name of the deserializer\u2014the same string used by the deserializer field in the object returned by your serialize() method. The value ("deserializeMyObject") is the name of a function in your main module that'll be passed the serialized data and will return a genuine object. For example, your main module might look like this:

    module.exports = {
    +	deserializeMyObject({ data }) {
    +		return new MyObject(data);
    +	},
    +};
    +

    Now you can call the global deserialize method with state returned from serialize, and your class's deserialize method will be selected automatically.

    atom.deserializers.add(klass)

    An alternative is to use the atom.deserializers.add method with your class in order to make it available to the deserialization system. Usually this is used in conjunction with a class-level deserialize method:

    class MyObject {
    +	static initClass() {
    +		atom.deserializers.add(this);
    +	}
    +
    +	static deserialize({ data }) {
    +		return new MyObject(data);
    +	}
    +
    +	constructor(data) {
    +		this.data = data;
    +	}
    +
    +	serialize() {
    +		return {
    +			deserializer: "MyObject",
    +			data: this.data,
    +		};
    +	}
    +}
    +
    +MyObject.initClass();
    +

    While this used to be the standard method of registering a deserializer, the package.json method is now preferred since it allows Atom to defer loading and executing your code until it's actually needed.

    Versioning

    class MyObject {
    +	static initClass() {
    +		atom.deserializers.add(this);
    +
    +		this.version = 2;
    +	}
    +
    +	static deserialize(state) {
    +		// ...
    +	}
    +
    +	serialize() {
    +		return {
    +			version: this.constructor.version,
    +			// ...
    +		};
    +	}
    +}
    +
    +MyObject.initClass();
    +

    Your serializable class can optionally have a class-level @version property and include a version key in its serialized state. When deserializing, Atom will only attempt to call deserialize if the two versions match, and otherwise return undefined. We plan on implementing a migration system in the future, but this at least protects you from improperly deserializing old state.

    Developing Node Modules

    Atom contains a number of packages that are Node modules instead of Atom packages. If you want to make changes to the Node modules, for instance atom-keymap, you have to link them into the development environment differently than you would a normal Atom package.

    Linking a Node Module Into Your Atom Dev Environment

    Here are the steps to run a local version of a Node module within Atom. We're using atom-keymap as an example:

    ::: tab#developing-node-modules

    `,31),cn=t(``,2),ln=t(`
    $ git clone https://github.com/atom/atom-keymap.git
    +$ cd atom-keymap
    +$ npm install
    +$ npm link
    +$ cd <em>WHERE YOU CLONED ATOM</em>
    +$ npm link atom-keymap
    +
    +# This is the special step, it makes the Node module work with Atom's version of Node
    +$ apm rebuild
    +
    +# If you have cloned Atom in a different location than %USERPROFILE%\\github\\atom
    +# you need to set the following environment variable
    +$ setx ATOM_DEV_RESOURCE_PATH=<em>WHERE YOU CLONED ATOM</em>
    +
    +# Should work!
    +$ atom --dev .
    +

    :::

    After you get the Node module linked and working, every time you make a change to the Node module's code, you will have to exit Atom and do the following:

    $ cd <em>WHERE YOU CLONED THE NODE MODULE</em>
    +$ npm install
    +$ cd <em>WHERE YOU CLONED ATOM</em>
    +$ apm rebuild
    +$ atom --dev .
    +

    Interacting With Other Packages Via Services

    Atom packages can interact with each other through versioned APIs called services. To provide a service, in your package.json, specify one or more version numbers, each paired with the name of a method on your package's main module:

    {
    +	"providedServices": {
    +		"my-service": {
    +			"description": "Does a useful thing",
    +			"versions": {
    +				"1.2.3": "provideMyServiceV1",
    +				"2.3.4": "provideMyServiceV2"
    +			}
    +		}
    +	}
    +}
    +

    In your package's main module, implement the methods named above. These methods will be called any time a package is activated that consumes their corresponding service. They should return a value that implements the service's API.

    module.exports = {
    +	activate() {
    +		// ...
    +	},
    +
    +	provideMyServiceV1() {
    +		return adaptToLegacyAPI(myService);
    +	},
    +
    +	provideMyServiceV2() {
    +		return myService;
    +	},
    +};
    +
    `,9),rn={href:"https://docs.npmjs.com/cli/v6/using-npm/semver#ranges",target:"_blank",rel:"noopener noreferrer"},dn=n("em",null,"ranges",-1),un=t(`
    {
    +	"consumedServices": {
    +		"another-service": {
    +			"versions": {
    +				"^1.2.3": "consumeAnotherServiceV1",
    +				">=2.3.4 <2.5": "consumeAnotherServiceV2"
    +			}
    +		}
    +	}
    +}
    +

    These methods will be called any time a package is activated that provides their corresponding service. They will receive the service object as an argument. You will usually need to perform some kind of cleanup in the event that the package providing the service is deactivated. To do this, return a Disposable from your service-consuming method:

    const { Disposable } = require("atom");
    +
    +module.exports = {
    +	activate() {
    +		// ...
    +	},
    +
    +	consumeAnotherServiceV1(service) {
    +		useService(adaptServiceFromLegacyAPI(service));
    +		return new Disposable(() => stopUsingService(service));
    +	},
    +
    +	consumeAnotherServiceV2(service) {
    +		useService(service);
    +		return new Disposable(() => stopUsingService(service));
    +	},
    +};
    +

    Maintaining Your Packages

    While publishing is, by far, the most common action you will perform when working with the packages you provide, there are other things you may need to do.

    Publishing a Package Manually

    Warning

    Danger: \u{1F6A8} Publishing a package manually is not a recommended practice and is only for the advanced user who has published packages before. If you perform the steps wrong, you may be unable to publish the new version of your package and may have to completely unpublish your package in order to correct the faulty state. You have been warned.

    Some people prefer to control every aspect of the package publishing process. Normally, the apm tool manages certain details during publishing to keep things consistent and make everything work smoothly. If you're one of those people that prefers to do things manually, there are certain steps you'll have to take in order to make things work just as smoothly as if apm has taken care of things for you.

    `,8),mn={class:"custom-container note"},kn=n("p",{class:"custom-container-title"},"Note",-1),hn=n("strong",null,"Note:",-1),vn={href:"https://github.com",target:"_blank",rel:"noopener noreferrer"},gn=t('

    When you have completed the changes that you want to publish and are ready to start the publishing process, you must perform the following steps on the master branch:

    1. Update the version number in your package's package.json. The version number must match the regular expression: ^\\d+\\.\\d+\\.\\d+
    2. Commit the version number change
    3. Create a Git tag referencing the above commit. The tag must match the regular expression ^v\\d+\\.\\d+\\.\\d+ and the part after the v must match the full text of the version number in the package.json
    4. Execute git push --follow-tags
    5. Execute apm publish --tag tagname where tagname must match the name of the tag created in the above step

    Adding a Collaborator

    ',3),bn={href:"https://help.github.com/articles/adding-collaborators-to-a-personal-repository/",target:"_blank",rel:"noopener noreferrer"},fn=n("em",null,"Note:",-1),yn={href:"https://help.github.com/articles/creating-a-new-organization-account/",target:"_blank",rel:"noopener noreferrer"},wn={href:"https://help.github.com/articles/permission-levels-for-an-organization/",target:"_blank",rel:"noopener noreferrer"},_n=n("h4",{id:"transferring-ownership",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#transferring-ownership","aria-hidden":"true"},"#"),s(" Transferring Ownership")],-1),xn=n("div",{class:"custom-container danger"},[n("p",{class:"custom-container-title"},"Warning"),n("p",null,[n("strong",null,"Danger:"),s(" \u{1F6A8} This is a permanent change. There is no going back! \u{1F6A8}")])],-1),jn={href:"https://help.github.com/articles/transferring-a-repository/",target:"_blank",rel:"noopener noreferrer"},qn=n("code",null,"package.json",-1),Sn=t(`

    Unpublish Your Package

    If you no longer want to support your package and cannot find anyone to take it over, you can unpublish your package from https://atom.io. For example, if your package is named package-name then the command you would execute is:

    $ apm unpublish <em>package-name</em>
    +

    This will remove your package from the https://atom.io package registry. Anyone who has already downloaded a copy of your package will still have it and be able to use it, but it will no longer be available for installation by others.

    Unpublish a Specific Version

    If you mistakenly published a version of your package or perhaps you find a glaring bug or security hole, you may want to unpublish just that version of your package. For example, if your package is named package-name and the bad version of your package is v1.2.3 then the command you would execute is:

    $ apm unpublish <em>package-name@1.2.3</em>
    +

    This will remove just this particular version from the https://atom.io package registry.

    Rename Your Package

    If you need to rename your package for any reason, you can do so with one simple command \u2013 apm publish --rename changes the name field in your package's package.json, pushes a new commit and tag, and publishes your renamed package. Requests made to the previous name will be forwarded to the new name.

    $ apm publish --rename <em>new-package-name</em>
    +

    Tips

    Tip: Once a package name has been used, it cannot be re-used by another package even if the original package is unpublished.

    How Atom Uses Chromium Snapshots

    `,13),zn={href:"https://v8project.blogspot.it/2015/09/custom-startup-snapshots.html",target:"_blank",rel:"noopener noreferrer"},An={href:"https://github.com/atom/electron-link",target:"_blank",rel:"noopener noreferrer"},On=n("code",null,"require",-1),Cn={href:"https://github.com/v8/v8/wiki/Embedder%27s-Guide#contexts",target:"_blank",rel:"noopener noreferrer"},Tn={href:"https://github.com/atom/atom/blob/74ff9fdb91205b89673209caf1e2ceb373e9c59f/script/lib/generate-startup-snapshot.js#L19-L65",target:"_blank",rel:"noopener noreferrer"},En={href:"https://github.com/atom/atom/blob/74ff9fdb91205b89673209caf1e2ceb373e9c59f/script/lib/generate-startup-snapshot.js#L73-L78",target:"_blank",rel:"noopener noreferrer"},In={href:"https://github.com/atom/atom/blob/74ff9fdb91205b89673209caf1e2ceb373e9c59f/script/lib/generate-startup-snapshot.js#L80-L89",target:"_blank",rel:"noopener noreferrer"},Mn=n("h3",{id:"summary",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#summary","aria-hidden":"true"},"#"),s(" Summary")],-1),Dn=n("p",null,"You should now have a better understanding of some of the core Atom APIs and systems.",-1);function Nn(Pn,Ln){const a=l("ExternalLinkIcon"),r=l("Tabs");return k(),h("div",null,[g,n("p",null,[s("Subscription methods return "),n("a",b,[f,e(a)]),s(" objects that can be used to unsubscribe. Note in the example above how we save the subscription to the "),y,s(" instance variable and dispose of it when the view is detached. To group multiple subscriptions together, you can add them all to a "),n("a",w,[_,e(a)]),s(" that you dispose when the view is detached.")]),x,n("p",null,[s("If you're exposing package configuration via specific key paths, you'll want to associate them with a schema in your package's main module. Read more about schemas in the "),n("a",j,[s("Config API documentation"),e(a)]),s(".")]),q,S,z,e(r,{id:"53",data:[{title:"Linux"},{title:"Mac"},{title:"Windows"}],"tab-id":"keymaps-indepth"},{tab0:o(({title:i,value:p,isActive:c})=>[A]),tab1:o(({title:i,value:p,isActive:c})=>[O]),tab2:o(({title:i,value:p,isActive:c})=>[C]),_:1}),T,n("template",null,[E,n("p",null,[s("Sometimes the problem isn't mapping the command to a key combination, the problem is that Atom doesn't recognize properly what keys you're pressing. This is due to "),n("a",I,[s("some limitations in how Chromium reports keyboard events"),e(a)]),s(". But even this can be customized now.")]),M,n("p",null,[s("If you want to know the "),D,s(" for the keystroke you pressed you can paste the following script to your "),n("a",N,[s("developer tools console"),e(a)])]),P,n("p",null,[n("a",L,[R,e(a)]),s(" accepts a "),V,s(". If you'd like to set a setting for JavaScript function names, you can give it the JavaScript function name "),$,s(":")]),W,n("p",null,[s("A scope descriptor is an "),n("a",K,[s("Object"),e(a)]),s(" that wraps an "),F,s(" of "),Y,s("s. The Array describes a path from the root of the syntax tree to a token including "),H,s(" scope names for the entire path.")]),B,n("p",null,[n("a",U,[J,e(a)]),s(" accepts a "),G,s(". You can get the value for your setting scoped to JavaScript function names via:")]),Q,n("ul",null,[n("li",null,[n("a",X,[Z,e(a)]),s(" to get the language's descriptor. For example: "),nn]),n("li",null,[n("a",sn,[en,e(a)]),s(" to get the descriptor at a specific position in the buffer.")]),n("li",null,[n("a",an,[tn,e(a)]),s(" to get a cursor's descriptor based on position. eg. if the cursor were in the name of the method in our example it would return "),on])]),pn]),cn,n("template",null,[ln,n("p",null,[s("Similarly, to consume a service, specify one or more "),n("a",rn,[s("version "),dn,e(a)]),s(", each paired with the name of a method on the package's main module:")]),un,n("div",mn,[kn,n("p",null,[hn,s(" The apm tool will only publish and https://atom.io will only list packages that are hosted on "),n("a",vn,[s("GitHub"),e(a)]),s(", regardless of what process is used to publish them.")])]),gn,n("p",null,[s("Some packages get too big for one person. Sometimes priorities change and someone else wants to help out. You can let others help or create co-owners by "),n("a",bn,[s("adding them as a collaborator"),e(a)]),s(" on the GitHub repository for your package. "),fn,s(" Anyone that has push access to your repository will have the ability to publish new versions of the package that belongs to that repository.")]),n("p",null,[s("You can also have packages that are owned by a "),n("a",yn,[s("GitHub organization"),e(a)]),s(". Anyone who is a member of an organization's "),n("a",wn,[s("team"),e(a)]),s(" which has push access to the package's repository will be able to publish new versions of the package.")]),_n,xn,n("p",null,[s("If you want to hand off support of your package to someone else, you can do that by "),n("a",jn,[s("transferring the package's repository"),e(a)]),s(" to the new owner. Once you do that, they can publish a new version with the updated repository information in the "),qn,s(".")]),Sn,n("p",null,[s("In order to improve startup time, when Atom is built we create a "),n("a",zn,[s("V8 snapshot"),e(a)]),s(" in which we preload core services and packages. Then, at runtime, we finish loading Atom by supplying all the information we didn't have during the compilation phase (e.g. loading third party packages, custom style sheets, configuration, etc.).")]),n("p",null,[n("a",An,[s("electron-link"),e(a)]),s(" is the tool that powers snapshots, as it enables us to traverse the entire require graph (starting at the entry point) and replace all the forbidden "),On,s(" calls (e.g. require calls to native modules, node core modules or other modules that can't be accessed in the snapshot "),n("a",Cn,[s("V8 context"),e(a)]),s(") with a function that will be called at runtime. When adding new code to Atom, we always try to put it inside the snapshot by, for example, deferring the usage of DOM APIs or native node modules to a later moment in time when those facilities are available. If that is not possible, we will add the unsupported code paths to the "),n("a",Tn,[s("list of files that get excluded from the snapshot"),e(a)]),s(", ensuring we only exclude those ones that are not supported as opposed to skipping an entire Node module.")]),n("p",null,[s("The output of electron-link is a single script containing the code for all the modules reachable from the entry point, which we then "),n("a",En,[s("supply to mksnapshot"),e(a)]),s(" to generate a snapshot blob.")]),n("p",null,[s("The generated blob is finally "),n("a",In,[s("copied into the application bundle"),e(a)]),s(" and will be automatically loaded by Electron when running Atom.")]),Mn,Dn])])}const Wn=m(v,[["render",Nn],["__file","index.html.vue"]]);export{Wn as default}; diff --git a/assets/index.html.fbff6f41.js b/assets/index.html.fbff6f41.js new file mode 100644 index 0000000000..fe016d4581 --- /dev/null +++ b/assets/index.html.fbff6f41.js @@ -0,0 +1 @@ +import{_ as e,o as c,c as t}from"./app.0e1565ce.js";const n={};function _(o,r){return c(),t("div")}const a=e(n,[["render",_],["__file","index.html.vue"]]);export{a as default}; diff --git a/assets/info.7c7632fc.png b/assets/info.7c7632fc.png new file mode 100644 index 0000000000..c83b29ce95 Binary files /dev/null and b/assets/info.7c7632fc.png differ diff --git a/assets/installing-atom.html.4dd4d36f.js b/assets/installing-atom.html.4dd4d36f.js new file mode 100644 index 0000000000..636fecda44 --- /dev/null +++ b/assets/installing-atom.html.4dd4d36f.js @@ -0,0 +1 @@ +const t=JSON.parse('{"key":"v-d48e3644","path":"/docs/atom-archive/getting-started/sections/installing-atom.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"Installing Atom","slug":"installing-atom","link":"#installing-atom","children":[]}],"git":{"updatedTime":1664083288000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":2}]},"readingTime":{"minutes":5.22,"words":1567},"filePathRelative":"docs/atom-archive/getting-started/sections/installing-atom.md"}');export{t as data}; diff --git a/assets/installing-atom.html.501202c9.js b/assets/installing-atom.html.501202c9.js new file mode 100644 index 0000000000..01e4858f34 --- /dev/null +++ b/assets/installing-atom.html.501202c9.js @@ -0,0 +1,53 @@ +import{_ as u,a as h,b as p}from"./windows-downloads.e9b59e10.js";import{_ as b}from"./windows-system-settings.141561e2.js";import{_ as g,o as _,c as f,d as n,w as a,a as e,b as t,f as c,r}from"./app.0e1565ce.js";const y={},v=e("h3",{id:"installing-atom",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#installing-atom","aria-hidden":"true"},"#"),t(" Installing Atom")],-1),w=e("p",null,"To get started with Atom, we'll need to get it on your system. This section will go over installing Atom on your system, as well as the basics of how to build it from source.",-1),A=e("p",null,"Installing Atom should be fairly simple. Generally, you can go to https://atom.io and you should see a download button as shown here:",-1),x=e("p",null,[e("img",{src:u,alt:"Download buttons on https://atom.io",title:"Download buttons on https://atom.io"})],-1),k=e("p",null,[e("img",{src:h,alt:"Download buttons on https://atom.io",title:"Download buttons on https://atom.io"})],-1),S=e("p",null,[e("img",{src:p,alt:"Download buttons on https://atom.io",title:"Download buttons on https://atom.io"})],-1),T=e("p",null,"The button or buttons should be specific to your platform and the download package should be easily installable. However, let's go over them here in a bit of detail.",-1),$=e("h4",{id:"installing-atom-on-linux",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#installing-atom-on-linux","aria-hidden":"true"},"#"),t(" Installing Atom on Linux")],-1),I=e("p",null,"You can install Atom on Linux using your distribution's package manager by configuring it to use one of our official package repositories. This will also enable you to update Atom when new releases are published.",-1),E=e("h5",{id:"debian-and-ubuntu-deb-apt",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#debian-and-ubuntu-deb-apt","aria-hidden":"true"},"#"),t(" Debian and Ubuntu (deb/apt)")],-1),U=e("p",null,"To install Atom on Debian, Ubuntu, or related distributions, add our official package repository to your system by running the following commands:",-1),C=e("div",{class:"language-command-line ext-command-line line-numbers-mode"},[e("pre",{class:"language-command-line"},[e("code",null,`$ wget -qO - https://packagecloud.io/AtomEditor/atom/gpgkey | sudo apt- +key add - +$ sudo sh -c 'echo "deb [arch=amd64] https://packagecloud.io/AtomEditor/atom/any/ any main" > /etc/apt/sources.list.d/atom.list' +$ sudo apt-get update +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),L=e("p",null,[t("You can now install Atom using "),e("code",null,"apt-get"),t(" (or "),e("code",null,"apt"),t(" on Ubuntu):")],-1),D=e("div",{class:"language-command-line ext-command-line line-numbers-mode"},[e("pre",{class:"language-command-line"},[e("code",null,`# Install Atom +$ sudo apt-get install atom + +# Install Atom Beta +$ sudo apt-get install atom-beta +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),M={href:"https://atom.io/download/deb",target:"_blank",rel:"noopener noreferrer"},O=e("div",{class:"language-command-line ext-command-line line-numbers-mode"},[e("pre",{class:"language-command-line"},[e("code",null,`# Install Atom +$ sudo apt install ./atom-amd64.deb +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),Y=e("h5",{id:"red-hat-and-centos-yum-or-fedora-dnf",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#red-hat-and-centos-yum-or-fedora-dnf","aria-hidden":"true"},"#"),t(" Red Hat and CentOS (YUM), or Fedora (DNF)")],-1),z=e("p",null,"To install Atom on CentOS, Oracle Linux, Red Hat Enterprise Linux, Scientific Linux, Fedora, or related distributions that use the YUM or DNF package managers, add our official package repository to your system by running the following commands:",-1),W=e("div",{class:"language-command-line ext-command-line line-numbers-mode"},[e("pre",{class:"language-command-line"},[e("code",null,`$ sudo rpm --import https://packagecloud.io/AtomEditor/atom/gpgkey +$ sudo sh -c 'echo -e "[Atom]\\nname=Atom Editor\\nbaseurl= +https://packagecloud.io/AtomEditor/atom/el/7/ +\\$basearch\\nenabled=1\\ngpgcheck=0\\nrepo_gpgcheck=1\\ngpgkey= +https://packagecloud.io/AtomEditor/atom/gpgkey" > /etc/yum.repos.d/ +atom.repo' +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),P=e("p",null,[t("You can now install Atom using "),e("code",null,"dnf"),t(" (or "),e("code",null,"yum"),t(" depending on your distribution):")],-1),B=e("div",{class:"language-command-line ext-command-line line-numbers-mode"},[e("pre",{class:"language-command-line"},[e("code",null,`# Install Atom +$ sudo dnf install atom + +# Install Atom Beta +$ sudo dnf install atom-beta +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),F={href:"https://atom.io/download/rpm",target:"_blank",rel:"noopener noreferrer"},H=e("div",{class:"language-command-line ext-command-line line-numbers-mode"},[e("pre",{class:"language-command-line"},[e("code",null,`# On YUM-based distributions +$ sudo yum install -y atom.x86_64.rpm + +# On DNF-based distributions +$ sudo dnf install -y atom.x86_64.rpm +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),N=e("h5",{id:"suse-zypp",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#suse-zypp","aria-hidden":"true"},"#"),t(" SUSE (zypp)")],-1),R=e("p",null,"To install Atom on openSUSE or other distributions that use the Zypp package manager, add our official package repository to your system by running the following commands:",-1),V=e("div",{class:"language-command-line ext-command-line line-numbers-mode"},[e("pre",{class:"language-command-line"},[e("code",null,`$ sudo sh -c 'echo -e "[Atom]\\nname=Atom Editor\\nbaseurl= +https://packagecloud.io/AtomEditor/atom/el/7/\\$basearch\\nenabled=1\\ntype=rpm- +md\\ngpgcheck=0\\nrepo_gpgcheck=1\\ +ngpgkey=https://packagecloud.io/AtomEditor/atom/gpgkey" > +/etc/zypp/repos.d/atom.repo' +$ sudo zypper --gpg-auto-import-keys refresh +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),j=e("p",null,[t("You can now install Atom using "),e("code",null,"zypper"),t(":")],-1),q=e("div",{class:"language-command-line ext-command-line line-numbers-mode"},[e("pre",{class:"language-command-line"},[e("code",null,`# Install Atom +$ sudo zypper install atom + +# Install Atom Beta +$ sudo zypper install atom-beta +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),G={href:"https://atom.io/download/rpm",target:"_blank",rel:"noopener noreferrer"},X=e("div",{class:"language-command-line ext-command-line line-numbers-mode"},[e("pre",{class:"language-command-line"},[e("code",null,`$ sudo zypper in -y atom.x86_64.rpm +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"})])],-1),Z=e("h4",{id:"installing-atom-on-mac",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#installing-atom-on-mac","aria-hidden":"true"},"#"),t(" Installing Atom on Mac")],-1),J={href:"https://github.com/atom/atom/releases/latest",target:"_blank",rel:"noopener noreferrer"},K=e("code",null,"atom-mac.zip",-1),Q=e("code",null,"Atom",-1),ee=e("p",null,[t("When you first open Atom, it will try to install the "),e("code",null,"atom"),t(" and "),e("code",null,"apm"),t(" commands for use in the terminal. In some cases, Atom might not be able to install these commands because it needs an administrator password. To check if Atom was able to install the "),e("code",null,"atom"),t(" command, for example, open a terminal window and type "),e("code",null,"which atom"),t(". If the "),e("code",null,"atom"),t(" command has been installed, you'll see something like this:")],-1),te=e("div",{class:"language-command-line ext-command-line line-numbers-mode"},[e("pre",{class:"language-command-line"},[e("code",null,`$ which atom +> /usr/local/bin/atom +$ +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),ne=e("p",null,[t("If the "),e("code",null,"atom"),t(" command wasn't installed, the "),e("code",null,"which"),t(" command won't return anything:")],-1),ae=e("div",{class:"language-command-line ext-command-line line-numbers-mode"},[e("pre",{class:"language-command-line"},[e("code",null,`$ which atom +$ +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),oe=e("p",null,[t("To install the "),e("code",null,"atom"),t(" and "),e("code",null,"apm"),t(' commands, run "Window: Install Shell Commands" from the '),e("a",{href:"/getting-started/sections/atom-basics#command-palette"},"Command Palette"),t(", which will prompt you for an administrator password.")],-1),ie=e("h4",{id:"installing-atom-on-windows",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#installing-atom-on-windows","aria-hidden":"true"},"#"),t(" Installing Atom on Windows")],-1),le={href:"https://github.com/atom/atom/releases/latest",target:"_blank",rel:"noopener noreferrer"},se=e("code",null,"AtomSetup.exe",-1),de=e("code",null,"AtomSetup-x64.exe",-1),re=e("code",null,"atom",-1),ce=e("code",null,"apm",-1),me=e("code",null,"PATH",-1),ue=e("p",null,[e("img",{src:b,alt:"Atom on Windows"})],-1),he=e("p",null,[t("The context menu "),e("code",null,"Open with Atom"),t(" in File Explorer, and the option to make Atom available for file association using "),e("code",null,"Open with..."),t(", is controlled by the System Settings panel as seen above.")],-1),pe=e("p",null,[t("With Atom open, click on "),e("code",null,"File > Settings"),t(", and then the "),e("code",null,"System"),t(" tab on the left. Check the boxes next to "),e("code",null,"Show in file context menus"),t(", as well as "),e("code",null,"Show in folder context menus"),t(". And you\u2019re all set.")],-1),be=e("h4",{id:"updating-atom",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#updating-atom","aria-hidden":"true"},"#"),t(" Updating Atom")],-1),ge=e("p",null,"You should consider updating Atom periodically for the latest improvements to the software. Additionally, When Atom receives hotfixes for security vulnerabilities you will want to update your version of Atom as soon as possible.",-1),_e=e("code",null,".rpm",-1),fe=e("code",null,".deb",-1),ye={href:"https://flight-manual.atom.io/getting-started/sections/installing-atom/#installing-atom-on-linux",target:"_blank",rel:"noopener noreferrer"},ve={href:"https://flight-manual.atom.io/getting-started/sections/atom-basics/#settings-and-preferences",target:"_blank",rel:"noopener noreferrer"},we=e("p",null,"To perform a manual update:",-1),Ae=e("li",null,[t("Click on the "),e("code",null,"Atom > Check for Update"),t(" menu item in the menu bar.")],-1),xe=e("code",null,"Application: About",-1),ke={href:"https://flight-manual.atom.io/getting-started/sections/atom-basics/#command-palette",target:"_blank",rel:"noopener noreferrer"},Se=e("code",null,"Check now",-1),Te=e("p",null,"Atom will begin to update if an update is available.",-1),$e={href:"https://flight-manual.atom.io/getting-started/sections/atom-basics/#settings-and-preferences",target:"_blank",rel:"noopener noreferrer"},Ie=e("p",null,"To perform a manual update:",-1),Ee=e("li",null,[t("Click on the "),e("code",null,"Help > Check for Update"),t(" menu item in the menu bar.")],-1),Ue=e("code",null,"Application: About",-1),Ce={href:"https://flight-manual.atom.io/getting-started/sections/atom-basics/#command-palette",target:"_blank",rel:"noopener noreferrer"},Le=e("code",null,"Check now",-1),De=e("p",null,"Atom will begin to update if an update is available.",-1),Me=e("h4",{id:"portable-mode",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#portable-mode","aria-hidden":"true"},"#"),t(" Portable Mode")],-1),Oe=e("p",null,[t("Atom stores configuration and state in a "),e("code",null,".atom"),t(" directory usually located in your home directory "),e("span",{class:"platform-windows"},[t("("),e("code",null,"%userprofile%"),t(" on Windows)")]),t(". You can however run Atom in portable mode where both the app and the configuration are stored together such as on a removable storage device.")],-1),Ye={href:"https://github.com/atom/atom/releases/latest",target:"_blank",rel:"noopener noreferrer"},ze=e("p",null,[t("Then create a "),e("code",null,".atom"),t(" directory alongside the directory that contains the Atom binary, for example:")],-1),We=e("div",{class:"language-text ext-text line-numbers-mode"},[e("pre",{class:"language-text"},[e("code",null,`/media/myusb/atom-1.14/atom +/media/myusb/.atom +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),Pe=e("p",null,[t("Then create a "),e("code",null,".atom"),t(" directory alongside the Atom.app application, for example:")],-1),Be=e("div",{class:"language-text ext-text line-numbers-mode"},[e("pre",{class:"language-text"},[e("code",null,`/MyUSB/Atom.app +/MyUSB/.atom +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),Fe=e("p",null,[t("Then create a "),e("code",null,".atom"),t(" directory alongside the directory that contains atom.exe, for example:")],-1),He=e("div",{class:"language-text ext-text line-numbers-mode"},[e("pre",{class:"language-text"},[e("code",null,`e:\\atom-1.14\\atom.exe +e:\\.atom +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),Ne=c('
    Portable Notes
    • The .atom directory must be writeable
    • You can move an existing .atom directory to your portable device
    • Atom can also store its Electron user data in your .atom directory - just create a subdirectory called electronUserData inside .atom
    • Alternatively you can set the ATOM_HOME environment variable to point wherever you want (you can write a .sh or .cmd script to temporarily set it and launch it from that)
    • Portable mode installations will not automatically update

    Building Atom from Source

    ',3),Re=c(`

    Proxy and Firewall Settings

    Behind a Firewall?

    If you are behind a firewall and seeing SSL errors when installing packages you can disable strict SSL by running:

    $ apm config set strict-ssl false
    +
    Using a Proxy?

    If you are using a HTTP(S) proxy you can configure apm to use it by running:

    $ apm config set https-proxy <em>YOUR_PROXY_ADDRESS</em>
    +

    You can run apm config get https-proxy to verify it has been set correctly.

    `,8);function Ve(je,qe){const d=r("Tabs"),o=r("ExternalLinkIcon"),m=r("RouterLink");return _(),f("div",null,[v,w,A,n(d,{id:"9",data:[{title:"Linux"},{title:"Mac"},{title:"Windows"}],"tab-id":"installing-atom"},{tab0:a(({title:i,value:l,isActive:s})=>[x]),tab1:a(({title:i,value:l,isActive:s})=>[k]),tab2:a(({title:i,value:l,isActive:s})=>[S]),_:1}),T,n(d,{id:"29",data:[{title:"Linux"},{title:"Mac"},{title:"Windows"}],"tab-id":"installing-atom"},{tab0:a(({title:i,value:l,isActive:s})=>[$,I,E,U,C,L,D,e("p",null,[t("Alternatively, you can download the "),e("a",M,[t("Atom .deb package"),n(o)]),t(" and install it directly:")]),O,Y,z,W,P,B,e("p",null,[t("Alternatively, you can download the "),e("a",F,[t("Atom .rpm package"),n(o)]),t(" and install it directly:")]),H,N,R,V,j,q,e("p",null,[t("Alternatively, you can download the "),e("a",G,[t("Atom .rpm package"),n(o)]),t(" and install it directly:")]),X]),tab1:a(({title:i,value:l,isActive:s})=>[Z,e("p",null,[t("Atom follows the standard Mac zip installation process. You can either press the download button from the https://atom.io site or you can go to the "),e("a",J,[t("Atom releases page"),n(o)]),t(" to download the "),K,t(" file explicitly. Once you have that file, you can click on it to extract the application and then drag the new "),Q,t(' application into your "Applications" folder.')]),ee,te,ne,ae,oe]),tab2:a(({title:i,value:l,isActive:s})=>[ie,e("p",null,[t("Atom is available with Windows installers that can be downloaded from https://atom.io or from the "),e("a",le,[t("Atom releases page"),n(o)]),t(". Use "),se,t(" for 32-bit systems and "),de,t(" for 64-bit systems. This setup program will install Atom, add the "),re,t(" and "),ce,t(" commands to your "),me,t(", and create shortcuts on the desktop and in the start menu.")]),ue,he,pe]),_:1}),be,ge,n(d,{id:"126",data:[{title:"Linux"},{title:"Mac"},{title:"Windows"}],"tab-id":"installing-atom"},{tab0:a(({title:i,value:l,isActive:s})=>[e("p",null,[t("If you are using Atom's official package repositories, use your distribution's package manager to update Atom. Otherwise, you will need to manually download and install the latest "),_e,t(" or "),fe,t(" package from https://atom.io. For more details, see "),e("a",ye,[t("Installing Atom on Linux."),n(o)])])]),tab1:a(({title:i,value:l,isActive:s})=>[e("p",null,[t('"Automatically Update" is enabled by default in Core Settings of the '),e("a",ve,[t("Settings View"),n(o)]),t(", which will allow Atom to check for updates automatically. If you disable this setting you can update Atom manually.")]),we,e("ul",null,[Ae,e("li",null,[t("Search for "),xe,t(" in the "),e("a",ke,[t("Command Palette"),n(o)]),t(" and click the "),Se,t(" button.")])]),Te]),tab2:a(({title:i,value:l,isActive:s})=>[e("p",null,[t('"Automatically Update" is enabled by default in Core Settings of the '),e("a",$e,[t("Settings View"),n(o)]),t(", which will allow Atom to check for updates automatically. If you disable this setting you can update Atom manually.")]),Ie,e("ul",null,[Ee,e("li",null,[t("Search for "),Ue,t(" in the "),e("a",Ce,[t("Command Palette"),n(o)]),t(" and click the "),Le,t(" button.")])]),De]),_:1}),Me,Oe,e("p",null,[t("To setup Atom in portable mode download the "),e("a",Ye,[t("zip/tar.gz package for your system"),n(o)]),t(" and extract it to your removable storage.")]),n(d,{id:"188",data:[{title:"Linux"},{title:"Mac"},{title:"Windows"}],"tab-id":"installing-atom"},{tab0:a(({title:i,value:l,isActive:s})=>[ze,We]),tab1:a(({title:i,value:l,isActive:s})=>[Pe,Be]),tab2:a(({title:i,value:l,isActive:s})=>[Fe,He]),_:1}),Ne,e("p",null,[t("The "),n(m,{to:"/hacking-atom/sections/hacking-on-atom-core/"},{default:a(()=>[t("Hacking on Atom Core")]),_:1}),t(" section of the flight manual covers instructions on how to clone and build the source code if you prefer that option.")]),Re])}const Je=g(y,[["render",Ve],["__file","installing-atom.html.vue"]]);export{Je as default}; diff --git a/assets/installing-pulsar.html.42e59c39.js b/assets/installing-pulsar.html.42e59c39.js new file mode 100644 index 0000000000..77e3f28103 --- /dev/null +++ b/assets/installing-pulsar.html.42e59c39.js @@ -0,0 +1,16 @@ +import{_ as p}from"./windows-system-settings.141561e2.js";import{_ as m,o as b,c as g,e as d,a as e,b as a,d as n,w as t,f as h,r as u}from"./app.0e1565ce.js";const _={},f=e("h2",{id:"installing-pulsar",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#installing-pulsar","aria-hidden":"true"},"#"),a(" Installing Pulsar")],-1),v=e("p",null,"To get started with Pulsar, we'll need to get it on your system. This section will go over installing Pulsar on your system, as well as the basics of how to build it from source.",-1),y={href:"https://pulsar-edit.dev/",target:"_blank",rel:"noopener noreferrer"},w=e("p",null,"Simply select your operating system (if not opened automatically) and architecture (where necessary) and choose the type of download you require.",-1),x=e("p",null,"The button or buttons should be specific to your platform, and the download package should be easily installable. However, let's go over them here in a bit of detail.",-1),P=e("h3",{id:"universal-releases",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#universal-releases","aria-hidden":"true"},"#"),a(" Universal releases")],-1),k=e("p",null,[a("Releases are provided in "),e("code",null,".AppImage"),a(" and "),e("code",null,".tar.gz"),a(' "universal" formats that should work on most distributions.')],-1),S=e("h4",{id:"appimage",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#appimage","aria-hidden":"true"},"#"),a(" AppImage")],-1),A=e("p",null,"Simply run the Pulsar AppImage from your file manager or the terminal:",-1),T=e("div",{class:"language-bash ext-sh line-numbers-mode"},[e("pre",{class:"language-bash"},[e("code",null,`$ ./pulsar_1.100.0_amd64.AppImage +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"})])],-1),O={href:"https://github.com/TheAssassin/AppImageLauncher",target:"_blank",rel:"noopener noreferrer"},D=e("h4",{id:"tar-gz",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#tar-gz","aria-hidden":"true"},"#"),a(" tar.gz")],-1),I=e("p",null,[a("Simply extract and run the "),e("code",null,"pulsar"),a(" binary or integrate it into your system manually.")],-1),L=e("h3",{id:"debian-ubuntu-based-distributions",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#debian-ubuntu-based-distributions","aria-hidden":"true"},"#"),a(" Debian/Ubuntu based distributions")],-1),W=e("code",null,".deb",-1),U=e("p",null,"You can install this by opening it in your file manager or via the terminal:",-1),E=e("div",{class:"language-bash ext-sh line-numbers-mode"},[e("pre",{class:"language-bash"},[e("code",null,[a("$ "),e("span",{class:"token function"},"sudo"),a(),e("span",{class:"token function"},"apt"),a(),e("span",{class:"token function"},"install"),a(` ./pulsar_1.100.0_amd64.deb +`)])]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"})])],-1),Y=e("h3",{id:"fedora-rhel-based-distributions",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#fedora-rhel-based-distributions","aria-hidden":"true"},"#"),a(" Fedora/RHEL based distributions")],-1),z=e("code",null,".rpm",-1),F=e("p",null,"You can install this by opening it in your file manager or via the terminal:",-1),R=e("div",{class:"language-bash ext-sh line-numbers-mode"},[e("pre",{class:"language-bash"},[e("code",null,[e("span",{class:"token comment"},"# On DNF-based distributions"),a(` +$ `),e("span",{class:"token function"},"sudo"),a(" dnf "),e("span",{class:"token function"},"install"),a(),e("span",{class:"token parameter variable"},"-y"),a(` ./pulsar_1.100.0_amd64.rpm + +`),e("span",{class:"token comment"},"# On YUM-based distributions"),a(` +$ `),e("span",{class:"token function"},"sudo"),a(" yum "),e("span",{class:"token function"},"install"),a(),e("span",{class:"token parameter variable"},"-y"),a(` ./pulsar_1.100.0_amd64.rpm +`)])]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),B=e("code",null,".dmg",-1),H=e("code",null,"Pulsar",-1),N=e("p",null,[a("If you prefer to install from "),e("code",null,".zip"),a(", this is also provided and requires you to extract the file and drag the "),e("code",null,"Pulsar"),a(' application into your "Applications" folder.')],-1),C={href:"https://en.wikipedia.org/wiki/Portable_application",target:"_blank",rel:"noopener noreferrer"},M=e("p",null,"We also have Pulsar available in certain package manager repositories. See the relevant section within the downloads for the installation commands.",-1),$=e("p",null,[e("img",{src:p,alt:"Pulsar on Windows"})],-1),V=e("p",null,[a("The context menu "),e("code",null,"Open with Pulsar"),a(" in File Explorer, and the option to make Pulsar available for file association using "),e("code",null,"Open with..."),a(", is controlled by the System Settings panel as seen above.")],-1),q=e("p",null,[a("With Pulsar open, click on "),e("code",null,"File > Settings"),a(", and then the "),e("code",null,"System"),a(" tab on the left. Check the boxes next to "),e("code",null,"Show in file context menus"),a(", as well as "),e("code",null,"Show in folder context menus"),a(". And you\u2019re all set.")],-1),G=e("h3",{id:"updating-pulsar",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#updating-pulsar","aria-hidden":"true"},"#"),a(" Updating Pulsar")],-1),j=e("p",null,"You should consider updating Pulsar periodically for the latest improvements to the software. Additionally, When Pulsar receives hotfixes for security vulnerabilities you will want to update your version of Pulsar as soon as possible.",-1),X=e("p",null,"If you have installed Pulsar via a package manager then you should use the instructions provided by that package manager for updating your installation.",-1),J=e("h3",{id:"portable-mode",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#portable-mode","aria-hidden":"true"},"#"),a(" Portable Mode")],-1),K=e("p",null,[a("Pulsar stores configuration and state in a "),e("code",null,".pulsar"),a(" directory usually located in your home directory ("),e("code",null,"%userprofile%"),a(" on Windows). You can however run Pulsar in portable mode where both the app and the configuration are stored together such as on a removable storage device.")],-1),Q=e("p",null,"To setup Pulsar in portable mode download the relevant package and extract it to your removable storage.",-1),Z=e("p",null,[a("Download the "),e("code",null,".appimage"),a(" or "),e("code",null,".tar.gz"),a(" release then create a "),e("code",null,".pulsar"),a(" directory alongside the directory that contains the Pulsar binary, for example:")],-1),ee=e("div",{class:"language-text ext-text line-numbers-mode"},[e("pre",{class:"language-text"},[e("code",null,`/media/myusb/pulsar-1.100.0/.pulsar +/media/myusb/.pulsar +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),ae=e("p",null,[a("Download the "),e("code",null,".zip"),a(" release then create a "),e("code",null,".pulsar"),a(" directory alongside the Pulsar.app application, for example:")],-1),te=e("div",{class:"language-text ext-text line-numbers-mode"},[e("pre",{class:"language-text"},[e("code",null,`/MyUSB/Pulsar.app +/MyUSB/.pulsar +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),ne=e("p",null,[a("Download the "),e("code",null,"Portable"),a(" release then create a "),e("code",null,".pulsar"),a(" directory alongside the directory that contains pulsar.exe, for example:")],-1),se=e("div",{class:"language-text ext-text line-numbers-mode"},[e("pre",{class:"language-text"},[e("code",null,`e:\\pulsar-1.100.0\\pulsar.exe +e:\\.pulsar +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),oe=h('

    Portable Notes

    • The .pulsar directory must be writeable
    • You can move an existing .pulsar directory to your portable device
    • Pulsar can also store its Electron user data in your .pulsar directory - just create a subdirectory called electronUserData inside .pulsar
    • Alternatively you can set the ATOM_HOME environment variable to point wherever you want (you can write a .sh or .cmd script to temporarily set it and launch it from that)
    • Portable mode installations will not automatically update

    Building Pulsar from Source

    ',3),le=h(`

    Proxy and Firewall Settings

    Behind a Firewall?

    If you are behind a firewall and seeing SSL errors when installing packages you can disable strict SSL by running:

    $ pulsar -p config set strict-ssl false
    +

    Using a Proxy?

    If you are using a HTTP(S) proxy you can configure ppm to use it by running:

    $ pulsar -p config set https-proxy <YOUR_PROXY_ADDRESS>
    +

    You can run pulsar -p config get https-proxy to verify it has been set correctly.

    `,8);function ie(re,de){const r=u("ExternalLinkIcon"),s=u("RouterLink"),c=u("Tabs");return b(),g("div",null,[f,d("TODO: We need a section here somewhere about how to get pulsar onto the PATH for all systems as it seems to be broken on mac and windows. Also needed for tar.gz and appimage"),v,e("p",null,[a("Installing Pulsar should be fairly simple. Generally, you can go to "),e("a",y,[a("pulsar-edit.dev"),n(r)]),a(" and you should see a download button.")]),w,x,n(c,{id:"16",data:[{title:"Linux"},{title:"macOS"},{title:"Windows"}],"tab-id":"getting-started"},{tab0:t(({title:o,value:l,isActive:i})=>[d("TODO: When/if we introduce other repository based downloads then we can re-instate much of this from the archived atom section"),P,k,S,A,T,e("p",null,[a("For deeper integration into the system consider using "),e("a",O,[a("AppImageLauncher"),n(r)]),a(".")]),D,I,L,e("p",null,[a("To install Pulsar on Debian, Ubuntu, or related distributions download the "),W,a(" from either the latest release or beta/dev version from "),n(s,{to:"/download.html"},{default:t(()=>[a("Pulsar Downloads")]),_:1}),a(".")]),U,E,Y,e("p",null,[a("To install Pulsar on Fedora, RHEL, or related distributions download the "),z,a(" from either the latest release or beta/dev version from "),n(s,{to:"/download.html"},{default:t(()=>[a("Pulsar Downloads")]),_:1}),a(".")]),F,R]),tab1:t(({title:o,value:l,isActive:i})=>[e("p",null,[a("Pulsar follows the standard macOS installation process. Grab the correct download "),B,a(" for your system from "),n(s,{to:"/download.html"},{default:t(()=>[a("Pulsar Downloads")]),_:1}),a(". Once you have the file, you can open it to run the installer and drag the new "),H,a(' application into your "Applications" folder.')]),N]),tab2:t(({title:o,value:l,isActive:i})=>[e("p",null,[a("Pulsar is available as a 64-bit Windows installer and "),e("a",C,[a("portable app"),n(r)]),a(" that can be downloaded from "),n(s,{to:"/download.html"},{default:t(()=>[a("Pulsar Downloads")]),_:1}),a(".")]),M,$,V,q]),_:1}),G,j,e("p",null,[a("Currently Pulsar does not support automatic updates. What this means is that new versions will have to be obtained via the "),n(s,{to:"/download.html"},{default:t(()=>[a("Pulsar Downloads")]),_:1}),a(" here on our website. This is something on our roadmap to change as soon as possible.")]),X,d("TODO: Auto upgrade instructions - selectively pull info from atom archive as this becomes possible"),J,K,Q,n(c,{id:"110",data:[{title:"Linux"},{title:"macOS"},{title:"Windows"}],"tab-id":"getting-started"},{tab0:t(({title:o,value:l,isActive:i})=>[Z,ee]),tab1:t(({title:o,value:l,isActive:i})=>[ae,te]),tab2:t(({title:o,value:l,isActive:i})=>[ne,se]),_:1}),oe,e("p",null,[a("The "),n(s,{to:"/docs/launch-manual/sections/core-hacking/#building-pulsar"},{default:t(()=>[a("Hacking the Core")]),_:1}),a(" section of the launch manual covers instructions on how to clone and build the source code if you prefer that option.")]),le])}const he=m(_,[["render",ie],["__file","installing-pulsar.html.vue"]]);export{he as default}; diff --git a/assets/installing-pulsar.html.60eab37a.js b/assets/installing-pulsar.html.60eab37a.js new file mode 100644 index 0000000000..b0d85788e3 --- /dev/null +++ b/assets/installing-pulsar.html.60eab37a.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-8c05c62e","path":"/docs/launch-manual/sections/getting-started/sections/installing-pulsar.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Installing Pulsar","slug":"installing-pulsar","link":"#installing-pulsar","children":[{"level":3,"title":"Universal releases","slug":"universal-releases","link":"#universal-releases","children":[]},{"level":3,"title":"Debian/Ubuntu based distributions","slug":"debian-ubuntu-based-distributions","link":"#debian-ubuntu-based-distributions","children":[]},{"level":3,"title":"Fedora/RHEL based distributions","slug":"fedora-rhel-based-distributions","link":"#fedora-rhel-based-distributions","children":[]},{"level":3,"title":"Updating Pulsar","slug":"updating-pulsar","link":"#updating-pulsar","children":[]},{"level":3,"title":"Portable Mode","slug":"portable-mode","link":"#portable-mode","children":[]},{"level":3,"title":"Building Pulsar from Source","slug":"building-pulsar-from-source","link":"#building-pulsar-from-source","children":[]},{"level":3,"title":"Proxy and Firewall Settings","slug":"proxy-and-firewall-settings","link":"#proxy-and-firewall-settings","children":[]}]}],"git":{"updatedTime":1680725278000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":7}]},"readingTime":{"minutes":3.34,"words":1002},"filePathRelative":"docs/launch-manual/sections/getting-started/sections/installing-pulsar.md"}');export{e as data}; diff --git a/assets/interacting-with-other-packages-via-services.html.137a2a04.js b/assets/interacting-with-other-packages-via-services.html.137a2a04.js new file mode 100644 index 0000000000..5577c83eb0 --- /dev/null +++ b/assets/interacting-with-other-packages-via-services.html.137a2a04.js @@ -0,0 +1,52 @@ +import{_ as t,o as p,c as o,a as s,b as n,d as c,f as a,r as i}from"./app.0e1565ce.js";const u={},l=a(`

    Interacting With Other Packages Via Services

    Pulsar packages can interact with each other through versioned APIs called services. To provide a service, in your package.json, specify one or more version numbers, each paired with the name of a method on your package's main module:

    {
    +	"providedServices": {
    +		"my-service": {
    +			"description": "Does a useful thing",
    +			"versions": {
    +				"1.2.3": "provideMyServiceV1",
    +				"2.3.4": "provideMyServiceV2"
    +			}
    +		}
    +	}
    +}
    +

    In your package's main module, implement the methods named above. These methods will be called any time a package is activated that consumes their corresponding service. They should return a value that implements the service's API.

    module.exports = {
    +	activate() {
    +		// ...
    +	},
    +
    +	provideMyServiceV1() {
    +		return adaptToLegacyAPI(myService);
    +	},
    +
    +	provideMyServiceV2() {
    +		return myService;
    +	},
    +};
    +
    `,5),r={href:"https://docs.npmjs.com/cli/v6/using-npm/semver#ranges",target:"_blank",rel:"noopener noreferrer"},d=s("em",null,"ranges",-1),v=a(`
    {
    +	"consumedServices": {
    +		"another-service": {
    +			"versions": {
    +				"^1.2.3": "consumeAnotherServiceV1",
    +				">=2.3.4 <2.5": "consumeAnotherServiceV2"
    +			}
    +		}
    +	}
    +}
    +

    These methods will be called any time a package is activated that provides their corresponding service. They will receive the service object as an argument. You will usually need to perform some kind of cleanup in the event that the package providing the service is deactivated. To do this, return a Disposable from your service-consuming method:

    const { Disposable } = require("atom");
    +
    +module.exports = {
    +	activate() {
    +		// ...
    +	},
    +
    +	consumeAnotherServiceV1(service) {
    +		useService(adaptServiceFromLegacyAPI(service));
    +		return new Disposable(() => stopUsingService(service));
    +	},
    +
    +	consumeAnotherServiceV2(service) {
    +		useService(service);
    +		return new Disposable(() => stopUsingService(service));
    +	},
    +};
    +
    `,3);function k(m,b){const e=i("ExternalLinkIcon");return p(),o("div",null,[l,s("p",null,[n("Similarly, to consume a service, specify one or more "),s("a",r,[n("version "),d,c(e)]),n(", each paired with the name of a method on the package's main module:")]),v])}const g=t(u,[["render",k],["__file","interacting-with-other-packages-via-services.html.vue"]]);export{g as default}; diff --git a/assets/interacting-with-other-packages-via-services.html.2f7e9afe.js b/assets/interacting-with-other-packages-via-services.html.2f7e9afe.js new file mode 100644 index 0000000000..cba9950ab7 --- /dev/null +++ b/assets/interacting-with-other-packages-via-services.html.2f7e9afe.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-012c1bd2","path":"/docs/atom-archive/behind-atom/sections/interacting-with-other-packages-via-services.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"Interacting With Other Packages Via Services","slug":"interacting-with-other-packages-via-services","link":"#interacting-with-other-packages-via-services","children":[]}],"git":{"updatedTime":1668309800000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":3},{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":0.82,"words":246},"filePathRelative":"docs/atom-archive/behind-atom/sections/interacting-with-other-packages-via-services.md"}');export{e as data}; diff --git a/assets/interacting-with-other-packages-via-services.html.4dc580d2.js b/assets/interacting-with-other-packages-via-services.html.4dc580d2.js new file mode 100644 index 0000000000..4de7a134b0 --- /dev/null +++ b/assets/interacting-with-other-packages-via-services.html.4dc580d2.js @@ -0,0 +1,52 @@ +import{_ as t,o as p,c as o,a as s,b as n,d as c,f as a,r as i}from"./app.0e1565ce.js";const u={},l=a(`

    Interacting With Other Packages Via Services

    Atom packages can interact with each other through versioned APIs called services. To provide a service, in your package.json, specify one or more version numbers, each paired with the name of a method on your package's main module:

    {
    +	"providedServices": {
    +		"my-service": {
    +			"description": "Does a useful thing",
    +			"versions": {
    +				"1.2.3": "provideMyServiceV1",
    +				"2.3.4": "provideMyServiceV2"
    +			}
    +		}
    +	}
    +}
    +

    In your package's main module, implement the methods named above. These methods will be called any time a package is activated that consumes their corresponding service. They should return a value that implements the service's API.

    module.exports = {
    +	activate() {
    +		// ...
    +	},
    +
    +	provideMyServiceV1() {
    +		return adaptToLegacyAPI(myService);
    +	},
    +
    +	provideMyServiceV2() {
    +		return myService;
    +	},
    +};
    +
    `,5),r={href:"https://docs.npmjs.com/cli/v6/using-npm/semver#ranges",target:"_blank",rel:"noopener noreferrer"},d=s("em",null,"ranges",-1),v=a(`
    {
    +	"consumedServices": {
    +		"another-service": {
    +			"versions": {
    +				"^1.2.3": "consumeAnotherServiceV1",
    +				">=2.3.4 <2.5": "consumeAnotherServiceV2"
    +			}
    +		}
    +	}
    +}
    +

    These methods will be called any time a package is activated that provides their corresponding service. They will receive the service object as an argument. You will usually need to perform some kind of cleanup in the event that the package providing the service is deactivated. To do this, return a Disposable from your service-consuming method:

    const { Disposable } = require("atom");
    +
    +module.exports = {
    +	activate() {
    +		// ...
    +	},
    +
    +	consumeAnotherServiceV1(service) {
    +		useService(adaptServiceFromLegacyAPI(service));
    +		return new Disposable(() => stopUsingService(service));
    +	},
    +
    +	consumeAnotherServiceV2(service) {
    +		useService(service);
    +		return new Disposable(() => stopUsingService(service));
    +	},
    +};
    +
    `,3);function k(m,b){const e=i("ExternalLinkIcon");return p(),o("div",null,[l,s("p",null,[n("Similarly, to consume a service, specify one or more "),s("a",r,[n("version "),d,c(e)]),n(", each paired with the name of a method on the package's main module:")]),v])}const g=t(u,[["render",k],["__file","interacting-with-other-packages-via-services.html.vue"]]);export{g as default}; diff --git a/assets/interacting-with-other-packages-via-services.html.ee16b4f1.js b/assets/interacting-with-other-packages-via-services.html.ee16b4f1.js new file mode 100644 index 0000000000..825605bba7 --- /dev/null +++ b/assets/interacting-with-other-packages-via-services.html.ee16b4f1.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-0a930154","path":"/docs/launch-manual/sections/behind-pulsar/sections/interacting-with-other-packages-via-services.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Interacting With Other Packages Via Services","slug":"interacting-with-other-packages-via-services","link":"#interacting-with-other-packages-via-services","children":[]}],"git":{"updatedTime":1668711072000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":0.82,"words":246},"filePathRelative":"docs/launch-manual/sections/behind-pulsar/sections/interacting-with-other-packages-via-services.md"}');export{e as data}; diff --git a/assets/is-atom-open-source.html.3e2a7899.js b/assets/is-atom-open-source.html.3e2a7899.js new file mode 100644 index 0000000000..c78a17069c --- /dev/null +++ b/assets/is-atom-open-source.html.3e2a7899.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-4ea46d24","path":"/docs/atom-archive/faq/sections/is-atom-open-source.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"Is Atom open source?","slug":"is-atom-open-source","link":"#is-atom-open-source","children":[]}],"git":{"updatedTime":1667690985000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1},{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":0.06,"words":19},"filePathRelative":"docs/atom-archive/faq/sections/is-atom-open-source.md"}');export{e as data}; diff --git a/assets/is-atom-open-source.html.9b17a52a.js b/assets/is-atom-open-source.html.9b17a52a.js new file mode 100644 index 0000000000..73b7971173 --- /dev/null +++ b/assets/is-atom-open-source.html.9b17a52a.js @@ -0,0 +1 @@ +import{_ as n,o as r,c as s,a as e,b as o,d as a,r as c}from"./app.0e1565ce.js";const i={},l=e("h3",{id:"is-atom-open-source",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#is-atom-open-source","aria-hidden":"true"},"#"),o(" Is Atom open source?")],-1),m={href:"https://github.com/atom/atom/blob/master/LICENSE.md",target:"_blank",rel:"noopener noreferrer"};function _(d,h){const t=c("ExternalLinkIcon");return r(),s("div",null,[l,e("p",null,[o("Yes, "),e("a",m,[o("Atom is licensed under the MIT license."),a(t)])])])}const u=n(i,[["render",_],["__file","is-atom-open-source.html.vue"]]);export{u as default}; diff --git a/assets/june-2017.html.924b5d43.js b/assets/june-2017.html.924b5d43.js new file mode 100644 index 0000000000..f186fd6858 --- /dev/null +++ b/assets/june-2017.html.924b5d43.js @@ -0,0 +1 @@ +import{_ as i,o as e,c as t,f as l}from"./app.0e1565ce.js";const a={},o=l('

    June 2017 - Monthly Planning

    Note

    Please note that its possible this is outdated, as its original version was published by @'Katrina Uychaco' on Jun 14, 2017.

    Looking Back

    • Beta launch went very well
    • Relatively few problems
    • Performance work @smashwilson and @kuychaco did is \u2728
    • GPG/credential helper work that @smashwilson shipped was solid
    • Feeling good about shipping to stable

    This Month

    • Ensure room to triage and fix issues that may arise after shipping to stable
    • File change notification service @smashwilson
    • GitHub integration @BinaryMuse @kuychaco
      • Create new PR
      • Fill out existing functionality (timeline item types, new fields, etc.)
      • Add mechanism for manual/auto refreshing
      • Continue to investigate possibility of real-time updates
    • Git integration @kuychaco
      • Add "last commit(s)" view under commit box
      • Investigate removing amend checkbox in favor of more robust HEAD management

    Other Topics Discussed

    • Web Workers
      • Wait to see if issues arise with current implementation
    • Better remote/branch management
      • Existing UI mocks are relatively old and pre-docks
      • Want to ensure what we ship is close to what we'll want long term to ensure UI/UX stability over time
      • Resume conversation around design and pick this up in coming months
    • core:undo support
      • Undo support is a misnomer in a system where there's not a linear set of edits
      • Want to design a system where any action is recoverable (different from undoable)
      • Really talking about UI Git porcelain at this point
      • Start deeper conversations now, talk about more at mini-summit
    • Git log / graph
      • We could do something simple now very quickly but would it be up to the standards we want?
      • Decided to work on "last commit(s)" view for June, talk about this more at mini-summit
    • Stash support
      • Start talking about UI/UX
    • Additional GitHub PR work
      • "Checking out" a PR from the GH pane is a great first step toward implementing a full code review flow
    ',8),n=[o];function s(r,u){return e(),t("div",null,n)}const h=i(a,[["render",s],["__file","june-2017.html.vue"]]);export{h as default}; diff --git a/assets/june-2017.html.c21d6acc.js b/assets/june-2017.html.c21d6acc.js new file mode 100644 index 0000000000..e00d1e48a4 --- /dev/null +++ b/assets/june-2017.html.c21d6acc.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-6a77de6d","path":"/docs/packages/core/github/june-2017.html","title":"June 2017 - Monthly Planning","lang":"en-us","frontmatter":{"lang":"en-us","title":"June 2017 - Monthly Planning"},"excerpt":"","headers":[{"level":2,"title":"Looking Back","slug":"looking-back","link":"#looking-back","children":[]},{"level":2,"title":"This Month","slug":"this-month","link":"#this-month","children":[]},{"level":2,"title":"Other Topics Discussed","slug":"other-topics-discussed","link":"#other-topics-discussed","children":[]}],"git":{"updatedTime":1664399092000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":2}]},"readingTime":{"minutes":1.07,"words":321},"filePathRelative":"docs/packages/core/github/june-2017.md"}');export{e as data}; diff --git a/assets/keybinding-resolver.befdd67c.png b/assets/keybinding-resolver.befdd67c.png new file mode 100644 index 0000000000..960f0996c1 Binary files /dev/null and b/assets/keybinding-resolver.befdd67c.png differ diff --git a/assets/keybinding.8bf3fa31.png b/assets/keybinding.8bf3fa31.png new file mode 100644 index 0000000000..ac495045bc Binary files /dev/null and b/assets/keybinding.8bf3fa31.png differ diff --git a/assets/keybinding.964a4e0d.js b/assets/keybinding.964a4e0d.js new file mode 100644 index 0000000000..d28b8911ce --- /dev/null +++ b/assets/keybinding.964a4e0d.js @@ -0,0 +1 @@ +const s="/assets/keybinding.8bf3fa31.png";export{s as _}; diff --git a/assets/keymaps-in-depth.html.253e4d41.js b/assets/keymaps-in-depth.html.253e4d41.js new file mode 100644 index 0000000000..c56b5c6095 --- /dev/null +++ b/assets/keymaps-in-depth.html.253e4d41.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-b6a94a42","path":"/docs/atom-archive/behind-atom/sections/keymaps-in-depth.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"Keymaps In-Depth","slug":"keymaps-in-depth","link":"#keymaps-in-depth","children":[]}],"git":{"updatedTime":1668309800000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":4},{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":5.76,"words":1727},"filePathRelative":"docs/atom-archive/behind-atom/sections/keymaps-in-depth.md"}');export{e as data}; diff --git a/assets/keymaps-in-depth.html.596e6691.js b/assets/keymaps-in-depth.html.596e6691.js new file mode 100644 index 0000000000..75e6bb4795 --- /dev/null +++ b/assets/keymaps-in-depth.html.596e6691.js @@ -0,0 +1,78 @@ +import{_ as d}from"./keybinding.964a4e0d.js";import{_ as u,o as m,c as k,d as i,w as s,a as e,b as n,e as h,f as r,r as l}from"./app.0e1565ce.js";const g={},y=e("h2",{id:"keymaps-in-depth",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#keymaps-in-depth","aria-hidden":"true"},"#"),n(" Keymaps In-Depth")],-1),b=e("h3",{id:"structure-of-a-keymap-file",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#structure-of-a-keymap-file","aria-hidden":"true"},"#"),n(" Structure of a Keymap File")],-1),v=e("p",null,[n("Keymap files are encoded as JSON or CSON files containing nested hashes. They work much like style sheets, but instead of applying style properties to elements matching the selector, they specify the meaning of keystrokes on elements matching the selector. Here is an example of some bindings that apply when keystrokes pass through "),e("code",null,"atom-text-editor"),n(" elements:")],-1),f=e("div",{class:"language-coffee ext-coffee line-numbers-mode"},[e("pre",{class:"language-coffee"},[e("code",null,[e("span",{class:"token string-property property"},"'atom-text-editor'"),e("span",{class:"token operator"},":"),n(` + `),e("span",{class:"token string-property property"},"'ctrl-left'"),e("span",{class:"token operator"},":"),n(),e("span",{class:"token string"},"'editor:move-to-beginning-of-word'"),n(` + `),e("span",{class:"token string-property property"},"'ctrl-right'"),e("span",{class:"token operator"},":"),n(),e("span",{class:"token string"},"'editor:move-to-end-of-word'"),n(` + `),e("span",{class:"token string-property property"},"'ctrl-shift-left'"),e("span",{class:"token operator"},":"),n(),e("span",{class:"token string"},"'editor:select-to-beginning-of-word'"),n(` + `),e("span",{class:"token string-property property"},"'ctrl-shift-right'"),e("span",{class:"token operator"},":"),n(),e("span",{class:"token string"},"'editor:select-to-end-of-word'"),n(` + `),e("span",{class:"token string-property property"},"'ctrl-backspace'"),e("span",{class:"token operator"},":"),n(),e("span",{class:"token string"},"'editor:delete-to-beginning-of-word'"),n(` + `),e("span",{class:"token string-property property"},"'ctrl-delete'"),e("span",{class:"token operator"},":"),n(),e("span",{class:"token string"},"'editor:delete-to-end-of-word'"),n(` + +`),e("span",{class:"token string-property property"},"'atom-text-editor:not([mini])'"),e("span",{class:"token operator"},":"),n(` + `),e("span",{class:"token string-property property"},"'ctrl-alt-['"),e("span",{class:"token operator"},":"),n(),e("span",{class:"token string"},"'editor:fold-current-row'"),n(` + `),e("span",{class:"token string-property property"},"'ctrl-alt-]'"),e("span",{class:"token operator"},":"),n(),e("span",{class:"token string"},"'editor:unfold-current-row'"),n(` +`)])]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),w=e("div",{class:"language-coffee ext-coffee line-numbers-mode"},[e("pre",{class:"language-coffee"},[e("code",null,[e("span",{class:"token string-property property"},"'atom-text-editor'"),e("span",{class:"token operator"},":"),n(` + `),e("span",{class:"token string-property property"},"'cmd-delete'"),e("span",{class:"token operator"},":"),n(),e("span",{class:"token string"},"'editor:delete-to-beginning-of-line'"),n(` + `),e("span",{class:"token string-property property"},"'alt-backspace'"),e("span",{class:"token operator"},":"),n(),e("span",{class:"token string"},"'editor:delete-to-beginning-of-word'"),n(` + `),e("span",{class:"token string-property property"},"'ctrl-A'"),e("span",{class:"token operator"},":"),n(),e("span",{class:"token string"},"'editor:select-to-first-character-of-line'"),n(` + `),e("span",{class:"token string-property property"},"'ctrl-shift-e'"),e("span",{class:"token operator"},":"),n(),e("span",{class:"token string"},"'editor:select-to-end-of-line'"),n(` + `),e("span",{class:"token string-property property"},"'cmd-left'"),e("span",{class:"token operator"},":"),n(),e("span",{class:"token string"},"'editor:move-to-first-character-of-line'"),n(` + +`),e("span",{class:"token string-property property"},"'atom-text-editor:not([mini])'"),e("span",{class:"token operator"},":"),n(` + `),e("span",{class:"token string-property property"},"'cmd-alt-['"),e("span",{class:"token operator"},":"),n(),e("span",{class:"token string"},"'editor:fold-current-row'"),n(` + `),e("span",{class:"token string-property property"},"'cmd-alt-]'"),e("span",{class:"token operator"},":"),n(),e("span",{class:"token string"},"'editor:unfold-current-row'"),n(` +`)])]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),x=e("div",{class:"language-coffee ext-coffee line-numbers-mode"},[e("pre",{class:"language-coffee"},[e("code",null,[e("span",{class:"token string-property property"},"'atom-text-editor'"),e("span",{class:"token operator"},":"),n(` + `),e("span",{class:"token string-property property"},"'ctrl-left'"),e("span",{class:"token operator"},":"),n(),e("span",{class:"token string"},"'editor:move-to-beginning-of-word'"),n(` + `),e("span",{class:"token string-property property"},"'ctrl-right'"),e("span",{class:"token operator"},":"),n(),e("span",{class:"token string"},"'editor:move-to-end-of-word'"),n(` + `),e("span",{class:"token string-property property"},"'ctrl-shift-left'"),e("span",{class:"token operator"},":"),n(),e("span",{class:"token string"},"'editor:select-to-beginning-of-word'"),n(` + `),e("span",{class:"token string-property property"},"'ctrl-shift-right'"),e("span",{class:"token operator"},":"),n(),e("span",{class:"token string"},"'editor:select-to-end-of-word'"),n(` + `),e("span",{class:"token string-property property"},"'ctrl-backspace'"),e("span",{class:"token operator"},":"),n(),e("span",{class:"token string"},"'editor:delete-to-beginning-of-word'"),n(` + `),e("span",{class:"token string-property property"},"'ctrl-delete'"),e("span",{class:"token operator"},":"),n(),e("span",{class:"token string"},"'editor:delete-to-end-of-word'"),n(` + +`),e("span",{class:"token string-property property"},"'atom-text-editor:not([mini])'"),e("span",{class:"token operator"},":"),n(` + `),e("span",{class:"token string-property property"},"'ctrl-alt-['"),e("span",{class:"token operator"},":"),n(),e("span",{class:"token string"},"'editor:fold-current-row'"),n(` + `),e("span",{class:"token string-property property"},"'ctrl-alt-]'"),e("span",{class:"token operator"},":"),n(),e("span",{class:"token string"},"'editor:unfold-current-row'"),n(` +`)])]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),_=r(`

    Beneath the first selector are several keybindings, mapping specific key combinations to commands. When an element with the atom-text-editor class is focused and LNX/WIN: Ctrl+Backspace - MAC: Alt+Backspace is pressed, a custom DOM event called editor:delete-to-beginning-of-word is emitted on the atom-text-editor element.

    The second selector group also targets editors, but only if they don't have the mini attribute. In this example, the commands for code folding don't really make sense on mini-editors, so the selector restricts them to regular editors.

    Key Combinations

    Key combinations express one or more keys combined with optional modifier keys. For example: ctrl-w v, or cmd-shift-up. A key combination is composed of the following symbols, separated by a -. A key sequence can be expressed as key combinations separated by spaces.

    TypeExamples
    Character literalsa 4 $
    Modifier keyscmd ctrl alt shift
    Special keysenter escape backspace delete tab home end pageup pagedown left right up down space

    Commands

    Commands are custom DOM events that are triggered when a key combination or sequence matches a binding. This allows user interface code to listen for named commands without specifying the specific keybinding that triggers it. For example, the following code creates a command to insert the current date in an editor:

    atom.commands.add("atom-text-editor", {
    +	"user:insert-date": function (event) {
    +		const editor = this.getModel();
    +		return editor.insertText(new Date().toLocaleString());
    +	},
    +});
    +

    atom.commands refers to the global CommandRegistry instance where all commands are set and consequently picked up by the command palette.

    When you are looking to bind new keys, it is often useful to use the Command Palette (LNX/WIN: Ctrl+Shift+P - MAC: Cmd+Shift+P) to discover what commands are being listened for in a given focus context. Commands are "humanized" following a simple algorithm, so a command like editor:fold-current-row would appear as "Editor: Fold Current Row".

    "Composed" Commands

    A common question is, "How do I make a single keybinding execute two or more commands?" There isn't any direct support for this in Pulsar, but it can be achieved by creating a custom command that performs the multiple actions you desire and then creating a keybinding for that command. For example, let's say I want to create a "composed" command that performs a Select Line followed by Cut. You could add the following to your init.js:

    atom.commands.add("atom-text-editor", "custom:cut-line", function () {
    +	const editor = this.getModel();
    +	editor.selectLinesContainingCursors();
    +	editor.cutSelectedText();
    +});
    +

    Then let's say we want to map this custom command to alt-ctrl-z, you could add the following to your keymap:

    'atom-text-editor':
    +  'alt-ctrl-z': 'custom:cut-line'
    +

    Specificity and Cascade Order

    As is the case with CSS applying styles, when multiple bindings match for a single element, the conflict is resolved by choosing the most specific selector. If two matching selectors have the same specificity, the binding for the selector appearing later in the cascade takes precedence.

    Currently, there's no way to specify selector ordering within a single keymap, because JSON objects do not preserve order. We handle cases where selector ordering is critical by breaking the keymap into separate files, such as snippets-1.cson and snippets-2.cson.

    Selectors and Custom Packages

    If a keybinding should only apply to a specific grammar, you can limit bindings to that grammar using the data-grammar attribute on the atom-text-editor element:

    "atom-text-editor[data-grammar='source example']":
    +  'ctrl-.': 'custom:custom-command'
    +

    While selectors can be applied to the entire editor by what grammar is associated with it, they cannot be applied to scopes defined within the grammar or to sub-elements of atom-text-editor.

    Removing Bindings

    When the keymap system encounters a binding with the unset! directive as its command, it will treat the current element as if it had no key bindings matching the current keystroke sequence and continue searching from its parent. For example, the following code removes the keybinding for a in the Tree View, which is normally used to trigger the tree-view:add-file command:

    '.tree-view':
    +  'a': 'unset!'
    +

    Keybinding Resolver

    But if some element above the Tree View had a keybinding for a, that keybinding would still execute even when the focus is inside the Tree View.

    When the keymap system encounters a binding with the abort! directive as its command, it will stop searching for a keybinding. For example, the following code removes the keybinding for LNX/WIN: Ctrl+O - MAC: Cmd+O when the selection is inside an editor pane:

    ',28),C=e("div",{class:"language-coffee ext-coffee line-numbers-mode"},[e("pre",{class:"language-coffee"},[e("code",null,[e("span",{class:"token string-property property"},"'atom-text-editor'"),e("span",{class:"token operator"},":"),n(` + `),e("span",{class:"token string-property property"},"'ctrl-o'"),e("span",{class:"token operator"},":"),n(),e("span",{class:"token string"},"'abort!'"),n(` +`)])]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),q=e("div",{class:"language-coffee ext-coffee line-numbers-mode"},[e("pre",{class:"language-coffee"},[e("code",null,[e("span",{class:"token string-property property"},"'atom-text-editor'"),e("span",{class:"token operator"},":"),n(` + `),e("span",{class:"token string-property property"},"'cmd-o'"),e("span",{class:"token operator"},":"),n(),e("span",{class:"token string"},"'abort!'"),n(` +`)])]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),S=e("div",{class:"language-coffee ext-coffee line-numbers-mode"},[e("pre",{class:"language-coffee"},[e("code",null,[e("span",{class:"token string-property property"},"'atom-text-editor'"),e("span",{class:"token operator"},":"),n(` + `),e("span",{class:"token string-property property"},"'ctrl-o'"),e("span",{class:"token operator"},":"),n(),e("span",{class:"token string"},"'abort!'"),n(` +`)])]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),T=r(`

    But if you click inside the Tree View and press LNX/WIN: Ctrl+O - MAC: Cmd+O , it will work.

    Forcing Chromium's Native Keystroke Handling

    If you want to force the native browser behavior for a given keystroke, use the native! directive as the command of a binding. This can be useful to enable the correct behavior in native input elements. If you apply the .native-key-bindings class to an element, all the keystrokes typically handled by the browser will be assigned the native! directive.

    Tips

    Tip: Components and input elements may not correctly handle backspace and arrow keys without forcing this behavior. If your backspace isn't working correctly inside of a component, add either the directive or the .native-key-bindings class.

    Overloading Key Bindings

    Occasionally, it makes sense to layer multiple actions on top of the same key binding. An example of this is the snippets package. Snippets are inserted by typing a snippet prefix such as for and then pressing Tab. Every time Tab is pressed, we want to execute code attempting to expand a snippet if one exists for the text preceding the cursor. If a snippet doesn't exist, we want Tab to actually insert whitespace.

    To achieve this, the snippets package makes use of the .abortKeyBinding() method on the event object representing the snippets:expand command.

    // pseudo-code
    +editor.command("snippets:expand", (e) => {
    +	if (this.cursorFollowsValidPrefix()) {
    +		this.expandSnippet();
    +	} else {
    +		e.abortKeyBinding();
    +	}
    +});
    +

    When the event handler observes that the cursor does not follow a valid prefix, it calls e.abortKeyBinding(), telling the keymap system to continue searching for another matching binding.

    Step-by-Step: How Keydown Events are Mapped to Commands

    • A keydown event occurs on a focused element.
    • Starting at the focused element, the keymap walks upward towards the root of the document, searching for the most specific CSS selector that matches the current DOM element and also contains a keystroke pattern matching the keydown event.
    • When a matching keystroke pattern is found, the search is terminated and the pattern's corresponding command is triggered on the current element.
    • If .abortKeyBinding() is called on the triggered event object, the search is resumed, triggering a binding on the next-most-specific CSS selector for the same element or continuing upward to parent elements.
    • If no bindings are found, the event is handled by Chromium normally.

    Overriding Pulsar's Keyboard Layout Recognition

    `,12),K={href:"https://web.archive.org/web/20220729003828/https://blog.atom.io/2016/10/17/the-wonderful-world-of-keyboards.html",target:"_blank",rel:"noopener noreferrer"},j=r(`

    You can add the following to your init.js to send Ctrl+@ when you press Ctrl+Alt+G:

    atom.keymaps.addKeystrokeResolver(({ event }) => {
    +	if (
    +		event.code === "KeyG" &&
    +		event.altKey &&
    +		event.ctrlKey &&
    +		event.type !== "keyup"
    +	) {
    +		return "ctrl-@";
    +	}
    +});
    +

    Or if you are still using the init.coffee file:

    atom.keymaps.addKeystrokeResolver ({event}) ->
    +  if event.code is 'KeyG' and event.altKey and event.ctrlKey and event.type isnt 'keyup'
    +    return 'ctrl-@'
    +
    `,4),I=e("code",null,"event",-1),A={href:"https://flight-manual.atom.io/hacking-atom/sections/debugging/#check-for-errors-in-the-developer-tools",target:"_blank",rel:"noopener noreferrer"},N=r(`
    document.addEventListener("keydown", (e) => console.log(e), true);
    +

    This will print every keypress event in Pulsar to the console so you can inspect KeyboardEvent.key and KeyboardEvent.code.

    `,2);function O(B,W){const c=l("Tabs"),p=l("ExternalLinkIcon");return m(),k("div",null,[y,b,v,i(c,{id:"9",data:[{title:"Linux"},{title:"Mac"},{title:"Windows"}],"tab-id":"behind-pulsar"},{tab0:s(({title:t,value:a,isActive:o})=>[f]),tab1:s(({title:t,value:a,isActive:o})=>[w]),tab2:s(({title:t,value:a,isActive:o})=>[x]),_:1}),_,i(c,{id:"129",data:[{title:"Linux"},{title:"Mac"},{title:"Windows"}],"tab-id":"behind-pulsar"},{tab0:s(({title:t,value:a,isActive:o})=>[C]),tab1:s(({title:t,value:a,isActive:o})=>[q]),tab2:s(({title:t,value:a,isActive:o})=>[S]),_:1}),T,e("p",null,[n("Sometimes the problem isn't mapping the command to a key combination, the problem is that Pulsar doesn't recognize properly what keys you're pressing. This is due to "),e("a",K,[n("some limitations in how Chromium reports keyboard events"),i(p)]),n(". But even this can be customized now.")]),j,e("p",null,[n("If you want to know the "),I,n(" for the keystroke you pressed you can paste the following script to your "),e("a",A,[n("developer tools console"),i(p)]),h("TODO: Convert to Pulsar docs when created")]),N])}const E=u(g,[["render",O],["__file","keymaps-in-depth.html.vue"]]);export{E as default}; diff --git a/assets/keymaps-in-depth.html.70df9d29.js b/assets/keymaps-in-depth.html.70df9d29.js new file mode 100644 index 0000000000..b97c35c20a --- /dev/null +++ b/assets/keymaps-in-depth.html.70df9d29.js @@ -0,0 +1,78 @@ +import{_ as d}from"./keybinding.964a4e0d.js";import{_ as u,o as m,c as k,d as a,w as t,a as e,b as n,f as s,r as c}from"./app.0e1565ce.js";const h={},g=e("h3",{id:"keymaps-in-depth",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#keymaps-in-depth","aria-hidden":"true"},"#"),n(" Keymaps In-Depth")],-1),y=e("h4",{id:"structure-of-a-keymap-file",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#structure-of-a-keymap-file","aria-hidden":"true"},"#"),n(" Structure of a Keymap File")],-1),f=e("p",null,[n("Keymap files are encoded as JSON or CSON files containing nested hashes. They work much like style sheets, but instead of applying style properties to elements matching the selector, they specify the meaning of keystrokes on elements matching the selector. Here is an example of some bindings that apply when keystrokes pass through "),e("code",null,"atom-text-editor"),n(" elements:")],-1),v=e("div",{class:"language-coffee ext-coffee line-numbers-mode"},[e("pre",{class:"language-coffee"},[e("code",null,[e("span",{class:"token string-property property"},"'atom-text-editor'"),e("span",{class:"token operator"},":"),n(` + `),e("span",{class:"token string-property property"},"'ctrl-left'"),e("span",{class:"token operator"},":"),n(),e("span",{class:"token string"},"'editor:move-to-beginning-of-word'"),n(` + `),e("span",{class:"token string-property property"},"'ctrl-right'"),e("span",{class:"token operator"},":"),n(),e("span",{class:"token string"},"'editor:move-to-end-of-word'"),n(` + `),e("span",{class:"token string-property property"},"'ctrl-shift-left'"),e("span",{class:"token operator"},":"),n(),e("span",{class:"token string"},"'editor:select-to-beginning-of-word'"),n(` + `),e("span",{class:"token string-property property"},"'ctrl-shift-right'"),e("span",{class:"token operator"},":"),n(),e("span",{class:"token string"},"'editor:select-to-end-of-word'"),n(` + `),e("span",{class:"token string-property property"},"'ctrl-backspace'"),e("span",{class:"token operator"},":"),n(),e("span",{class:"token string"},"'editor:delete-to-beginning-of-word'"),n(` + `),e("span",{class:"token string-property property"},"'ctrl-delete'"),e("span",{class:"token operator"},":"),n(),e("span",{class:"token string"},"'editor:delete-to-end-of-word'"),n(` + +`),e("span",{class:"token string-property property"},"'atom-text-editor:not([mini])'"),e("span",{class:"token operator"},":"),n(` + `),e("span",{class:"token string-property property"},"'ctrl-alt-['"),e("span",{class:"token operator"},":"),n(),e("span",{class:"token string"},"'editor:fold-current-row'"),n(` + `),e("span",{class:"token string-property property"},"'ctrl-alt-]'"),e("span",{class:"token operator"},":"),n(),e("span",{class:"token string"},"'editor:unfold-current-row'"),n(` +`)])]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),b=e("div",{class:"language-coffee ext-coffee line-numbers-mode"},[e("pre",{class:"language-coffee"},[e("code",null,[e("span",{class:"token string-property property"},"'atom-text-editor'"),e("span",{class:"token operator"},":"),n(` + `),e("span",{class:"token string-property property"},"'cmd-delete'"),e("span",{class:"token operator"},":"),n(),e("span",{class:"token string"},"'editor:delete-to-beginning-of-line'"),n(` + `),e("span",{class:"token string-property property"},"'alt-backspace'"),e("span",{class:"token operator"},":"),n(),e("span",{class:"token string"},"'editor:delete-to-beginning-of-word'"),n(` + `),e("span",{class:"token string-property property"},"'ctrl-A'"),e("span",{class:"token operator"},":"),n(),e("span",{class:"token string"},"'editor:select-to-first-character-of-line'"),n(` + `),e("span",{class:"token string-property property"},"'ctrl-shift-e'"),e("span",{class:"token operator"},":"),n(),e("span",{class:"token string"},"'editor:select-to-end-of-line'"),n(` + `),e("span",{class:"token string-property property"},"'cmd-left'"),e("span",{class:"token operator"},":"),n(),e("span",{class:"token string"},"'editor:move-to-first-character-of-line'"),n(` + +`),e("span",{class:"token string-property property"},"'atom-text-editor:not([mini])'"),e("span",{class:"token operator"},":"),n(` + `),e("span",{class:"token string-property property"},"'cmd-alt-['"),e("span",{class:"token operator"},":"),n(),e("span",{class:"token string"},"'editor:fold-current-row'"),n(` + `),e("span",{class:"token string-property property"},"'cmd-alt-]'"),e("span",{class:"token operator"},":"),n(),e("span",{class:"token string"},"'editor:unfold-current-row'"),n(` +`)])]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),w=e("div",{class:"language-coffee ext-coffee line-numbers-mode"},[e("pre",{class:"language-coffee"},[e("code",null,[e("span",{class:"token string-property property"},"'atom-text-editor'"),e("span",{class:"token operator"},":"),n(` + `),e("span",{class:"token string-property property"},"'ctrl-left'"),e("span",{class:"token operator"},":"),n(),e("span",{class:"token string"},"'editor:move-to-beginning-of-word'"),n(` + `),e("span",{class:"token string-property property"},"'ctrl-right'"),e("span",{class:"token operator"},":"),n(),e("span",{class:"token string"},"'editor:move-to-end-of-word'"),n(` + `),e("span",{class:"token string-property property"},"'ctrl-shift-left'"),e("span",{class:"token operator"},":"),n(),e("span",{class:"token string"},"'editor:select-to-beginning-of-word'"),n(` + `),e("span",{class:"token string-property property"},"'ctrl-shift-right'"),e("span",{class:"token operator"},":"),n(),e("span",{class:"token string"},"'editor:select-to-end-of-word'"),n(` + `),e("span",{class:"token string-property property"},"'ctrl-backspace'"),e("span",{class:"token operator"},":"),n(),e("span",{class:"token string"},"'editor:delete-to-beginning-of-word'"),n(` + `),e("span",{class:"token string-property property"},"'ctrl-delete'"),e("span",{class:"token operator"},":"),n(),e("span",{class:"token string"},"'editor:delete-to-end-of-word'"),n(` + +`),e("span",{class:"token string-property property"},"'atom-text-editor:not([mini])'"),e("span",{class:"token operator"},":"),n(` + `),e("span",{class:"token string-property property"},"'ctrl-alt-['"),e("span",{class:"token operator"},":"),n(),e("span",{class:"token string"},"'editor:fold-current-row'"),n(` + `),e("span",{class:"token string-property property"},"'ctrl-alt-]'"),e("span",{class:"token operator"},":"),n(),e("span",{class:"token string"},"'editor:unfold-current-row'"),n(` +`)])]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),x=s(`

    Beneath the first selector are several keybindings, mapping specific key combinations to commands. When an element with the atom-text-editor class is focused and Alt+BackspaceCtrl+Backspace is pressed, a custom DOM event called editor:delete-to-beginning-of-word is emitted on the atom-text-editor element.

    The second selector group also targets editors, but only if they don't have the mini attribute. In this example, the commands for code folding don't really make sense on mini-editors, so the selector restricts them to regular editors.

    Key Combinations

    Key combinations express one or more keys combined with optional modifier keys. For example: ctrl-w v, or cmd-shift-up. A key combination is composed of the following symbols, separated by a -. A key sequence can be expressed as key combinations separated by spaces.

    TypeExamples
    Character literalsa 4 $
    Modifier keyscmd ctrl alt shift
    Special keysenter escape backspace delete tab home end pageup pagedown left right up down space
    Commands

    Commands are custom DOM events that are triggered when a key combination or sequence matches a binding. This allows user interface code to listen for named commands without specifying the specific keybinding that triggers it. For example, the following code creates a command to insert the current date in an editor:

    atom.commands.add("atom-text-editor", {
    +	"user:insert-date": function (event) {
    +		const editor = this.getModel();
    +		return editor.insertText(new Date().toLocaleString());
    +	},
    +});
    +

    atom.commands refers to the global CommandRegistry instance where all commands are set and consequently picked up by the command palette.

    When you are looking to bind new keys, it is often useful to use the Command Palette (Cmd+Shift+PCtrl+Shift+P) to discover what commands are being listened for in a given focus context. Commands are "humanized" following a simple algorithm, so a command like editor:fold-current-row would appear as "Editor: Fold Current Row".

    "Composed" Commands

    A common question is, "How do I make a single keybinding execute two or more commands?" There isn't any direct support for this in Atom, but it can be achieved by creating a custom command that performs the multiple actions you desire and then creating a keybinding for that command. For example, let's say I want to create a "composed" command that performs a Select Line followed by Cut. You could add the following to your init.coffee:

    atom.commands.add("atom-text-editor", "custom:cut-line", function () {
    +	const editor = this.getModel();
    +	editor.selectLinesContainingCursors();
    +	editor.cutSelectedText();
    +});
    +

    Then let's say we want to map this custom command to alt-ctrl-z, you could add the following to your keymap:

    'atom-text-editor':
    +  'alt-ctrl-z': 'custom:cut-line'
    +
    Specificity and Cascade Order

    As is the case with CSS applying styles, when multiple bindings match for a single element, the conflict is resolved by choosing the most specific selector. If two matching selectors have the same specificity, the binding for the selector appearing later in the cascade takes precedence.

    Currently, there's no way to specify selector ordering within a single keymap, because JSON objects do not preserve order. We handle cases where selector ordering is critical by breaking the keymap into separate files, such as snippets-1.cson and snippets-2.cson.

    Selectors and Custom Packages

    If a keybinding should only apply to a specific grammar, you can limit bindings to that grammar using the data-grammar attribute on the atom-text-editor element:

    "atom-text-editor[data-grammar='source example']":
    +  'ctrl-.': 'custom:custom-command'
    +

    While selectors can be applied to the entire editor by what grammar is associated with it, they cannot be applied to scopes defined within the grammar or to sub-elements of atom-text-editor.

    Removing Bindings

    When the keymap system encounters a binding with the unset! directive as its command, it will treat the current element as if it had no key bindings matching the current keystroke sequence and continue searching from its parent. For example, the following code removes the keybinding for a in the Tree View, which is normally used to trigger the tree-view:add-file command:

    '.tree-view':
    +  'a': 'unset!'
    +

    Keybinding Resolver

    But if some element above the Tree View had a keybinding for a, that keybinding would still execute even when the focus is inside the Tree View.

    When the keymap system encounters a binding with the abort! directive as its command, it will stop searching for a keybinding. For example, the following code removes the keybinding for Cmd+OCtrl+O when the selection is inside an editor pane:

    ::: codetabs#keymaps-in-depth

    `,31),_=s(`
    'atom-text-editor':
    +  'ctrl-o': 'abort!'
    +

    :::

    But if you click inside the Tree View and press Cmd+OCtrl+O, it will work.

    Forcing Chromium's Native Keystroke Handling

    If you want to force the native browser behavior for a given keystroke, use the native! directive as the command of a binding. This can be useful to enable the correct behavior in native input elements. If you apply the .native-key-bindings class to an element, all the keystrokes typically handled by the browser will be assigned the native! directive.

    Tips

    Tip: Components and input elements may not correctly handle backspace and arrow keys without forcing this behavior. If your backspace isn't working correctly inside of a component, add either the directive or the .native-key-bindings class.

    Overloading Key Bindings

    Occasionally, it makes sense to layer multiple actions on top of the same key binding. An example of this is the snippets package. Snippets are inserted by typing a snippet prefix such as for and then pressing Tab. Every time Tab is pressed, we want to execute code attempting to expand a snippet if one exists for the text preceding the cursor. If a snippet doesn't exist, we want Tab to actually insert whitespace.

    To achieve this, the snippets package makes use of the .abortKeyBinding() method on the event object representing the snippets:expand command.

    // pseudo-code
    +editor.command("snippets:expand", (e) => {
    +	if (this.cursorFollowsValidPrefix()) {
    +		this.expandSnippet();
    +	} else {
    +		e.abortKeyBinding();
    +	}
    +});
    +

    When the event handler observes that the cursor does not follow a valid prefix, it calls e.abortKeyBinding(), telling the keymap system to continue searching for another matching binding.

    Step-by-Step: How Keydown Events are Mapped to Commands

    • A keydown event occurs on a focused element.
    • Starting at the focused element, the keymap walks upward towards the root of the document, searching for the most specific CSS selector that matches the current DOM element and also contains a keystroke pattern matching the keydown event.
    • When a matching keystroke pattern is found, the search is terminated and the pattern's corresponding command is triggered on the current element.
    • If .abortKeyBinding() is called on the triggered event object, the search is resumed, triggering a binding on the next-most-specific CSS selector for the same element or continuing upward to parent elements.
    • If no bindings are found, the event is handled by Chromium normally.

    Overriding Atom's Keyboard Layout Recognition

    `,14),C={href:"https://blog.atom.io/2016/10/17/the-wonderful-world-of-keyboards.html",target:"_blank",rel:"noopener noreferrer"},q=s(`

    You can add the following to your init.coffee to send Ctrl+@ when you press Ctrl+Alt+G:

    atom.keymaps.addKeystrokeResolver ({event}) ->
    +  if event.code is 'KeyG' and event.altKey and event.ctrlKey and event.type isnt 'keyup'
    +    return 'ctrl-@'
    +

    Or if you've converted your init script to JavaScript:

    atom.keymaps.addKeystrokeResolver(({ event }) => {
    +	if (
    +		event.code === "KeyG" &&
    +		event.altKey &&
    +		event.ctrlKey &&
    +		event.type !== "keyup"
    +	) {
    +		return "ctrl-@";
    +	}
    +});
    +
    `,4),S=e("code",null,"event",-1),K={href:"https://flight-manual.atom.io/hacking-atom/sections/debugging/#check-for-errors-in-the-developer-tools",target:"_blank",rel:"noopener noreferrer"},T=s(`
    document.addEventListener("keydown", (e) => console.log(e), true);
    +

    This will print every keypress event in Atom to the console so you can inspect KeyboardEvent.key and KeyboardEvent.code.

    `,2);function j(A,I){const l=c("Tabs"),o=c("ExternalLinkIcon");return m(),k("div",null,[g,y,f,a(l,{id:"9",data:[{title:"Linux"},{title:"Mac"},{title:"Windows"}],"tab-id":"keymaps-indepth"},{tab0:t(({title:i,value:p,isActive:r})=>[v]),tab1:t(({title:i,value:p,isActive:r})=>[b]),tab2:t(({title:i,value:p,isActive:r})=>[w]),_:1}),x,e("template",null,[_,e("p",null,[n("Sometimes the problem isn't mapping the command to a key combination, the problem is that Atom doesn't recognize properly what keys you're pressing. This is due to "),e("a",C,[n("some limitations in how Chromium reports keyboard events"),a(o)]),n(". But even this can be customized now.")]),q,e("p",null,[n("If you want to know the "),S,n(" for the keystroke you pressed you can paste the following script to your "),e("a",K,[n("developer tools console"),a(o)])]),T])])}const E=u(h,[["render",j],["__file","keymaps-in-depth.html.vue"]]);export{E as default}; diff --git a/assets/keymaps-in-depth.html.acd7fcb2.js b/assets/keymaps-in-depth.html.acd7fcb2.js new file mode 100644 index 0000000000..fe423ccff4 --- /dev/null +++ b/assets/keymaps-in-depth.html.acd7fcb2.js @@ -0,0 +1 @@ +const e=JSON.parse(`{"key":"v-563ab79c","path":"/docs/launch-manual/sections/behind-pulsar/sections/keymaps-in-depth.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Keymaps In-Depth","slug":"keymaps-in-depth","link":"#keymaps-in-depth","children":[{"level":3,"title":"Structure of a Keymap File","slug":"structure-of-a-keymap-file","link":"#structure-of-a-keymap-file","children":[]},{"level":3,"title":"Removing Bindings","slug":"removing-bindings","link":"#removing-bindings","children":[]},{"level":3,"title":"Forcing Chromium's Native Keystroke Handling","slug":"forcing-chromium-s-native-keystroke-handling","link":"#forcing-chromium-s-native-keystroke-handling","children":[]},{"level":3,"title":"Overloading Key Bindings","slug":"overloading-key-bindings","link":"#overloading-key-bindings","children":[]},{"level":3,"title":"Step-by-Step: How Keydown Events are Mapped to Commands","slug":"step-by-step-how-keydown-events-are-mapped-to-commands","link":"#step-by-step-how-keydown-events-are-mapped-to-commands","children":[]},{"level":3,"title":"Overriding Pulsar's Keyboard Layout Recognition","slug":"overriding-pulsar-s-keyboard-layout-recognition","link":"#overriding-pulsar-s-keyboard-layout-recognition","children":[]}]}],"git":{"updatedTime":1668711072000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":5.67,"words":1701},"filePathRelative":"docs/launch-manual/sections/behind-pulsar/sections/keymaps-in-depth.md"}`);export{e as data}; diff --git a/assets/lemmy-icon.093c6158.png b/assets/lemmy-icon.093c6158.png new file mode 100644 index 0000000000..e60dcb1ed2 Binary files /dev/null and b/assets/lemmy-icon.093c6158.png differ diff --git a/assets/less.7be58a37.png b/assets/less.7be58a37.png new file mode 100644 index 0000000000..794976b145 Binary files /dev/null and b/assets/less.7be58a37.png differ diff --git a/assets/linux-downloads.66db3662.png b/assets/linux-downloads.66db3662.png new file mode 100644 index 0000000000..4244b1caf1 Binary files /dev/null and b/assets/linux-downloads.66db3662.png differ diff --git a/assets/list.html.c39f324c.js b/assets/list.html.c39f324c.js new file mode 100644 index 0000000000..70481dc7c2 --- /dev/null +++ b/assets/list.html.c39f324c.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-4119e722","path":"/docs/packages/core/atom-languageclient/list.html","title":"List of Pulsar Packages using Pulsar LanguageClient","lang":"en-us","frontmatter":{"lang":"en-us","title":"List of Pulsar Packages using Pulsar LanguageClient","prev":"/docs/packages/core/atom-languageclient/"},"excerpt":"","headers":[{"level":2,"title":"Official Packages","slug":"official-packages","link":"#official-packages","children":[]},{"level":2,"title":"Community Packages","slug":"community-packages","link":"#community-packages","children":[]}],"git":{"updatedTime":1664399092000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":3}]},"readingTime":{"minutes":1.18,"words":355},"filePathRelative":"docs/packages/core/atom-languageclient/list.md"}');export{e as data}; diff --git a/assets/list.html.fb443d07.js b/assets/list.html.fb443d07.js new file mode 100644 index 0000000000..0fda30ee47 --- /dev/null +++ b/assets/list.html.fb443d07.js @@ -0,0 +1 @@ +import{_ as o,o as n,c as s,a as r,b as e,d as a,f as l,r as i}from"./app.0e1565ce.js";const h={},c=l('

    List of Pulsar Packages using atom-languageclient

    Note

    Please note that its possible this list is outdated, as its original version was published by @'Damien Guard' on Apr 21, 2018.

    Here are the known Pulsar packages that are using atom-languageclient today:

    Official Packages

    ',4),g={href:"https://github.com/pulsar-edit/ide-csharp",target:"_blank",rel:"noopener noreferrer"},p={href:"https://github.com/flowtype/ide-flowtype",target:"_blank",rel:"noopener noreferrer"},u={href:"https://github.com/pulsar-edit/ide-java",target:"_blank",rel:"noopener noreferrer"},d={href:"https://github.com/pulsar-edit/ide-json",target:"_blank",rel:"noopener noreferrer"},_={href:"https://github.com/pulsar-edit/ide-typescript",target:"_blank",rel:"noopener noreferrer"},f=r("h2",{id:"community-packages",tabindex:"-1"},[r("a",{class:"header-anchor",href:"#community-packages","aria-hidden":"true"},"#"),e(" Community Packages")],-1),b={href:"https://github.com/mads-hartmann/ide-bash",target:"_blank",rel:"noopener noreferrer"},m={href:"https://github.com/mads-hartmann/bash-language-server",target:"_blank",rel:"noopener noreferrer"},k={href:"https://atom.io/packages/ide-docker",target:"_blank",rel:"noopener noreferrer"},v={href:"https://github.com/rcjsuen/dockerfile-language-server-nodejs",target:"_blank",rel:"noopener noreferrer"},S={href:"https://atom.io/packages/ide-fortran",target:"_blank",rel:"noopener noreferrer"},y={href:"https://github.com/hansec/fortran-language-server",target:"_blank",rel:"noopener noreferrer"},w={href:"https://github.com/Tehnix/ide-haskell-hie",target:"_blank",rel:"noopener noreferrer"},P={href:"https://github.com/haskell/haskell-ide-engine",target:"_blank",rel:"noopener noreferrer"},L={href:"https://github.com/atom/ide-php",target:"_blank",rel:"noopener noreferrer"},x={href:"https://github.com/daviwil/ide-powershell",target:"_blank",rel:"noopener noreferrer"},E={href:"https://github.com/PowerShell/PowerShellEditorServices",target:"_blank",rel:"noopener noreferrer"},V={href:"https://github.com/lgeiger/ide-python",target:"_blank",rel:"noopener noreferrer"},B={href:"https://github.com/palantir/python-language-server",target:"_blank",rel:"noopener noreferrer"},D={href:"https://github.com/zaaack/atom-ide-reason",target:"_blank",rel:"noopener noreferrer"},F={href:"https://github.com/freebroccolo/ocaml-language-server",target:"_blank",rel:"noopener noreferrer"},N={href:"https://github.com/mehcode/atom-ide-rust",target:"_blank",rel:"noopener noreferrer"},C={href:"https://github.com/rust-lang-nursery/rls",target:"_blank",rel:"noopener noreferrer"},O={href:"https://github.com/laughedelic/atom-ide-scala",target:"_blank",rel:"noopener noreferrer"},j={href:"https://github.com/scalameta/metals",target:"_blank",rel:"noopener noreferrer"},H={href:"http://dotty.epfl.ch/docs/usage/ide-support.html",target:"_blank",rel:"noopener noreferrer"},R={href:"https://github.com/rwatts3/atom-ide-vue",target:"_blank",rel:"noopener noreferrer"},T={href:"https://www.npmjs.com/package/vue-language-server",target:"_blank",rel:"noopener noreferrer"};function J(I,z){const t=i("ExternalLinkIcon");return n(),s("div",null,[c,r("ul",null,[r("li",null,[r("a",g,[e("C#"),a(t)]),e(" based on Omnisharp.")]),r("li",null,[r("a",p,[e("Flow"),a(t)]),e(" from Facebook.")]),r("li",null,[r("a",u,[e("Java"),a(t)]),e(" based on Eclipse JDT.")]),r("li",null,[r("a",d,[e("JSON"),a(t)]),e(" based on VSCode JSON language server.")]),r("li",null,[r("a",_,[e("TypeScript"),a(t)]),e(" based on TypeScript server.")])]),f,r("ul",null,[r("li",null,[r("a",b,[e("Bash"),a(t)]),e(" provides Bash language support using "),r("a",m,[e("Bash Language Server"),a(t)]),e(".")]),r("li",null,[r("a",k,[e("Docker"),a(t)]),e(" provides Dockerfile language support using the "),r("a",v,[e("Dockerfile Language Server"),a(t)]),e(".")]),r("li",null,[r("a",S,[e("Fortran"),a(t)]),e(" provides Fortran language support using the "),r("a",y,[e("hansec's Fortran Language Server"),a(t)]),e(".")]),r("li",null,[r("a",w,[e("Haskell-hie"),a(t)]),e(" provides Haskell support via the "),r("a",P,[e("Haskell IDE Engine"),a(t)]),e(".")]),r("li",null,[r("a",L,[e("PHP"),a(t)]),e(" based on FelixFBecker's Language Server.")]),r("li",null,[r("a",x,[e("PowerShell"),a(t)]),e(" provides PowerShell language support using "),r("a",E,[e("PowerShell Editor Services"),a(t)]),e(".")]),r("li",null,[r("a",V,[e("Python"),a(t)]),e(" provides Python support via "),r("a",B,[e("Python Language Server"),a(t)]),e(".")]),r("li",null,[r("a",D,[e("Reason"),a(t)]),e(" provides OCaml and Reason support using "),r("a",F,[e("OCaml Language Server"),a(t)]),e(".")]),r("li",null,[r("a",N,[e("Rust"),a(t)]),e(" provides Rust support using "),r("a",C,[e("Rust Language Server"),a(t)]),e(".")]),r("li",null,[r("a",O,[e("Scala"),a(t)]),e(" provides Scala language support using "),r("a",j,[e("Metals"),a(t)]),e(" and "),r("a",H,[e("Dotty Language Server"),a(t)]),e(".")]),r("li",null,[r("a",R,[e("Vue"),a(t)]),e(" provides Vue language support using the "),r("a",T,[e("Vue Language Server"),a(t)]),e(".")])])])}const G=o(h,[["render",J],["__file","list.html.vue"]]);export{G as default}; diff --git a/assets/mac-downloads.a1802ab2.png b/assets/mac-downloads.a1802ab2.png new file mode 100644 index 0000000000..7b54a53307 Binary files /dev/null and b/assets/mac-downloads.a1802ab2.png differ diff --git a/assets/macos-mojave-font-rendering-change.html.47686ca6.js b/assets/macos-mojave-font-rendering-change.html.47686ca6.js new file mode 100644 index 0000000000..be47e1d17a --- /dev/null +++ b/assets/macos-mojave-font-rendering-change.html.47686ca6.js @@ -0,0 +1,4 @@ +import{_ as s,o as i,c as r,a as t,b as e,d as a,f as o,r as l}from"./app.0e1565ce.js";const c={},h=t("h3",{id:"macos-mojave-font-rendering-change",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#macos-mojave-font-rendering-change","aria-hidden":"true"},"#"),e(" MacOS Mojave font rendering change")],-1),d={href:"https://www.apple.com/macos/mojave/",target:"_blank",rel:"noopener noreferrer"},p={href:"https://github.com/atom/atom/issues/17486",target:"_blank",rel:"noopener noreferrer"},m=o('

    If this change is something that you dislike, there are a couple workarounds that the community has identified.

    Change the OS defaults

    1. Execute at the Terminal: defaults write -g CGFontRenderingFontSmoothingDisabled -bool NO
    2. Completely exit Atom
    3. Start Atom again

    This appears to re-enable the old "LCD font smoothing" option that was removed in Mojave. It is important to note that this is an OS-wide change.

    Change your font weight

    ',5),u={href:"https://flight-manual.atom.io/using-atom/sections/basic-customization/#style-tweaks",target:"_blank",rel:"noopener noreferrer"},g=o(`
    atom-text-editor {
    +	font-weight: bold;
    +}
    +

    This has the benefit of being a change local to Atom only if the rest of the OS looks fine to you.

    `,2);function f(_,b){const n=l("ExternalLinkIcon");return i(),r("div",null,[h,t("p",null,[e("In "),t("a",d,[e("macOS Mojave v10.14.x"),a(n)]),e(', Apple disabled subpixel antialiasing on all monitors by default. Previous to Mojave, subpixel antialiasing was disabled only on Retina displays or on all displays if the "LCD font smoothing" option was disabled in System Preferences. With this change in Mojave, some users have reported that '),t("a",p,[e('their fonts in Atom appear "thinner" or "dimmer" than they did previously.'),a(n)]),e(" It can look better or worse depending on your font and theme selections, but in all cases this is completely a side-effect of the change that Apple made to their font rendering and is outside Atom's and Electron's control.")]),m,t("p",null,[e("Add the following to "),t("a",u,[e("your stylesheet"),a(n)]),e(":")]),g])}const y=s(c,[["render",f],["__file","macos-mojave-font-rendering-change.html.vue"]]);export{y as default}; diff --git a/assets/macos-mojave-font-rendering-change.html.d33ba74d.js b/assets/macos-mojave-font-rendering-change.html.d33ba74d.js new file mode 100644 index 0000000000..93d1ccc71b --- /dev/null +++ b/assets/macos-mojave-font-rendering-change.html.d33ba74d.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-3e42c98a","path":"/docs/atom-archive/faq/sections/macos-mojave-font-rendering-change.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"MacOS Mojave font rendering change","slug":"macos-mojave-font-rendering-change","link":"#macos-mojave-font-rendering-change","children":[]}],"git":{"updatedTime":1667690985000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1},{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":0.79,"words":237},"filePathRelative":"docs/atom-archive/faq/sections/macos-mojave-font-rendering-change.md"}');export{e as data}; diff --git a/assets/maintaining-a-fork-of-a-core-package-in-atom-atom.html.81c74f67.js b/assets/maintaining-a-fork-of-a-core-package-in-atom-atom.html.81c74f67.js new file mode 100644 index 0000000000..b514262ce4 --- /dev/null +++ b/assets/maintaining-a-fork-of-a-core-package-in-atom-atom.html.81c74f67.js @@ -0,0 +1,10 @@ +import{_ as i,o as r,c as s,a as e,b as t,d as a,f as n,r as c}from"./app.0e1565ce.js";const d={},m=e("h3",{id:"maintaining-a-fork-of-a-core-package-in-atom-atom",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#maintaining-a-fork-of-a-core-package-in-atom-atom","aria-hidden":"true"},"#"),t(" Maintaining a Fork of a Core Package in atom/atom")],-1),l={href:"https://github.com/atom/atom/blob/master/docs/rfcs/003-consolidate-core-packages.md",target:"_blank",rel:"noopener noreferrer"},h={href:"https://github.com/atom/atom",target:"_blank",rel:"noopener noreferrer"},u={href:"https://github.com/atom/one-light-ui",target:"_blank",rel:"noopener noreferrer"},g={href:"https://github.com/atom/atom/tree/master/packages/one-light-ui",target:"_blank",rel:"noopener noreferrer"},p=e("code",null,"packages/one-light-ui",-1),f=e("p",null,"If you forked one of the core packages before it was moved into the atom/atom repository, and you want to continue merging upstream changes into your fork, please follow the steps below.",-1),_=e("h4",{id:"step-by-step-guide",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#step-by-step-guide","aria-hidden":"true"},"#"),t(" Step-by-step guide")],-1),k={href:"https://github.com/atom/one-light-ui",target:"_blank",rel:"noopener noreferrer"},b=e("code",null,"one-light-ui-plus",-1),v=n(`
    Add atom/atom as a Remote

    Navigate to your local clone of your fork:

    $ cd path/to/your/fork
    +
    `,3),y={href:"https://github.com/atom/atom",target:"_blank",rel:"noopener noreferrer"},x=n(`
    $ git remote add upstream https://github.com/atom/atom.git
    +
    Get the Latest Changes for the Core Package

    Tip

    Tip: Follow these steps each time you want to merge upstream changes into your fork.

    Fetch the latest changes from the atom/atom repository:

    $ git fetch upstream
    +

    Identify recent changes to the core package. For example, if you're maintaining a fork of the one-light-ui package, then you'll want to identify recent changes in the packages/one-light-ui directory:

    $ git log upstream/master -- packages/one-light-ui
    +8ac9919a0 Bump up border size (Hugh Baht, 17 minutes ago)
    +3bf4d226e Remove obsolete build status link in one-light-ui README (Jason Rudolph, 3 days ago)
    +3edf64ad0 Merge pull request #42 from atom/sm-select-list (simurai, 2 weeks ago)
    +...
    +

    Look through the log and identify the commits that you want to merge into your fork.

    Merge Upstream Changes into Your Fork
    `,9),w={href:"https://git-scm.com/docs/git-format-patch",target:"_blank",rel:"noopener noreferrer"},A=e("code",null,"git format-patch",-1),F={href:"https://git-scm.com/docs/git-am",target:"_blank",rel:"noopener noreferrer"},C=e("code",null,"git am",-1),I=e("code",null,"8ac9919a0",-1),B=e("div",{class:"language-command-line ext-command-line line-numbers-mode"},[e("pre",{class:"language-command-line"},[e("code",null,`$ git format-patch -1 --stdout 8ac9919a0 | git am -p3 +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"})])],-1),E=e("p",null,"Repeat this step for each commit that you want to merge into your fork.",-1);function N(R,$){const o=c("ExternalLinkIcon");return r(),s("div",null,[m,e("p",null,[t("Originally, each of Atom's core packages resided in a separate repository. In 2018, in an effort to streamline the development of Atom by reducing overhead, the Atom team "),e("a",l,[t("consolidated many core Atom packages"),a(o)]),t(" into the "),e("a",h,[t("atom/atom repository"),a(o)]),t(". For example, the one-light-ui package was originally maintained in the "),e("a",u,[t("atom/one-light-ui"),a(o)]),t(" repository, but it is now maintained in the "),e("a",g,[p,t(" directory in the atom/atom repository"),a(o)]),t(".")]),f,_,e("p",null,[t("For the sake of this guide, let's assume that you forked the "),e("a",k,[t("atom/one-light-ui"),a(o)]),t(" repository, renamed your fork to "),b,t(", and made some customizations.")]),v,e("p",null,[t("Add the "),e("a",y,[t("atom/atom repository"),a(o)]),t(" as a git remote:")]),x,e("p",null,[t("For each commit that you want to bring into your fork, use "),e("a",w,[A,a(o)]),t(" in conjunction with "),e("a",F,[C,a(o)]),t(". For example, to merge commit "),I,t(" into your fork:")]),B,E])}const M=i(d,[["render",N],["__file","maintaining-a-fork-of-a-core-package-in-atom-atom.html.vue"]]);export{M as default}; diff --git a/assets/maintaining-a-fork-of-a-core-package-in-atom-atom.html.b59d275a.js b/assets/maintaining-a-fork-of-a-core-package-in-atom-atom.html.b59d275a.js new file mode 100644 index 0000000000..89d4bc0d07 --- /dev/null +++ b/assets/maintaining-a-fork-of-a-core-package-in-atom-atom.html.b59d275a.js @@ -0,0 +1 @@ +const a=JSON.parse('{"key":"v-be0ab716","path":"/docs/atom-archive/hacking-atom/sections/maintaining-a-fork-of-a-core-package-in-atom-atom.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"Maintaining a Fork of a Core Package in atom/atom","slug":"maintaining-a-fork-of-a-core-package-in-atom-atom","link":"#maintaining-a-fork-of-a-core-package-in-atom-atom","children":[]}],"git":{"updatedTime":1664050274000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":3}]},"readingTime":{"minutes":1.6,"words":479},"filePathRelative":"docs/atom-archive/hacking-atom/sections/maintaining-a-fork-of-a-core-package-in-atom-atom.md"}');export{a as data}; diff --git a/assets/maintaining-a-fork-of-a-core-package.html.d089d3ba.js b/assets/maintaining-a-fork-of-a-core-package.html.d089d3ba.js new file mode 100644 index 0000000000..157a121493 --- /dev/null +++ b/assets/maintaining-a-fork-of-a-core-package.html.d089d3ba.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-05ecf928","path":"/docs/launch-manual/sections/core-hacking/sections/maintaining-a-fork-of-a-core-package.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Maintaining a Fork of a Core Package","slug":"maintaining-a-fork-of-a-core-package","link":"#maintaining-a-fork-of-a-core-package","children":[{"level":3,"title":"Step-by-step guide","slug":"step-by-step-guide","link":"#step-by-step-guide","children":[]}]}],"git":{"updatedTime":1671044195000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":1.56,"words":468},"filePathRelative":"docs/launch-manual/sections/core-hacking/sections/maintaining-a-fork-of-a-core-package.md"}');export{e as data}; diff --git a/assets/maintaining-a-fork-of-a-core-package.html.d4161206.js b/assets/maintaining-a-fork-of-a-core-package.html.d4161206.js new file mode 100644 index 0000000000..a88741a6ef --- /dev/null +++ b/assets/maintaining-a-fork-of-a-core-package.html.d4161206.js @@ -0,0 +1,10 @@ +import{_ as s,o as r,c as i,a,b as e,d as o,f as n,r as c}from"./app.0e1565ce.js";const d={},l=a("h2",{id:"maintaining-a-fork-of-a-core-package",tabindex:"-1"},[a("a",{class:"header-anchor",href:"#maintaining-a-fork-of-a-core-package","aria-hidden":"true"},"#"),e(" Maintaining a Fork of a Core Package")],-1),p={href:"https://github.com/atom/atom/blob/master/docs/rfcs/003-consolidate-core-packages.md",target:"_blank",rel:"noopener noreferrer"},h={href:"https://github.com/atom/atom",target:"_blank",rel:"noopener noreferrer"},u={href:"https://github.com/atom/one-light-ui",target:"_blank",rel:"noopener noreferrer"},m={href:"https://github.com/pulsar-edit/one-light-ui",target:"_blank",rel:"noopener noreferrer"},g=a("code",null,"packages/one-light-ui",-1),f={href:"https://github.com/pulsar-edit/pulsar/blob/master/packages/README.md",target:"_blank",rel:"noopener noreferrer"},k=a("p",null,"If you forked one of the core packages before it was moved into the atom/atom or pulsar-edit/pulsar repository, and you want to continue merging upstream changes into your fork, please follow the steps below.",-1),b=a("h3",{id:"step-by-step-guide",tabindex:"-1"},[a("a",{class:"header-anchor",href:"#step-by-step-guide","aria-hidden":"true"},"#"),e(" Step-by-step guide")],-1),v={href:"https://github.com/pulsar-edit/one-light-ui",target:"_blank",rel:"noopener noreferrer"},_=a("code",null,"one-light-ui-plus",-1),y=n(`

    Add pulsar-edit/pulsar as a Remote

    Navigate to your local clone of your fork:

    $ cd path/to/your/fork
    +
    `,3),x={href:"https://github.com/pulsar-edit/pulsar",target:"_blank",rel:"noopener noreferrer"},w=n(`
    $ git remote add upstream https://github.com/pulsar-edit/pulsar.git
    +

    Get the Latest Changes for the Core Package

    Tip

    Follow these steps each time you want to merge upstream changes into your fork.

    Fetch the latest changes from the pulsar-edit/pulsar repository:

    $ git fetch upstream
    +

    Identify recent changes to the core package. For example, if you're maintaining a fork of the one-light-ui package, then you'll want to identify recent changes in the packages/one-light-ui directory:

    $ git log --oneline upstream/master -- packages/one-light-ui
    +f884f6de8 [themes] Rename A[a]tom -> P[p]ulsar
    +0db3190f4 Additional rebranding where needed
    +234adb874 Remove deprecated code strings
    +...
    +

    Look through the log and identify the commits that you want to merge into your fork.

    Merge Upstream Changes into Your Fork

    For each commit that you want to bring into your fork, use [git format-patch][https://git-scm.com/docs/git-format-patch] in conjunction with [git am][https://git-scm.com/docs/git-am]. For example, to merge commit f884f6de8 into your fork:

    $ git format-patch -1 --stdout f884f6de8 | git am -p3
    +

    Repeat this step for each commit that you want to merge into your fork.

    `,12);function A(F,C){const t=c("ExternalLinkIcon");return r(),i("div",null,[l,a("p",null,[e("Originally, each of Atom's core packages resided in a separate repository. In 2018, in an effort to streamline the development of Atom by reducing overhead, the Atom team "),a("a",p,[e("consolidated many core Atom packages"),o(t)]),e(" into the "),a("a",h,[e("atom/atom repository"),o(t)]),e(". For example, the one-light-ui package was originally maintained in the "),a("a",u,[e("atom/one-light-ui"),o(t)]),e(" repository, but was moved to the "),a("a",m,[g,e(" directory"),o(t)]),e(" in the main repository.")]),a("p",null,[e("The Pulsar team has continued this trend and has move even more packages into the core, particularly default language packages. A list of these packages moved can be found in "),a("a",f,[e("this document"),o(t)]),e(".")]),k,b,a("p",null,[e("For the sake of this guide, let's assume that you forked the "),a("a",v,[e("pulsar-edit/one-light-ui"),o(t)]),e(" repository, renamed your fork to "),_,e(", and made some customizations.")]),y,a("p",null,[e("Add the "),a("a",x,[e("pulsar-edit/pulsar repository"),o(t)]),e(" as a git remote:")]),w])}const I=s(d,[["render",A],["__file","maintaining-a-fork-of-a-core-package.html.vue"]]);export{I as default}; diff --git a/assets/maintaining-your-packages.html.4eb25dae.js b/assets/maintaining-your-packages.html.4eb25dae.js new file mode 100644 index 0000000000..9daa8eb0fd --- /dev/null +++ b/assets/maintaining-your-packages.html.4eb25dae.js @@ -0,0 +1 @@ +const a=JSON.parse('{"key":"v-322ec6da","path":"/docs/launch-manual/sections/behind-pulsar/sections/maintaining-your-packages.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Maintaining Your Packages","slug":"maintaining-your-packages","link":"#maintaining-your-packages","children":[{"level":3,"title":"Publishing a Package Manually","slug":"publishing-a-package-manually","link":"#publishing-a-package-manually","children":[]},{"level":3,"title":"Adding a Collaborator","slug":"adding-a-collaborator","link":"#adding-a-collaborator","children":[]},{"level":3,"title":"Transferring Ownership","slug":"transferring-ownership","link":"#transferring-ownership","children":[]},{"level":3,"title":"Unpublish Your Package","slug":"unpublish-your-package","link":"#unpublish-your-package","children":[]},{"level":3,"title":"Rename Your Package","slug":"rename-your-package","link":"#rename-your-package","children":[]}]}],"git":{"updatedTime":1668992424000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":4}]},"readingTime":{"minutes":2.79,"words":837},"filePathRelative":"docs/launch-manual/sections/behind-pulsar/sections/maintaining-your-packages.md"}');export{a as data}; diff --git a/assets/maintaining-your-packages.html.641bcc0a.js b/assets/maintaining-your-packages.html.641bcc0a.js new file mode 100644 index 0000000000..016cab26d7 --- /dev/null +++ b/assets/maintaining-your-packages.html.641bcc0a.js @@ -0,0 +1,4 @@ +import{_ as r,o as s,c as i,a as e,b as a,d as n,f as t,r as c}from"./app.0e1565ce.js";const l={},h=t('

    Maintaining Your Packages

    While publishing is, by far, the most common action you will perform when working with the packages you provide, there are other things you may need to do.

    Publishing a Package Manually

    Warning

    Danger: \u{1F6A8} Publishing a package manually is not a recommended practice and is only for the advanced user who has published packages before. If you perform the steps wrong, you may be unable to publish the new version of your package and may have to completely unpublish your package in order to correct the faulty state. You have been warned.

    Some people prefer to control every aspect of the package publishing process. Normally, the apm tool manages certain details during publishing to keep things consistent and make everything work smoothly. If you're one of those people that prefers to do things manually, there are certain steps you'll have to take in order to make things work just as smoothly as if apm has taken care of things for you.

    ',5),d={class:"custom-container note"},p=e("p",{class:"custom-container-title"},"Note",-1),u=e("strong",null,"Note:",-1),g={href:"https://github.com",target:"_blank",rel:"noopener noreferrer"},m=t('

    When you have completed the changes that you want to publish and are ready to start the publishing process, you must perform the following steps on the master branch:

    1. Update the version number in your package's package.json. The version number must match the regular expression: ^\\d+\\.\\d+\\.\\d+
    2. Commit the version number change
    3. Create a Git tag referencing the above commit. The tag must match the regular expression ^v\\d+\\.\\d+\\.\\d+ and the part after the v must match the full text of the version number in the package.json
    4. Execute git push --follow-tags
    5. Execute apm publish --tag tagname where tagname must match the name of the tag created in the above step

    Adding a Collaborator

    ',3),b={href:"https://help.github.com/articles/adding-collaborators-to-a-personal-repository/",target:"_blank",rel:"noopener noreferrer"},y=e("em",null,"Note:",-1),f={href:"https://help.github.com/articles/creating-a-new-organization-account/",target:"_blank",rel:"noopener noreferrer"},k={href:"https://help.github.com/articles/permission-levels-for-an-organization/",target:"_blank",rel:"noopener noreferrer"},v=e("h4",{id:"transferring-ownership",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#transferring-ownership","aria-hidden":"true"},"#"),a(" Transferring Ownership")],-1),w=e("div",{class:"custom-container danger"},[e("p",{class:"custom-container-title"},"Warning"),e("p",null,[e("strong",null,"Danger:"),a(" \u{1F6A8} This is a permanent change. There is no going back! \u{1F6A8}")])],-1),_={href:"https://help.github.com/articles/transferring-a-repository/",target:"_blank",rel:"noopener noreferrer"},x=e("code",null,"package.json",-1),T=t(`

    Unpublish Your Package

    If you no longer want to support your package and cannot find anyone to take it over, you can unpublish your package from https://atom.io. For example, if your package is named package-name then the command you would execute is:

    $ apm unpublish <em>package-name</em>
    +

    This will remove your package from the https://atom.io package registry. Anyone who has already downloaded a copy of your package will still have it and be able to use it, but it will no longer be available for installation by others.

    Unpublish a Specific Version

    If you mistakenly published a version of your package or perhaps you find a glaring bug or security hole, you may want to unpublish just that version of your package. For example, if your package is named package-name and the bad version of your package is v1.2.3 then the command you would execute is:

    $ apm unpublish <em>package-name@1.2.3</em>
    +

    This will remove just this particular version from the https://atom.io package registry.

    Rename Your Package

    If you need to rename your package for any reason, you can do so with one simple command \u2013 apm publish --rename changes the name field in your package's package.json, pushes a new commit and tag, and publishes your renamed package. Requests made to the previous name will be forwarded to the new name.

    $ apm publish --rename <em>new-package-name</em>
    +

    Tips

    Tip: Once a package name has been used, it cannot be re-used by another package even if the original package is unpublished.

    `,12);function I(N,j){const o=c("ExternalLinkIcon");return s(),i("div",null,[h,e("div",d,[p,e("p",null,[u,a(" The apm tool will only publish and https://atom.io will only list packages that are hosted on "),e("a",g,[a("GitHub"),n(o)]),a(", regardless of what process is used to publish them.")])]),m,e("p",null,[a("Some packages get too big for one person. Sometimes priorities change and someone else wants to help out. You can let others help or create co-owners by "),e("a",b,[a("adding them as a collaborator"),n(o)]),a(" on the GitHub repository for your package. "),y,a(" Anyone that has push access to your repository will have the ability to publish new versions of the package that belongs to that repository.")]),e("p",null,[a("You can also have packages that are owned by a "),e("a",f,[a("GitHub organization"),n(o)]),a(". Anyone who is a member of an organization's "),e("a",k,[a("team"),n(o)]),a(" which has push access to the package's repository will be able to publish new versions of the package.")]),v,w,e("p",null,[a("If you want to hand off support of your package to someone else, you can do that by "),e("a",_,[a("transferring the package's repository"),n(o)]),a(" to the new owner. Once you do that, they can publish a new version with the updated repository information in the "),x,a(".")]),T])}const Y=r(l,[["render",I],["__file","maintaining-your-packages.html.vue"]]);export{Y as default}; diff --git a/assets/maintaining-your-packages.html.66a1baf7.js b/assets/maintaining-your-packages.html.66a1baf7.js new file mode 100644 index 0000000000..fc9d7de9e8 --- /dev/null +++ b/assets/maintaining-your-packages.html.66a1baf7.js @@ -0,0 +1 @@ +const a=JSON.parse('{"key":"v-ee9016a0","path":"/docs/atom-archive/behind-atom/sections/maintaining-your-packages.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"Maintaining Your Packages","slug":"maintaining-your-packages","link":"#maintaining-your-packages","children":[]}],"git":{"updatedTime":1668309800000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":3},{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":2.65,"words":795},"filePathRelative":"docs/atom-archive/behind-atom/sections/maintaining-your-packages.md"}');export{a as data}; diff --git a/assets/maintaining-your-packages.html.f9de6122.js b/assets/maintaining-your-packages.html.f9de6122.js new file mode 100644 index 0000000000..809427b3c2 --- /dev/null +++ b/assets/maintaining-your-packages.html.f9de6122.js @@ -0,0 +1,4 @@ +import{_ as s,o as r,c as i,a,b as e,d as t,f as n,r as l}from"./app.0e1565ce.js";const c={},p=n('

    Maintaining Your Packages

    Pre-release information

    This section is about a feature in pre-release. The information below documents the intended functionality but there is still ongoing work to support these features with stability.

    While publishing is, by far, the most common action you will perform when working with the packages you provide, there are other things you may need to do.

    Publishing a Package Manually

    STOP

    Publishing a package manually is not a recommended practice and is only for the advanced user who has published packages before. If you perform the steps wrong, you may be unable to publish the new version of your package and may have to completely unpublish your package in order to correct the faulty state. You have been warned.

    Some people prefer to control every aspect of the package publishing process. Normally, the ppm tool manages certain details during publishing to keep things consistent and make everything work smoothly. If you're one of those people that prefers to do things manually, there are certain steps you'll have to take in order to make things work just as smoothly as if ppm has taken care of things for you.

    ',6),h={class:"custom-container note"},d=a("p",{class:"custom-container-title"},"Note",-1),u=a("strong",null,"Note:",-1),g={href:"https://web.pulsar-edit.dev",target:"_blank",rel:"noopener noreferrer"},m={href:"https://github.com",target:"_blank",rel:"noopener noreferrer"},b=n('

    When you have completed the changes that you want to publish and are ready to start the publishing process, you must perform the following steps on the master branch:

    1. Update the version number in your package's package.json. The version number must match the regular expression: ^\\d+\\.\\d+\\.\\d+
    2. Commit the version number change
    3. Create a Git tag referencing the above commit. The tag must match the regular expression ^v\\d+\\.\\d+\\.\\d+ and the part after the v must match the full text of the version number in the package.json
    4. Execute git push --follow-tags
    5. Execute pulsar -p publish --tag tagname where tagname must match the name of the tag created in the above step

    Adding a Collaborator

    ',3),f={href:"https://help.github.com/articles/adding-collaborators-to-a-personal-repository/",target:"_blank",rel:"noopener noreferrer"},y=a("em",null,"Note:",-1),k={href:"https://help.github.com/articles/creating-a-new-organization-account/",target:"_blank",rel:"noopener noreferrer"},v={href:"https://help.github.com/articles/permission-levels-for-an-organization/",target:"_blank",rel:"noopener noreferrer"},_=a("h3",{id:"transferring-ownership",tabindex:"-1"},[a("a",{class:"header-anchor",href:"#transferring-ownership","aria-hidden":"true"},"#"),e(" Transferring Ownership")],-1),w=a("div",{class:"custom-container danger"},[a("p",{class:"custom-container-title"},"STOP"),a("p",null,"\u{1F6A8} This is a permanent change. There is no going back! \u{1F6A8}")],-1),x={href:"https://help.github.com/articles/transferring-a-repository/",target:"_blank",rel:"noopener noreferrer"},T=a("code",null,"package.json",-1),P=a("h3",{id:"unpublish-your-package",tabindex:"-1"},[a("a",{class:"header-anchor",href:"#unpublish-your-package","aria-hidden":"true"},"#"),e(" Unpublish Your Package")],-1),I={href:"https://web.pulsar-edit.dev",target:"_blank",rel:"noopener noreferrer"},N=a("code",null,"package-name",-1),j=n(`
    $ pulsar -p unpublish <package-name>
    +
    `,1),S={href:"https://web.pulsar-edit.dev",target:"_blank",rel:"noopener noreferrer"},Y=n(`

    Unpublish a Specific Version

    If you mistakenly published a version of your package or perhaps you find a glaring bug or security hole, you may want to unpublish just that version of your package. For example, if your package is named package-name and the bad version of your package is v1.2.3 then the command you would execute is:

    $ pulsar -p unpublish <package-name@1.2.3>
    +
    `,3),E={href:"https://web.pulsar-edit.dev",target:"_blank",rel:"noopener noreferrer"},O=n(`

    Rename Your Package

    If you need to rename your package for any reason, you can do so with one simple command \u2013 pulsar -p publish --rename changes the name field in your package's package.json, pushes a new commit and tag, and publishes your renamed package. Requests made to the previous name will be forwarded to the new name.

    $ pulsar -p publish --rename <new-package-name>
    +

    Tips

    Tip: Once a package name has been used, it cannot be re-used by another package even if the original package is unpublished.

    `,4);function V(z,A){const o=l("ExternalLinkIcon");return r(),i("div",null,[p,a("div",h,[d,a("p",null,[u,e(" The ppm tool will only publish and "),a("a",g,[e("https://pulsar-edit.dev"),t(o)]),e(" will only list packages that are hosted on "),a("a",m,[e("GitHub"),t(o)]),e(", regardless of what process is used to publish them.")])]),b,a("p",null,[e("Some packages get too big for one person. Sometimes priorities change and someone else wants to help out. You can let others help or create co-owners by "),a("a",f,[e("adding them as a collaborator"),t(o)]),e(" on the GitHub repository for your package. "),y,e(" Anyone that has push access to your repository will have the ability to publish new versions of the package that belongs to that repository.")]),a("p",null,[e("You can also have packages that are owned by a "),a("a",k,[e("GitHub organization"),t(o)]),e(". Anyone who is a member of an organization's "),a("a",v,[e("team"),t(o)]),e(" which has push access to the package's repository will be able to publish new versions of the package.")]),_,w,a("p",null,[e("If you want to hand off support of your package to someone else, you can do that by "),a("a",x,[e("transferring the package's repository"),t(o)]),e(" to the new owner. Once you do that, they can publish a new version with the updated repository information in the "),T,e(".")]),P,a("p",null,[e("If you no longer want to support your package and cannot find anyone to take it over, you can unpublish your package from "),a("a",I,[e("https://pulsar-edit.dev"),t(o)]),e(". For example, if your package is named "),N,e(" then the command you would execute is:")]),j,a("p",null,[e("This will remove your package from the "),a("a",S,[e("https://pulsar-edit.dev"),t(o)]),e(" package registry. Anyone who has already downloaded a copy of your package will still have it and be able to use it, but it will no longer be available for installation by others.")]),Y,a("p",null,[e("This will remove just this particular version from the "),a("a",E,[e("https://pulsar-edit.dev"),t(o)]),e(" package registry.")]),O])}const G=s(c,[["render",V],["__file","maintaining-your-packages.html.vue"]]);export{G as default}; diff --git a/assets/malware.adaba8cd.png b/assets/malware.adaba8cd.png new file mode 100644 index 0000000000..c84190f6ec Binary files /dev/null and b/assets/malware.adaba8cd.png differ diff --git a/assets/markup.30f112ce.js b/assets/markup.30f112ce.js new file mode 100644 index 0000000000..4ad71a2b52 --- /dev/null +++ b/assets/markup.30f112ce.js @@ -0,0 +1 @@ +const s="/assets/markup.5d7a3e87.png";export{s as _}; diff --git a/assets/markup.5d7a3e87.png b/assets/markup.5d7a3e87.png new file mode 100644 index 0000000000..aa4c1151cd Binary files /dev/null and b/assets/markup.5d7a3e87.png differ diff --git a/assets/menu.b336059b.png b/assets/menu.b336059b.png new file mode 100644 index 0000000000..40880e6f69 Binary files /dev/null and b/assets/menu.b336059b.png differ diff --git a/assets/menubar.0c506cec.png b/assets/menubar.0c506cec.png new file mode 100644 index 0000000000..fb5530f6e8 Binary files /dev/null and b/assets/menubar.0c506cec.png differ diff --git a/assets/menubar.df752f94.js b/assets/menubar.df752f94.js new file mode 100644 index 0000000000..5001906e90 --- /dev/null +++ b/assets/menubar.df752f94.js @@ -0,0 +1 @@ +const s="/assets/menubar.0c506cec.png";export{s as _}; diff --git a/assets/moving-in-atom.html.386980d6.js b/assets/moving-in-atom.html.386980d6.js new file mode 100644 index 0000000000..812cbd1fd0 --- /dev/null +++ b/assets/moving-in-atom.html.386980d6.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-4d358836","path":"/docs/atom-archive/using-atom/sections/moving-in-atom.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"Moving in Atom","slug":"moving-in-atom","link":"#moving-in-atom","children":[]}],"git":{"updatedTime":1664634443000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1},{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":5.59,"words":1677},"filePathRelative":"docs/atom-archive/using-atom/sections/moving-in-atom.md"}');export{e as data}; diff --git a/assets/moving-in-atom.html.f0c85e72.js b/assets/moving-in-atom.html.f0c85e72.js new file mode 100644 index 0000000000..b98bcf101c --- /dev/null +++ b/assets/moving-in-atom.html.f0c85e72.js @@ -0,0 +1,52 @@ +import{_ as u,a as b}from"./symbol.b88bf5a9.js";import{_ as p,o as h,c as f,d as i,w as t,a as e,b as o,f as c,r as d}from"./app.0e1565ce.js";const g={},v=e("h3",{id:"moving-in-atom",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#moving-in-atom","aria-hidden":"true"},"#"),o(" Moving in Atom")],-1),k=e("p",null,"While it's pretty easy to move around Atom by clicking with the mouse or using the arrow keys, there are some keybindings that may help you keep your hands on the keyboard and navigate around a little faster.",-1),y=e("p",null,"Atom has support for all the standard Linux cursor movement key combinations. To go up, down, left or right a single character you can use the arrow keys.",-1),_=e("p",null,"In addition to single character movement, there are a number of other movement keybindings:",-1),w=e("p",null,[o("Atom ships with many of the basic Emacs keybindings for navigating a document. To go up and down a single character, you can use "),e("kbd",{class:"platform-mac"},"Ctrl+P"),o(" and "),e("kbd",{class:"platform-mac"},"Ctrl+N"),o(". To go left and right a single character, you can use "),e("kbd",{class:"platform-mac"},"Ctrl+B"),o(" and "),e("kbd",{class:"platform-mac"},"Ctrl+F"),o(". These are the equivalent of using the arrow keys, though some people prefer not having to move their hands to where the arrow keys are located on their keyboard.")],-1),x=e("p",null,"In addition to single character movement, there are a number of other movement keybindings:",-1),C=e("p",null,"Atom has support for all the standard Windows cursor movement key combinations. To go up, down, left or right a single character you can use the arrow keys.",-1),A=e("p",null,"In addition to single character movement, there are a number of other movement keybindings:",-1),T=c('
    • Alt+Left or Alt+BCtrl+Left - Move to the beginning of word
    • Alt+Right or Alt+FCtrl+Right - Move to the end of word
    • Cmd+Left or Ctrl+AHome - Move to the first character of the current line
    • Cmd+Right or Ctrl+EEnd - Move to the end of the line
    • Cmd+UpCtrl+Home - Move to the top of the file
    • Cmd+DownCtrl+End - Move to the bottom of the file

    You can also move directly to a specific line (and column) number with Ctrl+G. This will bring up a dialog that asks which line you would like to jump to. You can also use the row:column syntax to jump to a character in that line as well.

    Go directly to a line

    Additional Movement and Selection Commands

    ',4),j=e("code",null,"keymap.cson",-1),S=e("code",null,"keymap.cson",-1),F=e("span",{class:"platform-mac"},[e("em",null,"Atom > Keymap")],-1),L=e("span",{class:"platform-windows"},[e("em",null,"File > Keymap")],-1),M=e("span",{class:"platform-linux"},[e("em",null,"Edit > Keymap")],-1),R=e("p",null,[o("For example, the command "),e("code",null,"editor:move-to-beginning-of-screen-line"),o(" is available in the command palette, but it's not bound to any key combination. To create a key combination you need to add an entry in your "),e("code",null,"keymap.cson"),o(" file. For "),e("code",null,"editor:select-to-previous-word-boundary"),o(", you can add the following to your "),e("code",null,"keymap.cson"),o(":")],-1),E=e("div",{class:"language-coffee ext-coffee line-numbers-mode"},[e("pre",{class:"language-coffee"},[e("code",null,[e("span",{class:"token string-property property"},"'atom-text-editor'"),e("span",{class:"token operator"},":"),o(` + `),e("span",{class:"token string-property property"},"'ctrl-shift-e'"),e("span",{class:"token operator"},":"),o(),e("span",{class:"token string"},"'editor:select-to-previous-word-boundary'"),o(` +`)])]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),Y=e("div",{class:"language-coffee ext-coffee line-numbers-mode"},[e("pre",{class:"language-coffee"},[e("code",null,[e("span",{class:"token string-property property"},"'atom-text-editor'"),e("span",{class:"token operator"},":"),o(` + `),e("span",{class:"token string-property property"},"'cmd-shift-e'"),e("span",{class:"token operator"},":"),o(),e("span",{class:"token string"},"'editor:select-to-previous-word-boundary'"),o(` +`)])]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),I=e("div",{class:"language-coffee ext-coffee line-numbers-mode"},[e("pre",{class:"language-coffee"},[e("code",null,[e("span",{class:"token string-property property"},"'atom-text-editor'"),e("span",{class:"token operator"},":"),o(` + `),e("span",{class:"token string-property property"},"'ctrl-shift-e'"),e("span",{class:"token operator"},":"),o(),e("span",{class:"token string"},"'editor:select-to-previous-word-boundary'"),o(` +`)])]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),q=e("code",null,"editor:select-to-previous-word-boundary",-1),z=e("kbd",{class:"platform-mac"},"Cmd+Shift+E",-1),V=e("kbd",{class:"platform-windows platform-linux"},"Ctrl+Shift+E",-1),B=e("p",null,"Here's a list of Movement and Selection Commands that do not have a keyboard shortcut by default:",-1),N=e("div",{class:"language-text ext-text line-numbers-mode"},[e("pre",{class:"language-text"},[e("code",null,`editor:move-to-beginning-of-next-paragraph +editor:move-to-beginning-of-previous-paragraph +editor:move-to-beginning-of-screen-line +editor:move-to-beginning-of-line +editor:move-to-end-of-line +editor:move-to-first-character-of-line +editor:move-to-beginning-of-next-word +editor:move-to-previous-word-boundary +editor:move-to-next-word-boundary +editor:select-to-beginning-of-next-paragraph +editor:select-to-beginning-of-previous-paragraph +editor:select-to-end-of-line +editor:select-to-beginning-of-line +editor:select-to-beginning-of-next-word +editor:select-to-next-word-boundary +editor:select-to-previous-word-boundary +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),W=e("div",{class:"language-text ext-text line-numbers-mode"},[e("pre",{class:"language-text"},[e("code",null,`editor:move-to-beginning-of-next-paragraph +editor:move-to-beginning-of-previous-paragraph +editor:move-to-beginning-of-screen-line +editor:move-to-beginning-of-line +editor:move-to-beginning-of-next-word +editor:move-to-previous-word-boundary +editor:move-to-next-word-boundary +editor:select-to-beginning-of-next-paragraph +editor:select-to-beginning-of-previous-paragraph +editor:select-to-beginning-of-line +editor:select-to-beginning-of-next-word +editor:select-to-next-word-boundary +editor:select-to-previous-word-boundary +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),O=e("div",{class:"language-text ext-text line-numbers-mode"},[e("pre",{class:"language-text"},[e("code",null,`editor:move-to-beginning-of-next-paragraph +editor:move-to-beginning-of-previous-paragraph +editor:move-to-beginning-of-screen-line +editor:move-to-beginning-of-line +editor:move-to-end-of-line +editor:move-to-first-character-of-line +editor:move-to-beginning-of-next-word +editor:move-to-previous-word-boundary +editor:move-to-next-word-boundary +editor:select-to-beginning-of-next-paragraph +editor:select-to-beginning-of-previous-paragraph +editor:select-to-end-of-line +editor:select-to-beginning-of-line +editor:select-to-beginning-of-next-word +editor:select-to-next-word-boundary +editor:select-to-previous-word-boundary +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),K=c('

    You can also jump around a little more informatively with the Symbols View. To jump to a symbol such as a method definition, press Cmd+RCtrl+R. This opens a list of all symbols in the current file, which you can fuzzy filter similarly to Cmd+TCtrl+T. You can also search for symbols across your project but it requires a tags file.

    Search by symbol across your project

    ',3),U=e("code",null,"tags",-1),D={href:"https://ctags.io/",target:"_blank",rel:"noopener noreferrer"},G=e("code",null,"tags",-1),H={href:"https://docs.ctags.io/en/latest/",target:"_blank",rel:"noopener noreferrer"},P=e("p",null,[o("Once you have your "),e("code",null,"tags"),o(" file generated, you can use it to search for symbols across your project by pressing "),e("kbd",{class:"platform-windows platform-linux"},"Ctrl+Shift+R"),o(". This also enables you to use "),e("kbd",{class:"platform-linux"},"Alt+Ctrl+Down"),o(" to go to and "),e("kbd",{class:"platform-linux"},"Alt+Ctrl+Up"),o(" to return from the declaration of the symbol under the cursor.")],-1),J=e("p",null,[o("Once you have your "),e("code",null,"tags"),o(" file generated, you can use it to search for symbols across your project by pressing "),e("kbd",{class:"platform-mac"},"Cmd+Shift+R"),o(". This also enables you to use "),e("kbd",{class:"platform-mac"},"Alt+Cmd+Down"),o(" to go to and "),e("kbd",{class:"platform-mac"},"Alt+Cmd+Up"),o(" to return from the declaration of the symbol under the cursor.")],-1),Q=e("p",null,[o("Once you have your "),e("code",null,"tags"),o(" file generated, you can use it to search for symbols across your project by pressing "),e("kbd",{class:"platform-mac"},"Cmd+Shift+R"),e("kbd",{class:"platform-windows platform-linux"},"Ctrl+Shift+R"),o(".")],-1),X=e("code",null,".ctags",-1),Z=e("span",{class:"platform-mac platform-linux"},[e("code",null,"~/.ctags")],-1),$=e("span",{class:"platform-windows"},[e("code",null,"%USERPROFILE%\\.ctags")],-1),ee={href:"https://github.com/atom/symbols-view/blob/master/lib/ctags-config",target:"_blank",rel:"noopener noreferrer"},oe={href:"https://github.com/atom/symbols-view",target:"_blank",rel:"noopener noreferrer"},te=c('

    Bookmarks

    Atom also has a great way to bookmark specific lines in your project so you can jump back to them quickly.

    If you press Cmd+F2Alt+Ctrl+F2Ctrl+Shift+F2, Atom will toggle a "bookmark" on the current line. You can set these throughout your project and use them to quickly find and jump to important lines of your project. A small bookmark symbol is added to the line gutter, like on line 22 of the image below.

    If you hit F2, Atom will jump to the next bookmark in the file you currently have focused. If you use Shift+F2 it will cycle backwards through them instead.

    You can also see a list of all your project's current bookmarks and quickly filter them and jump to any of them by hitting Ctrl+F2.

    ',6),ne={href:"https://github.com/atom/bookmarks",target:"_blank",rel:"noopener noreferrer"};function ae(se,ie){const r=d("Tabs"),m=d("RouterLink"),l=d("ExternalLinkIcon");return h(),f("div",null,[v,k,i(r,{id:"6",data:[{title:"Linux"},{title:"Mac"},{title:"Windows"}],"tab-id":"moving-in-atom"},{tab0:t(({title:n,value:a,isActive:s})=>[y,_]),tab1:t(({title:n,value:a,isActive:s})=>[w,x]),tab2:t(({title:n,value:a,isActive:s})=>[C,A]),_:1}),T,e("p",null,[o("Atom also has a few movement and selection commands that don't have keybindings by default. You can access these commands from the "),i(m,{to:"/getting-started/sections/atom-basics/#command-palette"},{default:t(()=>[o("Command Palette")]),_:1}),o(", but if you find yourself using commands that don't have a keybinding often, have no fear! You can easily add an entry to your "),j,o(" to create a key combination. You can open "),S,o(" file in an editor from the "),F,L,M,o(" menu.")]),R,i(r,{id:"79",data:[{title:"Linux"},{title:"Mac"},{title:"Windows"}],"tab-id":"moving-in-atom"},{tab0:t(({title:n,value:a,isActive:s})=>[E]),tab1:t(({title:n,value:a,isActive:s})=>[Y]),tab2:t(({title:n,value:a,isActive:s})=>[I]),_:1}),e("p",null,[o("This will bind the command "),q,o(" to "),z,V,o(". For more information on customizing your keybindings, see "),i(m,{to:"/using-atom/sections/basic-customization/#customizing-keybindings"},{default:t(()=>[o("Customizing Keybindings")]),_:1}),o(".")]),B,i(r,{id:"96",data:[{title:"Linux"},{title:"Mac"},{title:"Windows"}],"tab-id":"moving-in-atom"},{tab0:t(({title:n,value:a,isActive:s})=>[N]),tab1:t(({title:n,value:a,isActive:s})=>[W]),tab2:t(({title:n,value:a,isActive:s})=>[O]),_:1}),K,e("p",null,[o("You can generate a "),U,o(" file by using the "),e("a",D,[o("ctags utility"),i(l)]),o(". Once it is installed, you can use it to generate a "),G,o(" file by running a command to generate it. See the "),e("a",H,[o("ctags documentation"),i(l)]),o(" for details.")]),i(r,{id:"119",data:[{title:"Linux"},{title:"Mac"},{title:"Windows"}],"tab-id":"moving-in-atom"},{tab0:t(({title:n,value:a,isActive:s})=>[P]),tab1:t(({title:n,value:a,isActive:s})=>[J]),tab2:t(({title:n,value:a,isActive:s})=>[Q]),_:1}),e("p",null,[o("You can customize how tags are generated by creating your own "),X,o(" file in your home directory, "),Z,$,o(". An example can be found "),e("a",ee,[o("here"),i(l)]),o(".")]),e("p",null,[o("The symbols navigation functionality is implemented in the "),e("a",oe,[o("symbols-view"),i(l)]),o(" package.")]),te,o(' ![View and filter bookmarks](@images/atom/bookmarks.png "View and filter bookmarks") '),e("p",null,[o("The bookmarks functionality is implemented in the "),e("a",ne,[o("bookmarks"),i(l)]),o(" package.")])])}const de=p(g,[["render",ae],["__file","moving-in-atom.html.vue"]]);export{de as default}; diff --git a/assets/moving-in-pulsar.html.325aabf8.js b/assets/moving-in-pulsar.html.325aabf8.js new file mode 100644 index 0000000000..f2049947dc --- /dev/null +++ b/assets/moving-in-pulsar.html.325aabf8.js @@ -0,0 +1 @@ +const n=JSON.parse('{"key":"v-15cc7078","path":"/docs/launch-manual/sections/using-pulsar/sections/moving-in-pulsar.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Moving in Pulsar","slug":"moving-in-pulsar","link":"#moving-in-pulsar","children":[{"level":3,"title":"Additional Movement and Selection Commands","slug":"additional-movement-and-selection-commands","link":"#additional-movement-and-selection-commands","children":[]},{"level":3,"title":"Navigating by Symbols","slug":"navigating-by-symbols","link":"#navigating-by-symbols","children":[]},{"level":3,"title":"Bookmarks","slug":"bookmarks","link":"#bookmarks","children":[]}]}],"git":{"updatedTime":1669229414000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":4}]},"readingTime":{"minutes":5.31,"words":1594},"filePathRelative":"docs/launch-manual/sections/using-pulsar/sections/moving-in-pulsar.md"}');export{n as data}; diff --git a/assets/moving-in-pulsar.html.c8a1d28a.js b/assets/moving-in-pulsar.html.c8a1d28a.js new file mode 100644 index 0000000000..3c60c0db44 --- /dev/null +++ b/assets/moving-in-pulsar.html.c8a1d28a.js @@ -0,0 +1,52 @@ +import{_ as u,a as m}from"./symbol.b88bf5a9.js";import{_ as b}from"./bookmarks.736c4e14.js";import{_ as h,o as g,c as p,d as r,w as o,a as e,b as t,f as d,r as c}from"./app.0e1565ce.js";const v={},f=e("h2",{id:"moving-in-pulsar",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#moving-in-pulsar","aria-hidden":"true"},"#"),t(" Moving in Pulsar")],-1),y=e("p",null,"While it's pretty easy to move around Pulsar by clicking with the mouse or using the arrow keys, there are some keybindings that may help you keep your hands on the keyboard and navigate around a little faster.",-1),k=e("p",null,"Pulsar has support for all the standard Linux cursor movement key combinations. To go up, down, left or right a single character you can use the arrow keys.",-1),_=e("p",null,"In addition to single character movement, there are a number of other movement keybindings:",-1),w=e("ul",null,[e("li",null,[e("kbd",null,"Ctrl+Left"),t(" - Move to the beginning of word")]),e("li",null,[e("kbd",null,"Ctrl+Right"),t(" - Move to the end of word")]),e("li",null,[e("kbd",null,"Home"),t(" - Move to the first character of the current line")]),e("li",null,[e("kbd",null,"End"),t(" - Move to the end of the line")]),e("li",null,[e("kbd",null,"Ctrl+Home"),t(" - Move to the top of the file")]),e("li",null,[e("kbd",null,"Ctrl+End"),t(" - Move to the bottom of the file")])],-1),x=e("p",null,[t("Pulsar ships with many of the basic Emacs keybindings for navigating a document. To go up and down a single character, you can use "),e("kbd",null,"Ctrl+P"),t(" and "),e("kbd",null,"Ctrl+N"),t(". To go left and right a single character, you can use "),e("kbd",null,"Ctrl+B"),t(" and "),e("kbd",null,"Ctrl+F"),t(". These are the equivalent of using the arrow keys, though some people prefer not having to move their hands to where the arrow keys are located on their keyboard.")],-1),C=e("p",null,"In addition to single character movement, there are a number of other movement keybindings:",-1),A=e("ul",null,[e("li",null,[e("kbd",null,"Alt+Left"),t(" or "),e("kbd",null,"Alt+B"),t(" - Move to the beginning of word")]),e("li",null,[e("kbd",null,"Alt+Right"),t(" or "),e("kbd",null,"Alt+F"),t(" - Move to the end of word")]),e("li",null,[e("kbd",null,"Cmd+Left"),t(" or "),e("kbd",null,"Ctrl+A"),t(" - Move to the first character of the current line")]),e("li",null,[e("kbd",null,"Cmd+Right"),t(" or "),e("kbd",null,"Ctrl+E"),t(" - Move to the end of the line")]),e("li",null,[e("kbd",null,"Cmd+Up"),t(" - Move to the top of the file")]),e("li",null,[e("kbd",null,"Cmd+Down"),t(" - Move to the bottom of the file")])],-1),M=e("p",null,"Pulsar has support for all the standard Windows cursor movement key combinations. To go up, down, left or right a single character you can use the arrow keys.",-1),T=e("p",null,"In addition to single character movement, there are a number of other movement keybindings:",-1),L=e("ul",null,[e("li",null,[e("kbd",null,"Ctrl+Left"),t(" - Move to the beginning of word")]),e("li",null,[e("kbd",null,"Ctrl+Right"),t(" - Move to the end of word")]),e("li",null,[e("kbd",null,"Home"),t(" - Move to the first character of the current line")]),e("li",null,[e("kbd",null,"End"),t(" - Move to the end of the line")]),e("li",null,[e("kbd",null,"Ctrl+Home"),t(" - Move to the top of the file")]),e("li",null,[e("kbd",null,"Ctrl+End"),t(" - Move to the bottom of the file")])],-1),N=d('

    You can also move directly to a specific line (and column) number with Ctrl+G. This will bring up a dialog that asks which line you would like to jump to. You can also use the row:column syntax to jump to a character in that line as well.

    Go directly to a line

    Additional Movement and Selection Commands

    Pulsar also has a few movement and selection commands that don't have keybindings by default. You can access these commands from the Command Palette, but if you find yourself using commands that don't have a keybinding often, have no fear! You can easily add an entry to your keymap.cson to create a key combination. You can open keymap.cson file in an editor from the LNX: Edit > Keymap - MAC: Pulsar > Keymap - WIN: File > Keymap menu. For example, the command editor:move-to-beginning-of-screen-line is available in the command palette, but it's not bound to any key combination. To create a key combination you need to add an entry in your keymap.cson file. For editor:select-to-previous-word-boundary, you can add the following to your keymap.cson:

    ',4),S=e("div",{class:"language-coffee ext-coffee line-numbers-mode"},[e("pre",{class:"language-coffee"},[e("code",null,[e("span",{class:"token string-property property"},"'atom-text-editor'"),e("span",{class:"token operator"},":"),t(` + `),e("span",{class:"token string-property property"},"'ctrl-shift-e'"),e("span",{class:"token operator"},":"),t(),e("span",{class:"token string"},"'editor:select-to-previous-word-boundary'"),t(` +`)])]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),j=e("div",{class:"language-coffee ext-coffee line-numbers-mode"},[e("pre",{class:"language-coffee"},[e("code",null,[e("span",{class:"token string-property property"},"'atom-text-editor'"),e("span",{class:"token operator"},":"),t(` + `),e("span",{class:"token string-property property"},"'cmd-shift-e'"),e("span",{class:"token operator"},":"),t(),e("span",{class:"token string"},"'editor:select-to-previous-word-boundary'"),t(` +`)])]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),I=e("div",{class:"language-coffee ext-coffee line-numbers-mode"},[e("pre",{class:"language-coffee"},[e("code",null,[e("span",{class:"token string-property property"},"'atom-text-editor'"),e("span",{class:"token operator"},":"),t(` + `),e("span",{class:"token string-property property"},"'ctrl-shift-e'"),e("span",{class:"token operator"},":"),t(),e("span",{class:"token string"},"'editor:select-to-previous-word-boundary'"),t(` +`)])]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),E=d('

    This will bind the command editor:select-to-previous-word-boundary to LNX/WIN: Ctrl+Shift+E - MAC: Cmd+Shift+E. For more information on customizing your keybindings, see Customizing Keybindings.

    Here's a list of Movement and Selection Commands that do not have a keyboard shortcut by default:

    ',2),P=e("div",{class:"language-text ext-text line-numbers-mode"},[e("pre",{class:"language-text"},[e("code",null,`editor:move-to-beginning-of-next-paragraph +editor:move-to-beginning-of-previous-paragraph +editor:move-to-beginning-of-screen-line +editor:move-to-beginning-of-line +editor:move-to-end-of-line +editor:move-to-first-character-of-line +editor:move-to-beginning-of-next-word +editor:move-to-previous-word-boundary +editor:move-to-next-word-boundary +editor:select-to-beginning-of-next-paragraph +editor:select-to-beginning-of-previous-paragraph +editor:select-to-end-of-line +editor:select-to-beginning-of-line +editor:select-to-beginning-of-next-word +editor:select-to-next-word-boundary +editor:select-to-previous-word-boundary +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),F=e("div",{class:"language-text ext-text line-numbers-mode"},[e("pre",{class:"language-text"},[e("code",null,`editor:move-to-beginning-of-next-paragraph +editor:move-to-beginning-of-previous-paragraph +editor:move-to-beginning-of-screen-line +editor:move-to-beginning-of-line +editor:move-to-beginning-of-next-word +editor:move-to-previous-word-boundary +editor:move-to-next-word-boundary +editor:select-to-beginning-of-next-paragraph +editor:select-to-beginning-of-previous-paragraph +editor:select-to-beginning-of-line +editor:select-to-beginning-of-next-word +editor:select-to-next-word-boundary +editor:select-to-previous-word-boundary +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),W=e("div",{class:"language-text ext-text line-numbers-mode"},[e("pre",{class:"language-text"},[e("code",null,`editor:move-to-beginning-of-next-paragraph +editor:move-to-beginning-of-previous-paragraph +editor:move-to-beginning-of-screen-line +editor:move-to-beginning-of-line +editor:move-to-end-of-line +editor:move-to-first-character-of-line +editor:move-to-beginning-of-next-word +editor:move-to-previous-word-boundary +editor:move-to-next-word-boundary +editor:select-to-beginning-of-next-paragraph +editor:select-to-beginning-of-previous-paragraph +editor:select-to-end-of-line +editor:select-to-beginning-of-line +editor:select-to-beginning-of-next-word +editor:select-to-next-word-boundary +editor:select-to-previous-word-boundary +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),O=d('

    You can also jump around a little more informatively with the Symbols View. To jump to a symbol such as a method definition, press LNX/WIN: Ctrl+R - MAC: Cmd+R. This opens a list of all symbols in the current file, which you can fuzzy filter similarly to LNX/WIN: Ctrl+T - MAC: Cmd+T. You can also search for symbols across your project but it requires a tags file.

    Search by symbol across your project

    ',3),R=e("code",null,"tags",-1),Y={href:"https://ctags.io/",target:"_blank",rel:"noopener noreferrer"},q=e("code",null,"tags",-1),V={href:"https://docs.ctags.io/en/latest/",target:"_blank",rel:"noopener noreferrer"},z=e("p",null,[t("Once you have your "),e("code",null,"tags"),t(" file generated, you can use it to search for symbols across your project by pressing "),e("kbd",null,"Ctrl+Shift+R"),t(". This also enables you to use "),e("kbd",null,"Alt+Ctrl+Down"),t(" to go to and "),e("kbd",null,"Alt+Ctrl+Up"),t(" to return from the declaration of the symbol under the cursor.")],-1),B=e("p",null,[t("Once you have your "),e("code",null,"tags"),t(" file generated, you can use it to search for symbols across your project by pressing "),e("kbd",null,"Cmd+Shift+R"),t(". This also enables you to use "),e("kbd",null,"Alt+Cmd+Down"),t(" to go to and "),e("kbd",null,"Alt+Cmd+Up"),t(" to return from the declaration of the symbol under the cursor.")],-1),X=e("p",null,[t("Once you have your "),e("code",null,"tags"),t(" file generated, you can use it to search for symbols across your project by pressing "),e("kbd",{class:""},"Ctrl+Shift+R"),t(".")],-1),H=e("code",null,".ctags",-1),D=e("strong",null,[e("em",null,"LNX/MAC")],-1),K=e("code",null,"~/.ctags",-1),U=e("strong",null,[e("em",null,"WIN")],-1),G=e("code",null,"%USERPROFILE%\\.ctags",-1),J={href:"https://github.com/pulsar-edit/symbols-view/blob/master/lib/ctags-config",target:"_blank",rel:"noopener noreferrer"},Q={href:"https://github.com/pulsar-edit/symbols-view",target:"_blank",rel:"noopener noreferrer"},Z=d('

    Bookmarks

    Pulsar also has a great way to bookmark specific lines in your project so you can jump back to them quickly.

    If you press LNX/WIN: Alt+Ctrl+F2 - MAC Cmd+F2, Pulsar will toggle a "bookmark" on the current line. You can set these throughout your project and use them to quickly find and jump to important lines of your project. A small bookmark symbol is added to the line gutter, like on line 22 of the image below.

    If you hit F2, Pulsar will jump to the next bookmark in the file you currently have focused. If you use Shift+F2 it will cycle backwards through them instead.

    You can also see a list of all your project's current bookmarks and quickly filter them and jump to any of them by hitting Ctrl+F2.

    View and filter bookmarks

    ',6),$={href:"https://github.com/pulsar-edit/bookmarks",target:"_blank",rel:"noopener noreferrer"};function ee(te,oe){const a=c("Tabs"),s=c("ExternalLinkIcon");return g(),p("div",null,[f,y,r(a,{id:"6",data:[{title:"Linux"},{title:"macOS"},{title:"Windows"}],"tab-id":"using-pulsar"},{tab0:o(({title:n,value:i,isActive:l})=>[k,_,w]),tab1:o(({title:n,value:i,isActive:l})=>[x,C,A]),tab2:o(({title:n,value:i,isActive:l})=>[M,T,L]),_:1}),N,r(a,{id:"140",data:[{title:"Linux"},{title:"macOS"},{title:"Windows"}],"tab-id":"using-pulsar "},{tab0:o(({title:n,value:i,isActive:l})=>[S]),tab1:o(({title:n,value:i,isActive:l})=>[j]),tab2:o(({title:n,value:i,isActive:l})=>[I]),_:1}),E,r(a,{id:"157",data:[{title:"Linux"},{title:"macOS"},{title:"Windows"}],"tab-id":"using-pulsar"},{tab0:o(({title:n,value:i,isActive:l})=>[P]),tab1:o(({title:n,value:i,isActive:l})=>[F]),tab2:o(({title:n,value:i,isActive:l})=>[W]),_:1}),O,e("p",null,[t("You can generate a "),R,t(" file by using the "),e("a",Y,[t("ctags utility"),r(s)]),t(". Once it is installed, you can use it to generate a "),q,t(" file by running a command to generate it. See the "),e("a",V,[t("ctags documentation"),r(s)]),t(" for details.")]),r(a,{id:"180",data:[{title:"Linux"},{title:"macOS"},{title:"Windows"}],"tab-id":"using-pulsar"},{tab0:o(({title:n,value:i,isActive:l})=>[z]),tab1:o(({title:n,value:i,isActive:l})=>[B]),tab2:o(({title:n,value:i,isActive:l})=>[X]),_:1}),e("p",null,[t("You can customize how tags are generated by creating your own "),H,t(" file in your home directory, "),D,t(": "),K,t(" - "),U,t(": "),G,t(". An example can be found "),e("a",J,[t("here"),r(s)]),t(".")]),e("p",null,[t("The symbols navigation functionality is implemented in the "),e("a",Q,[t("symbols-view"),r(s)]),t(" package.")]),Z,e("p",null,[t("The bookmarks functionality is implemented in the "),e("a",$,[t("bookmarks"),r(s)]),t(" package.")])])}const re=h(v,[["render",ee],["__file","moving-in-pulsar.html.vue"]]);export{re as default}; diff --git a/assets/moving.0cfb3518.png b/assets/moving.0cfb3518.png new file mode 100644 index 0000000000..fbf5fb572e Binary files /dev/null and b/assets/moving.0cfb3518.png differ diff --git a/assets/multiple-cursors.4466b041.gif b/assets/multiple-cursors.4466b041.gif new file mode 100644 index 0000000000..b0256c7429 Binary files /dev/null and b/assets/multiple-cursors.4466b041.gif differ diff --git a/assets/network.00727d52.png b/assets/network.00727d52.png new file mode 100644 index 0000000000..ce253a53ea Binary files /dev/null and b/assets/network.00727d52.png differ diff --git a/assets/node-package.b9535f33.png b/assets/node-package.b9535f33.png new file mode 100644 index 0000000000..7d7d4605be Binary files /dev/null and b/assets/node-package.b9535f33.png differ diff --git a/assets/octicons1.9b0fa408.png b/assets/octicons1.9b0fa408.png new file mode 100644 index 0000000000..4dd8472a54 Binary files /dev/null and b/assets/octicons1.9b0fa408.png differ diff --git a/assets/octicons2.69cc8255.png b/assets/octicons2.69cc8255.png new file mode 100644 index 0000000000..b0fcf47aeb Binary files /dev/null and b/assets/octicons2.69cc8255.png differ diff --git a/assets/octicons3.a3bf7b53.png b/assets/octicons3.a3bf7b53.png new file mode 100644 index 0000000000..cc2d1f52eb Binary files /dev/null and b/assets/octicons3.a3bf7b53.png differ diff --git a/assets/open-file.c788eed5.png b/assets/open-file.c788eed5.png new file mode 100644 index 0000000000..fec83c1a56 Binary files /dev/null and b/assets/open-file.c788eed5.png differ diff --git a/assets/open-on-github.6fa9b166.js b/assets/open-on-github.6fa9b166.js new file mode 100644 index 0000000000..4693214718 --- /dev/null +++ b/assets/open-on-github.6fa9b166.js @@ -0,0 +1 @@ +const s="/assets/git-checkout-head.1f356cbf.gif",t="/assets/git-status.748c4f3e.gif",a="/assets/git-message.9e959a5d.gif",e="/assets/git-status-bar.774ed823.png",i="/assets/git-lines.8152cd74.png",o="/assets/open-on-github.e6116efb.png";export{t as _,e as a,s as b,a as c,i as d,o as e}; diff --git a/assets/open-on-github.e6116efb.png b/assets/open-on-github.e6116efb.png new file mode 100644 index 0000000000..88e7671337 Binary files /dev/null and b/assets/open-on-github.e6116efb.png differ diff --git a/assets/openapi-logo.f023afce.png b/assets/openapi-logo.f023afce.png new file mode 100644 index 0000000000..7d0ca40379 Binary files /dev/null and b/assets/openapi-logo.f023afce.png differ diff --git a/assets/package-active-editor-info.html.6ba548b2.js b/assets/package-active-editor-info.html.6ba548b2.js new file mode 100644 index 0000000000..fc53d7aab3 --- /dev/null +++ b/assets/package-active-editor-info.html.6ba548b2.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-5d85798e","path":"/docs/atom-archive/hacking-atom/sections/package-active-editor-info.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"Package: Active Editor Info","slug":"package-active-editor-info","link":"#package-active-editor-info","children":[]}],"git":{"updatedTime":1664050274000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":3}]},"readingTime":{"minutes":4.38,"words":1314},"filePathRelative":"docs/atom-archive/hacking-atom/sections/package-active-editor-info.md"}');export{e as data}; diff --git a/assets/package-active-editor-info.html.6db919df.js b/assets/package-active-editor-info.html.6db919df.js new file mode 100644 index 0000000000..43dd16ed37 --- /dev/null +++ b/assets/package-active-editor-info.html.6db919df.js @@ -0,0 +1,104 @@ +import{_ as o,o as i,c as p,e as t,a as n,b as s,d as e,f as c,r as l}from"./app.0e1565ce.js";const u={},r=n("h2",{id:"package-active-editor-info",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#package-active-editor-info","aria-hidden":"true"},"#"),s(" Package: Active Editor Info")],-1),d=n("p",null,[s("We saw in our "),n("a",{href:"#package-word-count"},"Word Count"),s(" package how we could show information in a modal panel. However, panels aren't the only way to extend Pulsar's UI\u2014you can also add items to the workspace. These items can be dragged to new locations (for example, one of the docks on the edges of the window), and Pulsar will restore them the next time you open the project.")],-1),k={href:"https://github.com/pulsar-edit/active-editor-info",target:"_blank",rel:"noopener noreferrer"},v=n("h3",{id:"create-the-package",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#create-the-package","aria-hidden":"true"},"#"),s(" Create the Package")],-1),m=n("strong",null,[n("em",null,"LNX/WIN")],-1),h=n("kbd",null,"Ctrl+Shift+P",-1),g=n("strong",null,[n("em",null,"MAC")],-1),b=n("kbd",null,"Cmd+Shift+P",-1),w={href:"https://github.com/pulsar-edit/command-palette",target:"_blank",rel:"noopener noreferrer"},f=n("code",null,"Package Generator: Generate Package",-1),y=n("a",{href:"#package-generator"},"the section on package generation",-1),q=n("code",null,"active-editor-info",-1),j=c(`

    Add an Opener

    Now let's edit the package files to show our view in a workspace item instead of a modal panel. The way we do this is by registering an opener with Pulsar. Openers are just functions that accept a URI and return a view (if it's a URI that the opener knows about). When you call atom.workspace.open(), Pulsar will go through all of its openers until it finds one that can handle the URI you passed.

    Let's open lib/active-editor-info.js and edit our activate() method to register an opener:

    "use babel";
    +
    +import ActiveEditorInfoView from "./active-editor-info-view";
    +import { CompositeDisposable, Disposable } from "atom";
    +
    +export default {
    +	subscriptions: null,
    +
    +	activate(state) {
    +		this.subscriptions = new CompositeDisposable(
    +			// Add an opener for our view.
    +			atom.workspace.addOpener((uri) => {
    +				if (uri === "atom://active-editor-info") {
    +					return new ActiveEditorInfoView();
    +				}
    +			}),
    +
    +			// Register command that toggles this view
    +			atom.commands.add("atom-workspace", {
    +				"active-editor-info:toggle": () => this.toggle(),
    +			}),
    +
    +			// Destroy any ActiveEditorInfoViews when the package is deactivated.
    +			new Disposable(() => {
    +				atom.workspace.getPaneItems().forEach((item) => {
    +					if (item instanceof ActiveEditorInfoView) {
    +						item.destroy();
    +					}
    +				});
    +			})
    +		);
    +	},
    +
    +	deactivate() {
    +		this.subscriptions.dispose();
    +	},
    +
    +	toggle() {
    +		console.log("Toggle it!");
    +	},
    +};
    +

    You'll notice we also removed the activeEditorInfoView property and the serialize() method. That's because, with workspace items, it's possible to have more than one instance of a given view. Since each instance can have its own state, each should do its own serialization instead of relying on a package-level serialize() method. We'll come back to that later.

    You probably also noticed that our toggle() implementation just logs the text "Toggle it!" to the console. Let's make it actually toggle our view:

      toggle() {
    +    atom.workspace.toggle('atom://active-editor-info');
    +  }
    +

    Updating the View

    Pulsar uses the same view abstractions everywhere, so we can almost use the generated ActiveEditorInfoView class as-is. We just need to add two small methods:

      getTitle() {
    +    // Used by Pulsar for tab text
    +    return 'Active Editor Info';
    +  }
    +
    +  getURI() {
    +    // Used by Pulsar to identify the view when toggling.
    +    return 'atom://active-editor-info';
    +  }
    +

    Now reload the window and run the Active Editor Info: Toggle command from the command palette! Our view will appear in a new tab in the center of the workspace. If you want, you can drag it into one of the docks. Toggling it again will then hide that dock. If you close the tab and run the toggle command again, it will appear in the last place you had it.

    Note

    We've repeated the same URI three times now. That's okay, but it's probably a good idea to define the URL in one place and then import it from that module wherever you need it.

    Constraining Our Item's Locations

    The purpose of our view is to show information about the active text editor, so it doesn't really make sense to show our item in the center of the workspace (where the text editor will be). Let's add some methods to our view class to influence where its opened:

      getDefaultLocation() {
    +    // This location will be used if the user hasn't overridden it by dragging the item elsewhere.
    +    // Valid values are "left", "right", "bottom", and "center" (the default).
    +    return 'right';
    +  }
    +
    +  getAllowedLocations() {
    +    // The locations into which the item can be moved.
    +    return ['left', 'right', 'bottom'];
    +  }
    +

    Now our item will appear in the right dock initially and users will only be able to drag it to one of the other docks.

    Show Active Editor Info

    Now that we have our view all wired up, let's update it to show some information about the active text editor. Add this to the constructor:

    this.subscriptions = atom.workspace
    +	.getCenter()
    +	.observeActivePaneItem((item) => {
    +		if (!atom.workspace.isTextEditor(item)) {
    +			message.innerText = "Open a file to see important information about it.";
    +			return;
    +		}
    +		message.innerHTML = \`
    +    <h2>\${item.getFileName() || "untitled"}</h2>
    +    <ul>
    +      <li><b>Soft Wrap:</b> \${item.softWrapped}</li>
    +      <li><b>Tab Length:</b> \${item.getTabLength()}</li>
    +      <li><b>Encoding:</b> \${item.getEncoding()}</li>
    +      <li><b>Line Count:</b> \${item.getLineCount()}</li>
    +    </ul>
    +  \`;
    +	});
    +

    Now whenever you open a text editor in the center, the view will update with some information about it.

    WARNING

    We use a template string here because it's simple and we have a lot of control over what's going into it, but this could easily result in the insertion of unwanted HTML if you're not careful. Sanitize your input and use the DOM API or a templating system when doing this for real.

    Also, don't forget to clean up the subscription in the destroy() method:

    destroy() {
    +  this.element.remove();
    +  this.subscriptions.dispose();
    +}
    +

    Serialization

    If you were to reload Atom now, you'd see that our item had disappeared. That's because we haven't told Pulsar how to serialize it yet. Let's do that now.

    The first step is to implement a serialize() method on our ActiveEditorInfoView class. Atom will call the serialize() method on every item in the workspace periodically to save its state.

      serialize() {
    +    return {
    +      // This is used to look up the deserializer function. It can be any string, but it needs to be
    +      // unique across all packages!
    +      deserializer: 'active-editor-info/ActiveEditorInfoView'
    +    };
    +  }
    +

    Note

    All of our view's state is derived from the active text editor so we only need the deserializer field. If we had other state that we wanted to preserve across reloads, we would just add things to the object we're returning. Just make sure that they're JSON serializable!

    Next we need to register a deserializer function that Atom can use to recreate the real object when it starts up. The best way to do that is to add a "deserializers" object to our package.json file:

    {
    +  "name": "active-editor-info",
    +  ...
    +  "deserializers": {
    +    "active-editor-info/ActiveEditorInfoView": "deserializeActiveEditorInfoView"
    +  }
    +}
    +

    Notice that the key ("active-editor-info/ActiveEditorInfoView") matches the string we used in our serialize() method above. The value ("deserializeActiveEditorInfoView") refers to a function in our main module, which we still need to add. Go back to active-editor-info.js and do that now:

      deserializeActiveEditorInfoView(serialized) {
    +    return new ActiveEditorInfoView();
    +  }
    +

    The value returned from our serialize() method will be passed to this function. Since our serialized object didn't include any state, we can just return a new ActiveEditorInfoView instance.

    Reload Pulsar and toggle the view with the Active Editor Info: Toggle command. Then reload Pulsar again. Your view should be just where you left it!

    Summary

    In this section, we've made a toggleable workspace item whose placement can be controlled by the user. This could be helpful when creating all sorts of visual tools for working with code!

    `,36);function _(I,x){const a=l("ExternalLinkIcon");return i(),p("div",null,[r,d,t("Below was part of this section in the original docs but as Nuclide is retired we either need a new example or remove this entirely"),t(`This system is used by Pulsar's tree view, as well as by third party +packages like [Nuclide](https://nuclide.io) for its console, debugger, outline +view, and diagnostics (linter results).`),n("p",null,[s("For this package, we'll define a workspace item that tells us some information about our active text editor. The final package can be viewed at "),n("a",k,[s("https://github.com/pulsar-edit/active-editor-info"),e(a)]),s(".")]),v,n("p",null,[s("To begin, press "),m,s(": "),h,s(" - "),g,s(": "),b,s(" to bring up the "),n("a",w,[s("Command Palette"),e(a)]),s('. Type "generate package" and select the '),f,s(" command, just as we did in "),y,s(". Enter "),q,s(" as the name of the package.")]),j])}const T=o(u,[["render",_],["__file","package-active-editor-info.html.vue"]]);export{T as default}; diff --git a/assets/package-active-editor-info.html.765e6517.js b/assets/package-active-editor-info.html.765e6517.js new file mode 100644 index 0000000000..6225a8403f --- /dev/null +++ b/assets/package-active-editor-info.html.765e6517.js @@ -0,0 +1,102 @@ +import{_ as p,o as c,c as l,a as s,b as n,d as a,w as o,f as u,r as i}from"./app.0e1565ce.js";const r={},d=s("h3",{id:"package-active-editor-info",tabindex:"-1"},[s("a",{class:"header-anchor",href:"#package-active-editor-info","aria-hidden":"true"},"#"),n(" Package: Active Editor Info")],-1),k={href:"https://nuclide.io",target:"_blank",rel:"noopener noreferrer"},v=s("p",null,"For this package, we'll define a workspace item that tells us some information about our active text editor. The final package can be viewed at https://github.com/atom/active-editor-info.",-1),m=s("h4",{id:"create-the-package",tabindex:"-1"},[s("a",{class:"header-anchor",href:"#create-the-package","aria-hidden":"true"},"#"),n(" Create the Package")],-1),h=s("kbd",{class:"platform-mac"},"Cmd+Shift+P",-1),g=s("kbd",{class:"platform-windows platform-linux"},"Ctrl+Shift+P",-1),b={href:"https://github.com/atom/command-palette",target:"_blank",rel:"noopener noreferrer"},w=s("code",null,"active-editor-info",-1),f=u(`

    Add an Opener

    Now let's edit the package files to show our view in a workspace item instead of a modal panel. The way we do this is by registering an opener with Atom. Openers are just functions that accept a URI and return a view (if it's a URI that the opener knows about). When you call atom.workspace.open(), Atom will go through all of its openers until it finds one that can handle the URI you passed.

    Let's open lib/active-editor-info.js and edit our activate() method to register an opener:

    "use babel";
    +
    +import ActiveEditorInfoView from "./active-editor-info-view";
    +import { CompositeDisposable, Disposable } from "atom";
    +
    +export default {
    +	subscriptions: null,
    +
    +	activate(state) {
    +		this.subscriptions = new CompositeDisposable(
    +			// Add an opener for our view.
    +			atom.workspace.addOpener((uri) => {
    +				if (uri === "atom://active-editor-info") {
    +					return new ActiveEditorInfoView();
    +				}
    +			}),
    +
    +			// Register command that toggles this view
    +			atom.commands.add("atom-workspace", {
    +				"active-editor-info:toggle": () => this.toggle(),
    +			}),
    +
    +			// Destroy any ActiveEditorInfoViews when the package is deactivated.
    +			new Disposable(() => {
    +				atom.workspace.getPaneItems().forEach((item) => {
    +					if (item instanceof ActiveEditorInfoView) {
    +						item.destroy();
    +					}
    +				});
    +			})
    +		);
    +	},
    +
    +	deactivate() {
    +		this.subscriptions.dispose();
    +	},
    +
    +	toggle() {
    +		console.log("Toggle it!");
    +	},
    +};
    +

    You'll notice we also removed the activeEditorInfoView property and the serialize() method. That's because, with workspace items, it's possible to have more than one instance of a given view. Since each instance can have its own state, each should do its own serialization instead of relying on a package-level serialize() method. We'll come back to that later.

    You probably also noticed that our toggle() implementation just logs the text "Toggle it!" to the console. Let's make it actually toggle our view:

      toggle() {
    +    atom.workspace.toggle('atom://active-editor-info');
    +  }
    +

    Updating the View

    Atom uses the same view abstractions everywhere, so we can almost use the generated ActiveEditorInfoView class as-is. We just need to add two small methods:

      getTitle() {
    +    // Used by Atom for tab text
    +    return 'Active Editor Info';
    +  }
    +
    +  getURI() {
    +    // Used by Atom to identify the view when toggling.
    +    return 'atom://active-editor-info';
    +  }
    +

    Now reload the window and run the "Active Editor Info: Toggle" command from the command palette! Our view will appear in a new tab in the center of the workspace. If you want, you can drag it into one of the docks. Toggling it again will then hide that dock. If you close the tab and run the toggle command again, it will appear in the last place you had it.

    Note

    We've repeated the same URI three times now. That's okay, but it's probably a good idea to define the URL in one place and then import it from that module wherever you need it.

    Constraining Our Item's Locations

    The purpose of our view is to show information about the active text editor, so it doesn't really make sense to show our item in the center of the workspace (where the text editor will be). Let's add some methods to our view class to influence where its opened:

      getDefaultLocation() {
    +    // This location will be used if the user hasn't overridden it by dragging the item elsewhere.
    +    // Valid values are "left", "right", "bottom", and "center" (the default).
    +    return 'right';
    +  }
    +
    +  getAllowedLocations() {
    +    // The locations into which the item can be moved.
    +    return ['left', 'right', 'bottom'];
    +  }
    +

    Now our item will appear in the right dock initially and users will only be able to drag it to one of the other docks.

    Show Active Editor Info

    Now that we have our view all wired up, let's update it to show some information about the active text editor. Add this to the constructor:

    this.subscriptions = atom.workspace
    +	.getCenter()
    +	.observeActivePaneItem((item) => {
    +		if (!atom.workspace.isTextEditor(item)) {
    +			message.innerText = "Open a file to see important information about it.";
    +			return;
    +		}
    +		message.innerHTML = \`
    +    <h2>\${item.getFileName() || "untitled"}</h2>
    +    <ul>
    +      <li><b>Soft Wrap:</b> \${item.softWrapped}</li>
    +      <li><b>Tab Length:</b> \${item.getTabLength()}</li>
    +      <li><b>Encoding:</b> \${item.getEncoding()}</li>
    +      <li><b>Line Count:</b> \${item.getLineCount()}</li>
    +    </ul>
    +  \`;
    +	});
    +

    Now whenever you open a text editor in the center, the view will update with some information about it.

    WARNING

    We use a template string here because it's simple and we have a lot of control over what's going into it, but this could easily result in the insertion of unwanted HTML if you're not careful. Sanitize your input and use the DOM API or a templating system when doing this for real.

    Also, don't forget to clean up the subscription in the destroy() method:

    destroy() {
    +  this.element.remove();
    +  this.subscriptions.dispose();
    +}
    +

    Serialization

    If you were to reload Atom now, you'd see that our item had disappeared. That's because we haven't told Atom how to serialize it yet. Let's do that now.

    The first step is to implement a serialize() method on our ActiveEditorInfoView class. Atom will call the serialize() method on every item in the workspace periodically to save its state.

      serialize() {
    +    return {
    +      // This is used to look up the deserializer function. It can be any string, but it needs to be
    +      // unique across all packages!
    +      deserializer: 'active-editor-info/ActiveEditorInfoView'
    +    };
    +  }
    +

    Note

    All of our view's state is derived from the active text editor so we only need the deserializer field. If we had other state that we wanted to preserve across reloads, we would just add things to the object we're returning. Just make sure that they're JSON serializable!

    Next we need to register a deserializer function that Atom can use to recreate the real object when it starts up. The best way to do that is to add a "deserializers" object to our package.json file:

    {
    +  "name": "active-editor-info",
    +  ...
    +  "deserializers": {
    +    "active-editor-info/ActiveEditorInfoView": "deserializeActiveEditorInfoView"
    +  }
    +}
    +

    Notice that the key ("active-editor-info/ActiveEditorInfoView") matches the string we used in our serialize() method above. The value ("deserializeActiveEditorInfoView") refers to a function in our main module, which we still need to add. Go back to active-editor-info.js and do that now:

      deserializeActiveEditorInfoView(serialized) {
    +    return new ActiveEditorInfoView();
    +  }
    +

    The value returned from our serialize() method will be passed to this function. Since our serialized object didn't include any state, we can just return a new ActiveEditorInfoView instance.

    Reload Atom and toggle the view with the "Active Editor Info: Toggle" command. Then reload Atom again. Your view should be just where you left it!

    Summary

    In this section, we've made a toggleable workspace item whose placement can be controlled by the user. This could be helpful when creating all sorts of visual tools for working with code!

    `,36);function y(q,A){const t=i("RouterLink"),e=i("ExternalLinkIcon");return c(),l("div",null,[d,s("p",null,[n("We saw in our "),a(t,{to:"/hacking-atom/sections/package-word-count/"},{default:o(()=>[n("Word Count")]),_:1}),n(" package how we could show information in a modal panel. However, panels aren't the only way to extend Atom's UI\u2014you can also add items to the workspace. These items can be dragged to new locations (for example, one of the docks on the edges of the window), and Atom will restore them the next time you open the project. This system is used by Atom's tree view, as well as by third party packages like "),s("a",k,[n("Nuclide"),a(e)]),n(" for its console, debugger, outline view, and diagnostics (linter results).")]),v,m,s("p",null,[n("To begin, press "),h,g,n(" to bring up the "),s("a",b,[n("Command Palette"),a(e)]),n('. Type "generate package" and select the "Package Generator: Generate Package" command, just as we did in '),a(t,{to:"/hacking-atom/sections/package-word-count/#package-generator"},{default:o(()=>[n("the section on package generation")]),_:1}),n(". Enter "),w,n(" as the name of the package.")]),f])}const x=p(r,[["render",y],["__file","package-active-editor-info.html.vue"]]);export{x as default}; diff --git a/assets/package-active-editor-info.html.f8b84ee7.js b/assets/package-active-editor-info.html.f8b84ee7.js new file mode 100644 index 0000000000..f6240d96dd --- /dev/null +++ b/assets/package-active-editor-info.html.f8b84ee7.js @@ -0,0 +1 @@ +const e=JSON.parse(`{"key":"v-878fda62","path":"/docs/launch-manual/sections/core-hacking/sections/package-active-editor-info.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Package: Active Editor Info","slug":"package-active-editor-info","link":"#package-active-editor-info","children":[{"level":3,"title":"Create the Package","slug":"create-the-package","link":"#create-the-package","children":[]},{"level":3,"title":"Add an Opener","slug":"add-an-opener","link":"#add-an-opener","children":[]},{"level":3,"title":"Updating the View","slug":"updating-the-view","link":"#updating-the-view","children":[]},{"level":3,"title":"Constraining Our Item's Locations","slug":"constraining-our-item-s-locations","link":"#constraining-our-item-s-locations","children":[]},{"level":3,"title":"Show Active Editor Info","slug":"show-active-editor-info","link":"#show-active-editor-info","children":[]},{"level":3,"title":"Serialization","slug":"serialization","link":"#serialization","children":[]},{"level":3,"title":"Summary","slug":"summary","link":"#summary","children":[]}]}],"git":{"updatedTime":1670466847000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":4.45,"words":1335},"filePathRelative":"docs/launch-manual/sections/core-hacking/sections/package-active-editor-info.md"}`);export{e as data}; diff --git a/assets/package-frontend.a8bc39dd.png b/assets/package-frontend.a8bc39dd.png new file mode 100644 index 0000000000..8a7310b10e Binary files /dev/null and b/assets/package-frontend.a8bc39dd.png differ diff --git a/assets/package-issue-link.51bb6d85.js b/assets/package-issue-link.51bb6d85.js new file mode 100644 index 0000000000..8a7921ae82 --- /dev/null +++ b/assets/package-issue-link.51bb6d85.js @@ -0,0 +1 @@ +const s="/assets/package-issue-link.65162d65.png";export{s as _}; diff --git a/assets/package-issue-link.65162d65.png b/assets/package-issue-link.65162d65.png new file mode 100644 index 0000000000..863533d0dd Binary files /dev/null and b/assets/package-issue-link.65162d65.png differ diff --git a/assets/package-modifying-text.html.02ee0996.js b/assets/package-modifying-text.html.02ee0996.js new file mode 100644 index 0000000000..b9480a6f17 --- /dev/null +++ b/assets/package-modifying-text.html.02ee0996.js @@ -0,0 +1,63 @@ +import{_ as p,o as l,c as r,a,b as n,d as s,w as i,f as t,r as c}from"./app.0e1565ce.js";const d={},u=a("h3",{id:"package-modifying-text",tabindex:"-1"},[a("a",{class:"header-anchor",href:"#package-modifying-text","aria-hidden":"true"},"#"),n(" Package: Modifying Text")],-1),k={href:"https://en.wikipedia.org/wiki/ASCII_art",target:"_blank",rel:"noopener noreferrer"},h=t(`
                                         o888
    +    ooooooo     ooooooo     ooooooo   888
    +  888     888 888     888 888     888 888
    +  888         888     888 888     888 888
    +    88ooo888    88ooo88     88ooo88  o888o
    +
    +

    This should demonstrate how to do basic text manipulation in the current text buffer and how to deal with selections.

    The final package can be viewed at https://github.com/atom/ascii-art.

    Basic Text Insertion

    `,4),m=a("kbd",{class:"platform-mac"},"Cmd+Shift+P",-1),v=a("kbd",{class:"platform-windows platform-linux"},"Ctrl+Shift+P",-1),g={href:"https://github.com/atom/command-palette",target:"_blank",rel:"noopener noreferrer"},b=a("code",null,"ascii-art",-1),f=t(`

    Now let's edit the package files to make our ASCII Art package do something interesting. Since this package doesn't need any UI, we can remove all view-related code so go ahead and delete lib/ascii-art-view.js, spec/ascii-art-view-spec.js, and styles/.

    Next, open up lib/ascii-art.js and remove all view code, so it looks like this:

    const { CompositeDisposable } = require("atom");
    +
    +module.exports = {
    +	subscriptions: null,
    +
    +	activate() {
    +		this.subscriptions = new CompositeDisposable();
    +		this.subscriptions.add(
    +			atom.commands.add("atom-workspace", {
    +				"ascii-art:convert": () => this.convert(),
    +			})
    +		);
    +	},
    +
    +	deactivate() {
    +		this.subscriptions.dispose();
    +	},
    +
    +	convert() {
    +		console.log("Convert text!");
    +	},
    +};
    +
    Create a Command

    Now let's add a command. You should namespace your commands with the package name followed by a : and then the name of the command. As you can see in the code, we called our command ascii-art:convert and we will define it to call the convert() method when it's executed.

    So far, that will simply log to the console. Let's start by making it insert something into the text buffer.

    convert() {
    +  const editor = atom.workspace.getActiveTextEditor()
    +  if (editor) {
    +    editor.insertText('Hello, World!')
    +  }
    +}
    +
    `,7),w=a("code",null,"atom.workspace.getActiveTextEditor()",-1),y=a("code",null,"convert()",-1),x={href:"https://atom.io/docs/api/latest/TextEditor#instance-insertText",target:"_blank",rel:"noopener noreferrer"},_=a("code",null,"insertText()",-1),q=t(`
    Reload the Package

    Before we can trigger ascii-art:convert, we need to load the latest code for our package by reloading the window. Run the command "Window: Reload" from the Command Palette or by pressing Alt+Cmd+Ctrl+LCtrl+Shift+F5.

    Trigger the Command

    Now open the Command Palette and search for the "Ascii Art: Convert" command. But it's not there! To fix this, open package.json and find the property called activationCommands. Activation commands make Atom launch faster by allowing Atom to delay a package's activation until it's needed. So remove the existing command and use ascii-art:convert in activationCommands:

    "activationCommands": {
    +  "atom-workspace": "ascii-art:convert"
    +}
    +

    First, reload the window by running the command "Window: Reload" from the command palette. Now when you run the "Ascii Art: Convert" command it will insert "Hello, World!" into the active editor, if any.

    Add a Key Binding

    Now let's add a key binding to trigger the ascii-art:convert command. Open keymaps/ascii-art.json and add a key binding linking Alt+Ctrl+A to the ascii-art:convert command. You can delete the pre-existing key binding since you won't need it anymore.

    When finished, the file should look like this:

    {
    +  "atom-text-editor": {
    +    "ctrl-alt-a": "ascii-art:convert"
    +  }
    +}
    +
    +

    Now reload the window and verify that the key binding works.

    WARNING

    Warning: The Atom keymap system is case-sensitive. This means that there is a distinction between a and A when creating keybindings. a means that you want to trigger the keybinding when you press A. But A means that you want to trigger the keybinding when you press Shift+A. You can also write shift-a when you want to trigger the keybinding when you press Shift+A.

    We strongly recommend always using lowercase and explicitly spelling out when you want to include Shift in your keybindings.

    Add the ASCII Art

    `,13),A={href:"https://npmjs.org/package/figlet",target:"_blank",rel:"noopener noreferrer"},T={href:"https://npmjs.org/",target:"_blank",rel:"noopener noreferrer"},C=a("code",null,"package.json",-1),j=t(`
    "dependencies": {
    +  "figlet": "1.0.8"
    +}
    +

    After saving the file, run the command "Update Package Dependencies: Update" from the Command Palette. This will install the package's node module dependencies, only figlet in this case. You will need to run "Update Package Dependencies: Update" whenever you update the dependencies field in your package.json file.

    If for some reason this doesn't work, you'll see a message saying "Failed to update package dependencies" and you will find a new npm-debug.log file in your directory. That file should give you some idea as to what went wrong.

    Now require the figlet node module in lib/ascii-art.js and instead of inserting "Hello, World!", convert the selected text to ASCII art.

    convert () {
    +  const editor = atom.workspace.getActiveTextEditor()
    +  if (editor) {
    +    const selection = editor.getSelectedText()
    +
    +    const figlet = require('figlet')
    +    const font = 'o8'
    +    figlet(selection, {font}, function (error, art) {
    +      if (error) {
    +        console.error(error)
    +      } else {
    +        editor.insertText(\`\\n\${art}\\n\`)
    +      }
    +    })
    +  }
    +}
    +

    Now reload the editor, select some text in an editor window and press Alt+Ctrl+A. It should be replaced with a ridiculous ASCII art version instead.

    `,6),I={href:"https://atom.io/docs/api/latest/TextEditor#instance-getSelectedText",target:"_blank",rel:"noopener noreferrer"},S=a("code",null,"editor.getSelectedText()",-1),N={href:"https://atom.io/docs/api/latest/TextEditor#instance-insertText",target:"_blank",rel:"noopener noreferrer"},W=a("code",null,"editor.insertText()",-1),P=a("h4",{id:"summary",tabindex:"-1"},[a("a",{class:"header-anchor",href:"#summary","aria-hidden":"true"},"#"),n(" Summary")],-1),E=a("p",null,"In this section, we've made a UI-less package that takes selected text and replaces it with a processed version. This could be helpful in creating linters or checkers for your code.",-1);function B(R,L){const e=c("ExternalLinkIcon"),o=c("RouterLink");return l(),r("div",null,[u,a("p",null,[n("Now that we have our first package written, let's go through examples of other types of packages we can make. This section will guide you though creating a simple command that replaces the selected text with "),a("a",k,[n("ascii art"),s(e)]),n('. When you run our new command with the word "cool" selected, it will be replaced with:')]),h,a("p",null,[n("To begin, press "),m,v,n(" to bring up the "),a("a",g,[n("Command Palette"),s(e)]),n('. Type "generate package" and select the "Package Generator: Generate Package" command, just as we did in '),s(o,{to:"/hacking-atom/sections/package-word-count/#package-generator"},{default:i(()=>[n("the section on package generation")]),_:1}),n(". Enter "),b,n(" as the name of the package.")]),f,a("p",null,[n("As in "),s(o,{to:"/hacking-atom/sections/package-word-count/#counting-the-words"},{default:i(()=>[n("Counting Words")]),_:1}),n(", we're using "),w,n(" to get the object that represents the active text editor. If this "),y,n(" method is called when not focused on a text editor, nothing will happen.")]),a("p",null,[n("Next we insert a string into the current text editor with the "),a("a",x,[_,s(e)]),n(' method. This will insert the text wherever the cursor currently is in the current editor. If there are selections, it will replace all selections with the "Hello, World!" text.')]),q,a("p",null,[n("Now we need to convert the selected text to ASCII art. To do this we will use the "),a("a",A,[n("figlet"),s(e)]),n(" Node module from "),a("a",T,[n("npm"),s(e)]),n(". Open "),C,n(" and add the latest version of figlet to the dependencies:")]),j,a("p",null,[n("There are a couple of new things in this example we should look at quickly. The first is the "),a("a",I,[S,s(e)]),n(" which, as you might guess, returns the text that is currently selected.")]),a("p",null,[n("We then call the Figlet code to convert that into something else and replace the current selection with it with the "),a("a",N,[W,s(e)]),n(" call.")]),P,E])}const D=p(d,[["render",B],["__file","package-modifying-text.html.vue"]]);export{D as default}; diff --git a/assets/package-modifying-text.html.8104c421.js b/assets/package-modifying-text.html.8104c421.js new file mode 100644 index 0000000000..3b1a3b9305 --- /dev/null +++ b/assets/package-modifying-text.html.8104c421.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-e59f4bf8","path":"/docs/launch-manual/sections/core-hacking/sections/package-modifying-text.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Package: Modifying Text","slug":"package-modifying-text","link":"#package-modifying-text","children":[{"level":3,"title":"Basic Text Insertion","slug":"basic-text-insertion","link":"#basic-text-insertion","children":[]},{"level":3,"title":"Add the ASCII Art","slug":"add-the-ascii-art","link":"#add-the-ascii-art","children":[]},{"level":3,"title":"Summary","slug":"summary","link":"#summary","children":[]}]}],"git":{"updatedTime":1670466847000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":3.83,"words":1149},"filePathRelative":"docs/launch-manual/sections/core-hacking/sections/package-modifying-text.md"}');export{e as data}; diff --git a/assets/package-modifying-text.html.a1bdfaa6.js b/assets/package-modifying-text.html.a1bdfaa6.js new file mode 100644 index 0000000000..da94a6b85e --- /dev/null +++ b/assets/package-modifying-text.html.a1bdfaa6.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-4284f843","path":"/docs/atom-archive/hacking-atom/sections/package-modifying-text.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"Package: Modifying Text","slug":"package-modifying-text","link":"#package-modifying-text","children":[]}],"git":{"updatedTime":1664050274000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":3}]},"readingTime":{"minutes":3.89,"words":1166},"filePathRelative":"docs/atom-archive/hacking-atom/sections/package-modifying-text.md"}');export{e as data}; diff --git a/assets/package-modifying-text.html.ca206c57.js b/assets/package-modifying-text.html.ca206c57.js new file mode 100644 index 0000000000..3c5c743cc5 --- /dev/null +++ b/assets/package-modifying-text.html.ca206c57.js @@ -0,0 +1,63 @@ +import{_ as i,o as c,c as p,a as n,b as e,d as a,e as t,f as o,r as l}from"./app.0e1565ce.js";const r={},d=n("h2",{id:"package-modifying-text",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#package-modifying-text","aria-hidden":"true"},"#"),e(" Package: Modifying Text")],-1),u={href:"https://en.wikipedia.org/wiki/ASCII_art",target:"_blank",rel:"noopener noreferrer"},k=n("pre",null,` o888 + ooooooo ooooooo ooooooo 888 + 888 888 888 888 888 888 888 + 888 888 888 888 888 888 + 88ooo888 88ooo88 88ooo88 o888o + +`,-1),h=n("p",null,"This should demonstrate how to do basic text manipulation in the current text buffer and how to deal with selections.",-1),m=n("p",null,"The final package can be viewed at https://github.com/pulsar-edit/ascii-art.",-1),v=n("h3",{id:"basic-text-insertion",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#basic-text-insertion","aria-hidden":"true"},"#"),e(" Basic Text Insertion")],-1),g=n("strong",null,[n("em",null,"LNX/WIN")],-1),b=n("kbd",null,"Ctrl+Shift+P",-1),w=n("strong",null,[n("em",null,"MAC")],-1),f=n("kbd",null,"Cmd+Shift+P",-1),y={href:"https://github.com/pulsar-edit/command-palette",target:"_blank",rel:"noopener noreferrer"},_=n("a",{href:"#package-generator"},"the section on package generation",-1),x=n("code",null,"ascii-art",-1),q=o(`

    Now let's edit the package files to make our ASCII Art package do something interesting. Since this package doesn't need any UI, we can remove all view-related code so go ahead and delete lib/ascii-art-view.js, spec/ascii-art-view-spec.js, and styles/.

    Next, open up lib/ascii-art.js and remove all view code, so it looks like this:

    const { CompositeDisposable } = require("atom");
    +
    +module.exports = {
    +	subscriptions: null,
    +
    +	activate() {
    +		this.subscriptions = new CompositeDisposable();
    +		this.subscriptions.add(
    +			atom.commands.add("atom-workspace", {
    +				"ascii-art:convert": () => this.convert(),
    +			})
    +		);
    +	},
    +
    +	deactivate() {
    +		this.subscriptions.dispose();
    +	},
    +
    +	convert() {
    +		console.log("Convert text!");
    +	},
    +};
    +

    Create a Command

    Now let's add a command. You should namespace your commands with the package name followed by a : and then the name of the command. As you can see in the code, we called our command ascii-art:convert and we will define it to call the convert() method when it's executed.

    So far, that will simply log to the console. Let's start by making it insert something into the text buffer.

    convert() {
    +  const editor = atom.workspace.getActiveTextEditor()
    +  if (editor) {
    +    editor.insertText('Hello, World!')
    +  }
    +}
    +

    As in Counting Words, we're using atom.workspace.getActiveTextEditor() to get the object that represents the active text editor. If this convert() method is called when not focused on a text editor, nothing will happen.

    `,8),T={href:"https://atom.io/docs/api/latest/TextEditor#instance-insertText",target:"_blank",rel:"noopener noreferrer"},A=n("code",null,"insertText()",-1),C=o(`

    Reload the Package

    Before we can trigger ascii-art:convert, we need to load the latest code for our package by reloading the window. Run the command "Window: Reload" from the Command Palette or by pressing LNX/WIN: Ctrl+Shift+F5 - MAC: Alt+Cmd+Ctrl+L

    Trigger the Command

    Now open the Command Palette and search for the Ascii Art: Convert command. But it's not there! To fix this, open package.json and find the property called activationCommands. Activation commands make Pulsar launch faster by allowing Pulsar to delay a package's activation until it's needed. So remove the existing command and use ascii-art:convert in activationCommands:

    "activationCommands": {
    +  "atom-workspace": "ascii-art:convert"
    +}
    +

    First, reload the window by running the command "Window: Reload" from the command palette. Now when you run the Ascii Art: Convert command it will insert "Hello, World!" into the active editor, if any.

    Add a Key Binding

    Now let's add a key binding to trigger the ascii-art:convert command. Open keymaps/ascii-art.json and add a key binding linking Alt+Ctrl+A to the ascii-art:convert command. You can delete the pre-existing key binding since you won't need it anymore.

    When finished, the file should look like this:

    {
    +  "atom-text-editor": {
    +    "ctrl-alt-a": "ascii-art:convert"
    +  }
    +}
    +
    +

    Now reload the window and verify that the key binding works.

    WARNING

    The Pulsar keymap system is case-sensitive. This means that there is a distinction between a and A when creating keybindings. a means that you want to trigger the keybinding when you press A. But A means that you want to trigger the keybinding when you press Shift+A. You can also write shift-a when you want to trigger the keybinding when you press Shift+A.

    We strongly recommend always using lowercase and explicitly spelling out when you want to include Shift in your keybindings.

    Add the ASCII Art

    `,13),j={href:"https://npmjs.org/package/figlet",target:"_blank",rel:"noopener noreferrer"},I={href:"https://npmjs.org/",target:"_blank",rel:"noopener noreferrer"},N=n("code",null,"package.json",-1),S=o(`
    "dependencies": {
    +  "figlet": "1.0.8"
    +}
    +

    After saving the file, run the command Update Package Dependencies: Update from the Command Palette. This will install the package's node module dependencies, only figlet in this case. You will need to run Update Package Dependencies: Update whenever you update the dependencies field in your package.json file.

    If for some reason this doesn't work, you'll see a message saying "Failed to update package dependencies" and you will find a new npm-debug.log file in your directory. That file should give you some idea as to what went wrong.

    Now require the figlet node module in lib/ascii-art.js and instead of inserting "Hello, World!", convert the selected text to ASCII art.

    convert () {
    +  const editor = atom.workspace.getActiveTextEditor()
    +  if (editor) {
    +    const selection = editor.getSelectedText()
    +
    +    const figlet = require('figlet')
    +    const font = 'o8'
    +    figlet(selection, {font}, function (error, art) {
    +      if (error) {
    +        console.error(error)
    +      } else {
    +        editor.insertText(\`\\n\${art}\\n\`)
    +      }
    +    })
    +  }
    +}
    +

    Now reload the editor, select some text in an editor window and press Alt+Ctrl+A. It should be replaced with a ridiculous ASCII art version instead.

    `,6),P={href:"https://atom.io/docs/api/latest/TextEditor#instance-getSelectedText",target:"_blank",rel:"noopener noreferrer"},W=n("code",null,"editor.getSelectedText()",-1),E={href:"https://atom.io/docs/api/latest/TextEditor#instance-insertText",target:"_blank",rel:"noopener noreferrer"},B=n("code",null,"editor.insertText()",-1),O=n("h3",{id:"summary",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#summary","aria-hidden":"true"},"#"),e(" Summary")],-1),R=n("p",null,"In this section, we've made a UI-less package that takes selected text and replaces it with a processed version. This could be helpful in creating linters or checkers for your code.",-1);function D(L,U){const s=l("ExternalLinkIcon");return c(),p("div",null,[d,n("p",null,[e("Now that we have our first package written, let's go through examples of other types of packages we can make. This section will guide you though creating a simple command that replaces the selected text with "),n("a",u,[e("ascii art"),a(s)]),e('. When you run our new command with the word "cool" selected, it will be replaced with:')]),k,h,m,v,n("p",null,[e("To begin, press "),g,e(": "),b,e(" - "),w,e(": "),f,e(" to bring up the "),n("a",y,[e("Command Palette"),a(s)]),e('. Type "generate package" and select the "Package Generator: Generate Package" command, just as we did in '),_,e(". Enter "),x,e(" as the name of the package.")]),q,n("p",null,[e("Next we insert a string into the current text editor with the "),n("a",T,[A,a(s)]),t("TODO: Replace with API link once documented)"),e(' method. This will insert the text wherever the cursor currently is in the current editor. If there are selections, it will replace all selections with the "Hello, World!" text.')]),C,n("p",null,[e("Now we need to convert the selected text to ASCII art. To do this we will use the "),n("a",j,[e("figlet"),a(s)]),e(" Node module from "),n("a",I,[e("npm"),a(s)]),e(". Open "),N,e(" and add the latest version of figlet to the dependencies:")]),S,n("p",null,[e("There are a couple of new things in this example we should look at quickly. The first is the "),n("a",P,[W,a(s)]),t("TODO: Replace with API link once documented)"),e(" which, as you might guess, returns the text that is currently selected.")]),n("p",null,[e("We then call the Figlet code to convert that into something else and replace the current selection with it with the "),n("a",E,[B,a(s)]),t("TODO: Replace with API link once documented)"),e(" call.")]),O,R])}const F=i(r,[["render",D],["__file","package-modifying-text.html.vue"]]);export{F as default}; diff --git a/assets/package-settings.ad8a3253.png b/assets/package-settings.ad8a3253.png new file mode 100644 index 0000000000..828653355d Binary files /dev/null and b/assets/package-settings.ad8a3253.png differ diff --git a/assets/package-specific-settings.970b531c.png b/assets/package-specific-settings.970b531c.png new file mode 100644 index 0000000000..3a19db7ecb Binary files /dev/null and b/assets/package-specific-settings.970b531c.png differ diff --git a/assets/package-webhook.7e0f3c29.png b/assets/package-webhook.7e0f3c29.png new file mode 100644 index 0000000000..37a660c8c7 Binary files /dev/null and b/assets/package-webhook.7e0f3c29.png differ diff --git a/assets/package-word-count.html.0ef1249a.js b/assets/package-word-count.html.0ef1249a.js new file mode 100644 index 0000000000..5f25acecab --- /dev/null +++ b/assets/package-word-count.html.0ef1249a.js @@ -0,0 +1,212 @@ +import{_ as c,a as l,b as u,c as r,d,e as k}from"./spec-suite.8f5e854d.js";import{_ as m}from"./dev-tools.1b7b2813.js";import{_ as h,o as v,c as g,a as n,b as s,d as a,w as p,f as t,r as i}from"./app.0e1565ce.js";const y={},b=n("h3",{id:"package-word-count",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#package-word-count","aria-hidden":"true"},"#"),s(" Package: Word Count")],-1),w=n("p",null,"Let's get started by writing a very simple package and looking at some of the tools needed to develop one effectively. We'll start by writing a package that tells you how many words are in the current buffer and display it in a small modal window.",-1),f=n("h4",{id:"package-generator",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#package-generator","aria-hidden":"true"},"#"),s(" Package Generator")],-1),q={href:"https://github.com/atom/package-generator",target:"_blank",rel:"noopener noreferrer"},_=n("p",null,[s('You can run the generator by invoking the command palette and searching for "Generate Package". A dialog will appear asking you to name your new project. Name it '),n("code",null,"your-name-word-count"),s(". Atom will then create that directory and fill it out with a skeleton project and link it into your "),n("span",{class:"platform-mac platform-linux"},[n("code",null,"~/.atom/packages")]),n("span",{class:"platform-windows"},[n("code",null,"%USERPROFILE%\\.atom\\packages")]),s(" directory so it's loaded when you launch your editor next time.")],-1),x={class:"custom-container note"},j=n("p",{class:"custom-container-title"},"Note",-1),C=n("strong",null,"Note:",-1),T={href:"https://atom.io/packages",target:"_blank",rel:"noopener noreferrer"},A=n("code",null,"your-name-word-count",-1),I=n("em",null,"should",-1),W=t('

    Basic generated Atom package

    You can see that Atom has created about a dozen files that make up the package. Let's take a look at each of them to get an idea of how a package is structured, then we can modify them to get our word count functionality.

    The basic package layout is as follows:

    my-package/
    +\u251C\u2500 grammars/
    +\u251C\u2500 keymaps/
    +\u251C\u2500 lib/
    +\u251C\u2500 menus/
    +\u251C\u2500 spec/
    +\u251C\u2500 snippets/
    +\u251C\u2500 styles/
    +\u251C\u2500 index.js
    +\u2514\u2500 package.json
    +

    Not every package will have (or need) all of these directories and the package generator doesn't create snippets or grammars. Let's see what some of these are so we can start messing with them.

    package.json
    `,6),S={href:"https://en.wikipedia.org/wiki/Npm_(software)",target:"_blank",rel:"noopener noreferrer"},N=n("code",null,"package.json",-1),P={href:"https://docs.npmjs.com/files/package.json",target:"_blank",rel:"noopener noreferrer"},V=n("code",null,"package.json",-1),z=n("code",null,"package.json",-1),D=t(`
    • main: the path to the JavaScript file that's the entry point to your package. If this is missing, Atom will default to looking for an index.coffee or index.js.
    • styles: an Array of Strings identifying the order of the style sheets your package needs to load. If not specified, style sheets in the styles directory are added alphabetically.
    • keymaps: an Array of Strings identifying the order of the key mappings your package needs to load. If not specified, mappings in the keymaps directory are added alphabetically.
    • menus: an Array of Strings identifying the order of the menu mappings your package needs to load. If not specified, mappings in the menus directory are added alphabetically.
    • snippets: an Array of Strings identifying the order of the snippets your package needs to load. If not specified, snippets in the snippets directory are added alphabetically.
    • activationCommands: an Object identifying commands that trigger your package's activation. The keys are CSS selectors, the values are Arrays of Strings identifying the command. The loading of your package is delayed until one of these events is triggered within the associated scope defined by the CSS selector. If not specified, the activate() method of your main export will be called when your package is loaded.
    • activationHooks: an Array of Strings identifying hooks that trigger your package's activation. The loading of your package is delayed until one of these hooks are triggered. Currently, there are three activation hooks:
      • core:loaded-shell-environment for when Atom has finished loading the shell environment variables
      • scope.name:root-scope-used for when a file is opened from the specified language (e.g. source.ruby:root-scope-used)
      • language-package-name:grammar-used for when a specific language package is used (e.g., my-special-language-javascript:grammar-used)
    • workspaceOpeners: An Array of Strings identifying URIs that trigger your package's activation. For example, say your package registers a custom opener for atom://my-custom-panel. By including that string in workspaceOpeners, your package will defer its activation until that URI is opened.

    The package.json in the package we've just generated looks like this currently:

    {
    +	"name": "your-name-word-count",
    +	"main": "./lib/your-name-word-count",
    +	"version": "0.0.0",
    +	"description": "A short description of your package",
    +	"activationCommands": {
    +		"atom-workspace": "your-name-word-count:toggle"
    +	},
    +	"repository": "https://github.com/atom/your-name-word-count",
    +	"license": "MIT",
    +	"engines": {
    +		"atom": ">=1.0.0 <2.0.0"
    +	},
    +	"dependencies": {}
    +}
    +

    If you wanted to use activationHooks, you might have:

    {
    +	"name": "your-name-word-count",
    +	"main": "./lib/your-name-word-count",
    +	"version": "0.0.0",
    +	"description": "A short description of your package",
    +	"activationHooks": [
    +		"language-javascript:grammar-used",
    +		"language-coffee-script:grammar-used"
    +	],
    +	"repository": "https://github.com/atom/your-name-word-count",
    +	"license": "MIT",
    +	"engines": {
    +		"atom": ">=1.0.0 <2.0.0"
    +	},
    +	"dependencies": {}
    +}
    +

    One of the first things you should do is ensure that this information is filled out. The name, description, repository URL the project will be at, and the license can all be filled out immediately. The other information we'll get into more detail on as we go.

    WARNING

    Warning: Do not forget to update the repository URL. The one generated for you is invalid by design and will prevent you from publishing your package until updated.

    Source Code

    If you want to extend Atom's behavior, your package should contain a single top-level module, which you export from whichever file is indicated by the main key in your package.json file. In the package we just generated, the main package file is lib/your-name-word-count.js. The remainder of your code should be placed in the lib directory, and required from your top-level file. If the main key is not in your package.json file, it will look for index.js or index.coffee as the main entry point.

    Your package's top-level module is a singleton object that manages the lifecycle of your extensions to Atom. Even if your package creates ten different views and appends them to different parts of the DOM, it's all managed from your top-level object.

    Your package's top-level module can implement the following basic methods:

    `,11),E=n("li",null,[n("code",null,"activate(state)"),s(": This "),n("strong",null,"optional"),s(" method is called when your package is activated. It is passed the state data from the last time the window was serialized if your module implements the "),n("code",null,"serialize()"),s(" method. Use this to do initialization work when your package is started (like setting up DOM elements or binding events). If this method returns a promise the package will be considered loading until the promise resolves (or rejects).")],-1),Y=n("code",null,"initialize(state)",-1),L=n("strong",null,"optional",-1),O=n("code",null,"activate()",-1),M=n("code",null,"initialize()",-1),R=n("code",null,"activate()",-1),U=n("code",null,"initialize()",-1),B=n("li",null,[n("code",null,"serialize()"),s(": This "),n("strong",null,"optional"),s(" method is called when the window is shutting down, allowing you to return JSON to represent the state of your component. When the window is later restored, the data you returned is passed to your module's "),n("code",null,"activate"),s(" method so you can restore your view to where the user left off.")],-1),F=n("li",null,[n("code",null,"deactivate()"),s(": This "),n("strong",null,"optional"),s(" method is called when the window is shutting down and when the package is disabled. If your package is watching any files or holding external resources in any other way, release them here. You should also dispose of all subscriptions you're holding on to.")],-1),G=n("h5",{id:"style-sheets",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#style-sheets","aria-hidden":"true"},"#"),s(" Style Sheets")],-1),K=n("code",null,"styles",-1),H={href:"http://lesscss.org",target:"_blank",rel:"noopener noreferrer"},J=n("p",null,[s("Ideally, you won't need much in the way of styling. Atom provides a standard set of components which define both the colors and UI elements for any package that fits into Atom seamlessly. You can view all of Atom's UI components by opening the styleguide: open the command palette "),n("kbd",{class:"platform-mac"},"Cmd+Shift+P"),n("kbd",{class:"platform-windows platform-linux"},"Ctrl+Shift+P"),s(" and search for "),n("code",null,"styleguide"),s(", or type "),n("kbd",{class:"platform-mac"},"Cmd+Ctrl+Shift+G"),n("kbd",{class:"platform-windows platform-linux"},"Ctrl+Shift+G"),s(".")],-1),$=n("em",null,"do",-1),Q=n("em",null,"must",-1),X={href:"https://github.com/atom/atom-dark-ui/blob/master/styles/ui-variables.less",target:"_blank",rel:"noopener noreferrer"},Z=t(`

    An optional styleSheets array in your package.json can list the style sheets by name to specify a loading order; otherwise, style sheets are loaded alphabetically.

    Keymaps

    You can provide key bindings for commonly used actions for your extension, especially if you're also adding a new command. In our new package, we have a keymap filled in for us already in the keymaps/your-name-word-count.json file:

    {
    +  "atom-workspace": {
    +    "ctrl-alt-o": "your-name-word-count:toggle"
    +  }
    +}
    +

    This means that if you press Alt+Ctrl+O, our package will run the your-name-word-count:toggle command. We'll look at that code next, but if you want to change the default key mapping, you can do that in this file.

    Keymaps are placed in the keymaps subdirectory. By default, all keymaps are loaded in alphabetical order. An optional keymaps array in your package.json can specify which keymaps to load and in what order.

    Keybindings are executed by determining which element the keypress occurred on. In the example above, the your-name-word-count:toggle command is executed when pressing Alt+Ctrl+O on the atom-workspace element. Because the atom-workspace element is the parent of the entire Atom UI, this means the key combination will work anywhere in the application.

    `,7),nn=t(`

    Menus are placed in the menus subdirectory. This defines menu elements like what pops up when you right click a context-menu or would go in the application menu to trigger functionality in your plugin.

    By default, all menus are loaded in alphabetical order. An optional menus array in your package.json can specify which menus to load and in what order.

    Application Menu

    It's recommended that you create an application menu item under the Packages menu for common actions with your package that aren't tied to a specific element. If we look in the menus/your-name-word-count.json file that was generated for us, we'll see a section that looks like this:

    
    +"menu": [
    +  {
    +    "label": "Packages",
    +    "submenu": [
    +      {
    +        "label": "Word Count",
    +        "submenu": [
    +          {
    +            "label": "Toggle",
    +            "command": "your-name-word-count:toggle"
    +          }
    +        ]
    +      }
    +    ]
    +  }
    +]
    +
    +

    This section puts a "Toggle" menu item under a menu group named "Your Name Word Count" in the "Packages" menu.

    Application Menu Item

    When you select that menu item, it will run the your-name-word-count:toggle command, which we'll look at in a bit.

    The menu templates you specify are merged with all other templates provided by other packages in the order which they were loaded.

    Context Menu

    It's recommended to specify a context menu item for commands that are linked to specific parts of the interface. In our menus/your-name-word-count.json file, we can see an auto-generated section that looks like this:

    "context-menu": {
    +    "atom-text-editor": [
    +      {
    +        "label": "Toggle your-name-word-count",
    +        "command": "your-name-word-count:toggle"
    +      }
    +    ]
    +  }
    +

    This adds a "Toggle Word Count" menu option to the menu that pops up when you right-click in an Atom text editor pane.

    Context Menu Entry

    When you click that it will again run the your-name-word-count:toggle method in your code.

    Context menus are created by determining which element was selected and then adding all of the menu items whose selectors match that element (in the order which they were loaded). The process is then repeated for the elements until reaching the top of the DOM tree.

    You can also add separators and submenus to your context menus. To add a submenu, provide a submenu key instead of a command. To add a separator, add an item with a single type: 'separator' key/value pair. For instance, you could do something like this:

    {
    +  "context-menu": {
    +    "atom-workspace": [
    +      {
    +        "label": "Text",
    +        "submenu": [
    +          {
    +            "label": "Inspect Element",
    +            "command": "core:inspect"
    +          },
    +          {
    +            "type": "separator"
    +          },
    +          {
    +            "label": "Selector All",
    +            "command": "core:select-all"
    +          },
    +          {
    +            "type": "separator"
    +          },
    +          {
    +            "label": "Deleted Selected Text",
    +            "command": "core:delete"
    +          }
    +        ]
    +      }
    +    ]
    +  }
    +}
    +

    Developing Our Package

    Currently with the generated package we have, if we run that your-name-word-count:toggle command through the menu or the command palette, we'll get a dialog that says "The YourNameWordCount package is Alive! It's ALIVE!".

    Wordcount Package is Alive Dialog

    Understanding the Generated Code

    Let's take a look at the code in our lib directory and see what is happening.

    There are two files in our lib directory. One is the main file (lib/your-name-word-count.js), which is pointed to in the package.json file as the main file to execute for this package. This file handles the logic of the whole plugin.

    The second file is a View class, lib/your-name-word-count-view.js, which handles the UI elements of the package. Let's look at this file first, since it's pretty simple.

    export default class YourNameWordCountView {
    +	constructor(serializedState) {
    +		// Create root element
    +		this.element = document.createElement("div");
    +		this.element.classList.add("your-name-word-count");
    +
    +		// Create message element
    +		const message = document.createElement("div");
    +		message.textContent = "The YourNameWordCount package is Alive! It's ALIVE!";
    +		message.classList.add("message");
    +		this.element.appendChild(message);
    +	}
    +
    +	// Returns an object that can be retrieved when package is activated
    +	serialize() {}
    +
    +	// Tear down any state and detach
    +	destroy() {
    +		this.element.remove();
    +	}
    +
    +	getElement() {
    +		return this.element;
    +	}
    +}
    +

    Basically the only thing happening here is that when the View class is created, it creates a simple div element and adds the your-name-word-count class to it (so we can find or style it later) and then adds the "Your Name Word Count package is Alive!" text to it. There is also a getElement method which returns that div. The serialize and destroy methods don't do anything and we won't have to worry about that until another example.

    Notice that we're simply using the basic browser DOM methods: createElement() and appendChild().

    The second file we have is the main entry point to the package. Again, because it's referenced in the package.json file. Let's take a look at that file.

    import YourNameWordCountView from "./your-name-word-count-view";
    +import { CompositeDisposable } from "atom";
    +
    +export default {
    +	yourNameWordCountView: null,
    +	modalPanel: null,
    +	subscriptions: null,
    +
    +	activate(state) {
    +		this.yourNameWordCountView = new YourNameWordCountView(
    +			state.yourNameWordCountViewState
    +		);
    +		this.modalPanel = atom.workspace.addModalPanel({
    +			item: this.yourNameWordCountView.getElement(),
    +			visible: false,
    +		});
    +
    +		// Events subscribed to in atom's system can be easily cleaned up with a CompositeDisposable
    +		this.subscriptions = new CompositeDisposable();
    +
    +		// Register command that toggles this view
    +		this.subscriptions.add(
    +			atom.commands.add("atom-workspace", {
    +				"your-name-word-count:toggle": () => this.toggle(),
    +			})
    +		);
    +	},
    +
    +	deactivate() {
    +		this.modalPanel.destroy();
    +		this.subscriptions.dispose();
    +		this.yourNameWordCountView.destroy();
    +	},
    +
    +	serialize() {
    +		return {
    +			yourNameWordCountViewState: this.yourNameWordCountView.serialize(),
    +		};
    +	},
    +
    +	toggle() {
    +		console.log("YourNameWordCount was toggled!");
    +		return this.modalPanel.isVisible()
    +			? this.modalPanel.hide()
    +			: this.modalPanel.show();
    +	},
    +};
    +

    There is a bit more going on here. First of all we can see that we are defining four methods. The only required one is activate. The deactivate and serialize methods are expected by Atom but optional. The toggle method is one Atom is not looking for, so we'll have to invoke it somewhere for it to be called, which you may recall we do both in the activationCommands section of the package.json file and in the action we have in the menu file.

    The deactivate method simply destroys the various class instances we've created and the serialize method simply passes on the serialization to the View class. Nothing too exciting here.

    The activate command does a number of things. For one, it is not called automatically when Atom starts up, it is first called when one of the activationCommands as defined in the package.json file are called. In this case, activate is only called the first time the toggle command is called. If nobody ever invokes the menu item or hotkey, this code is never called.

    This method does two things. The first is that it creates an instance of the View class we have and adds the element that it creates to a hidden modal panel in the Atom workspace.

    this.yourNameWordCountView = new YourNameWordCountView(
    +	state.yourNameWordCountViewState
    +);
    +this.modalPanel = atom.workspace.addModalPanel({
    +	item: this.yourNameWordCountView.getElement(),
    +	visible: false,
    +});
    +

    We'll ignore the state stuff for now, since it's not important for this simple plugin. The rest should be fairly straightforward.

    The next thing this method does is create an instance of the CompositeDisposable class so it can register all the commands that can be called from the plugin so other plugins could subscribe to these events.

    // Events subscribed to in atom's system can be easily cleaned up with a CompositeDisposable
    +this.subscriptions = new CompositeDisposable();
    +
    +// Register command that toggles this view
    +this.subscriptions.add(
    +	atom.commands.add("atom-workspace", {
    +		"your-name-word-count:toggle": () => this.toggle(),
    +	})
    +);
    +

    Next we have the toggle method. This method simply toggles the visibility of the modal panel that we created in the activate method.

    toggle() {
    +  console.log('YourNameWordCount was toggled!');
    +  return (
    +    this.modalPanel.isVisible() ?
    +    this.modalPanel.hide() :
    +    this.modalPanel.show()
    +  );
    +}
    +

    This should be fairly simple to understand. We're looking to see if the modal element is visible and hiding or showing it depending on its current state.

    The Flow

    So, let's review the actual flow in this package.

    1. Atom starts up
    2. Atom starts loading packages
    3. Atom reads your package.json
    4. Atom loads keymaps, menus, styles and the main module
    5. Atom finishes loading packages
    6. At some point, the user executes your package command your-name-word-count:toggle
    7. Atom executes the activate method in your main module which sets up the UI by creating the hidden modal view
    8. Atom executes the package command your-name-word-count:toggle which reveals the hidden modal view
    9. At some point, the user executes the your-name-word-count:toggle command again
    10. Atom executes the command which hides the modal view
    11. Eventually, Atom is shut down which can trigger any serializations that your package has defined

    Tip

    Tip: Keep in mind that the flow will be slightly different if you choose not to use activationCommands in your package.

    Counting the Words

    So now that we understand what is happening, let's modify the code so that our little modal box shows us the current word count instead of static text.

    We'll do this in a very simple way. When the dialog is toggled, we'll count the words right before displaying the modal. So let's do this in the toggle command. If we add some code to count the words and ask the view to update itself, we'll have something like this:

    toggle() {
    +  if (this.modalPanel.isVisible()) {
    +    this.modalPanel.hide();
    +  } else {
    +    const editor = atom.workspace.getActiveTextEditor();
    +    const words = editor.getText().split(/\\s+/).length;
    +    this.yourNameWordCountView.setCount(words);
    +    this.modalPanel.show();
    +  }
    +}
    +
    `,50),sn={href:"https://atom.io/docs/api/latest/Workspace#instance-getActiveTextEditor",target:"_blank",rel:"noopener noreferrer"},an=n("code",null,"atom.workspace.getActiveTextEditor()",-1),en={href:"https://atom.io/docs/api/latest/TextEditor#instance-getText",target:"_blank",rel:"noopener noreferrer"},tn=n("code",null,"getText()",-1),on=t(`

    Finally, we tell our view to update the word count it displays by calling the setCount() method on our view and then showing the modal again. Since that method doesn't yet exist, let's create it now.

    We can add this code to the end of our your-name-word-count-view.js file:

    setCount(count) {
    +  const displayText = \`There are \${count} words.\`;
    +  this.element.children[0].textContent = displayText;
    +}
    +

    Pretty simple! We take the count number that was passed in and place it into a string that we then stick into the element that our view is controlling.

    Note

    Note: To see your changes, you'll need to reload the code. You can do this by reloading the window (The window:reload command in the Command Palette). A common practice is to have two Atom windows, one for developing your package, and one for testing and reloading.

    Word Count Working

    Basic Debugging

    You'll notice a few console.log statements in the code. One of the cool things about Atom being built on Chromium is that you can use some of the same debugging tools available to you that you have when doing web development.

    To open up the Developer Console, press Alt+Cmd+ICtrl+Shift+I, or choose the menu option View > Developer > Toggle Developer Tools.

    Developer Tools Debugging

    From here you can inspect objects, run code and view console output just as though you were debugging a web site.

    Testing

    Your package should have tests, and if they're placed in the spec directory, they can be run by Atom.

    ',13),pn={href:"https://jasmine.github.io/archives/1.3/introduction",target:"_blank",rel:"noopener noreferrer"},cn=t('
    Running Tests

    Once you've got your test suite written, you can run it by pressing Alt+Cmd+Ctrl+PAlt+Ctrl+P or via the View > Developer > Run Package Specs menu. Our generated package comes with an example test suite, so you can run this right now to see what happens.

    Spec Suite Results

    You can also use the atom --test spec command to run them from the command line. It prints the test output and results to the console and returns the proper status code depending on whether the tests passed or failed.

    Summary

    ',5);function ln(un,rn){const e=i("ExternalLinkIcon"),o=i("RouterLink");return v(),g("div",null,[b,w,f,n("p",null,[s("The simplest way to start a package is to use the built-in package generator that ships with Atom. As you might expect by now, this generator is itself a separate package implemented in "),n("a",q,[s("package-generator"),a(e)]),s(".")]),_,n("div",x,[j,n("p",null,[C,s(" You may encounter a situation where your package is not loaded. That is because a new package using the same name as an actual package hosted on "),n("a",T,[s("atom.io"),a(e)]),s(' (e.g. "wordcount" and "word-count") is not being loaded as you expected. If you follow our suggestion above of using the '),A,s(" package name, you "),I,s(" be safe \u{1F600}")])]),W,n("p",null,[s("Similar to "),n("a",S,[s("Node modules"),a(e)]),s(", Atom packages contain a "),N,s(' file in their top-level directory. This file contains metadata about the package, such as the path to its "main" module, library dependencies, and manifests specifying the order in which its resources should be loaded.')]),n("p",null,[s("In addition to some of the regular "),n("a",P,[s("Node "),V,s(" keys"),a(e)]),s(" available, Atom "),z,s(" files have their own additions.")]),D,n("ul",null,[E,n("li",null,[Y,s(": (Available in Atom 1.14 and above) This "),L,s(" method is similar to "),O,s(" but is called earlier. Whereas activation occurs after the workspace has been deserialized (and can therefore happen after "),a(o,{to:"/behind-atom/sections/serialization-in-atom/#serialization-methods"},{default:p(()=>[s("your package's deserializers")]),_:1}),s(" have been called), "),M,s(" is guaranteed to be called before everything. Use "),R,s(" if you want to be sure that the workspace is ready; use "),U,s(" if you need to do some setup prior to your deserializers or view providers being invoked.")]),B,F]),G,n("p",null,[s("Style sheets for your package should be placed in the "),K,s(" directory. Any style sheets in this directory will be loaded and attached to the DOM when your package is activated. Style sheets can be written as CSS or "),n("a",H,[s("Less"),a(e)]),s(", but Less is recommended.")]),J,n("p",null,[s("If you "),$,s(" need special styling, try to keep only structural styles in the package style sheets. If you "),Q,s(" specify colors and sizing, these should be taken from the active theme's "),n("a",X,[s("ui-variables.less"),a(e)]),s(".")]),Z,n("p",null,[s("We'll cover more advanced keybinding stuff a bit later in "),a(o,{to:"/behind-atom/sections/keymaps-in-depth/"},{default:p(()=>[s("Keymaps in Depth")]),_:1}),s(".")]),nn,n("p",null,[s("Let's look at the 3 lines we've added. First we get an instance of the current editor object (where our text to count is) by calling "),n("a",sn,[an,a(e)]),s(".")]),n("p",null,[s("Next we get the number of words by calling "),n("a",en,[tn,a(e)]),s(" on our new editor object, then splitting that text on whitespace with a regular expression and then getting the length of that array.")]),on,n("p",null,[s("Under the hood, "),n("a",pn,[s("Jasmine v1.3"),a(e)]),s(" executes your tests, so you can assume that any DSL available there is also available to your package.")]),cn,n("p",null,[s("We've now generated, customized and tested our first plugin for Atom. Congratulations! Now let's go ahead and "),a(o,{to:"/docs/atom-archive/hacking-atom/publishing/"},{default:p(()=>[s("publish")]),_:1}),s(" it so it's available to the world.")])])}const hn=h(y,[["render",ln],["__file","package-word-count.html.vue"]]);export{hn as default}; diff --git a/assets/package-word-count.html.49781908.js b/assets/package-word-count.html.49781908.js new file mode 100644 index 0000000000..71af8c07e8 --- /dev/null +++ b/assets/package-word-count.html.49781908.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-36ba24e9","path":"/docs/atom-archive/hacking-atom/sections/package-word-count.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"Package: Word Count","slug":"package-word-count","link":"#package-word-count","children":[]}],"git":{"updatedTime":1664050274000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":3}]},"readingTime":{"minutes":12.95,"words":3886},"filePathRelative":"docs/atom-archive/hacking-atom/sections/package-word-count.md"}');export{e as data}; diff --git a/assets/package-word-count.html.7f5691c3.js b/assets/package-word-count.html.7f5691c3.js new file mode 100644 index 0000000000..361b96db6c --- /dev/null +++ b/assets/package-word-count.html.7f5691c3.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-93d2e1ac","path":"/docs/launch-manual/sections/core-hacking/sections/package-word-count.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Package: Word Count","slug":"package-word-count","link":"#package-word-count","children":[{"level":3,"title":"Package Generator","slug":"package-generator","link":"#package-generator","children":[]},{"level":3,"title":"Developing Our Package","slug":"developing-our-package","link":"#developing-our-package","children":[]},{"level":3,"title":"Basic Debugging","slug":"basic-debugging","link":"#basic-debugging","children":[]},{"level":3,"title":"Testing","slug":"testing","link":"#testing","children":[]},{"level":3,"title":"Summary","slug":"summary","link":"#summary","children":[]}]}],"git":{"updatedTime":1670466847000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":12.85,"words":3855},"filePathRelative":"docs/launch-manual/sections/core-hacking/sections/package-word-count.md"}');export{e as data}; diff --git a/assets/package-word-count.html.96694544.js b/assets/package-word-count.html.96694544.js new file mode 100644 index 0000000000..f0c776b9cb --- /dev/null +++ b/assets/package-word-count.html.96694544.js @@ -0,0 +1,212 @@ +import{_ as p,a as i,b as c,c as l,d as r,e as u}from"./spec-suite.8f5e854d.js";import{_ as d}from"./dev-tools.1b7b2813.js";import{_ as k,o as m,c as h,a as n,b as s,d as e,e as o,f as t,r as v}from"./app.0e1565ce.js";const g={},y=n("h2",{id:"package-word-count",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#package-word-count","aria-hidden":"true"},"#"),s(" Package: Word Count")],-1),b=n("p",null,"Let's get started by writing a very simple package and looking at some of the tools needed to develop one effectively. We'll start by writing a package that tells you how many words are in the current buffer and display it in a small modal window.",-1),w=n("h3",{id:"package-generator",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#package-generator","aria-hidden":"true"},"#"),s(" Package Generator")],-1),f={href:"https://github.com/pulsar-edit/package-generator",target:"_blank",rel:"noopener noreferrer"},q=n("p",null,[s('You can run the generator by invoking the command palette and searching for "Generate Package". A dialog will appear asking you to name your new project. Name it '),n("code",null,"your-name-word-count"),s(". Pulsar will then create that directory and fill it out with a skeleton project and link it into your "),n("strong",null,[n("em",null,"LNX/MAC")]),s(": "),n("code",null,"~/.pulsar/packages"),s(" - "),n("strong",null,[n("em",null,"WIN")]),s(": "),n("code",null,"%USERPROFILE%\\.pulsar\\packages"),s(" directory so it's loaded when you launch your editor next time.")],-1),x={class:"custom-container note"},_=n("p",{class:"custom-container-title"},"Note",-1),j={href:"https://web.pulsar-edit.dev/packages",target:"_blank",rel:"noopener noreferrer"},C=n("code",null,"your-name-word-count",-1),T=n("em",null,"should",-1),P=t('

    Basic generated Pulsar package

    You can see that Pulsar has created about a dozen files that make up the package. Let's take a look at each of them to get an idea of how a package is structured, then we can modify them to get our word count functionality.

    The basic package layout is as follows:

    my-package/
    +\u251C\u2500 grammars/
    +\u251C\u2500 keymaps/
    +\u251C\u2500 lib/
    +\u251C\u2500 menus/
    +\u251C\u2500 spec/
    +\u251C\u2500 snippets/
    +\u251C\u2500 styles/
    +\u251C\u2500 index.js
    +\u2514\u2500 package.json
    +

    Not every package will have (or need) all of these directories and the package generator doesn't create snippets or grammars. Let's see what some of these are so we can start messing with them.

    package.json

    `,6),I={href:"https://en.wikipedia.org/wiki/Npm_(software)",target:"_blank",rel:"noopener noreferrer"},N=n("code",null,"package.json",-1),W={href:"https://docs.npmjs.com/files/package.json",target:"_blank",rel:"noopener noreferrer"},A=n("code",null,"package.json",-1),S=n("code",null,"package.json",-1),V=t(`
    • main: the path to the JavaScript file that's the entry point to your package. If this is missing, Pulsar will default to looking for an index.js or index.coffee.
    • styles: an Array of Strings identifying the order of the style sheets your package needs to load. If not specified, style sheets in the styles directory are added alphabetically.
    • keymaps: an Array of Strings identifying the order of the key mappings your package needs to load. If not specified, mappings in the keymaps directory are added alphabetically.
    • menus: an Array of Strings identifying the order of the menu mappings your package needs to load. If not specified, mappings in the menus directory are added alphabetically.
    • snippets: an Array of Strings identifying the order of the snippets your package needs to load. If not specified, snippets in the snippets directory are added alphabetically.
    • activationCommands: an Object identifying commands that trigger your package's activation. The keys are CSS selectors, the values are Arrays of Strings identifying the command. The loading of your package is delayed until one of these events is triggered within the associated scope defined by the CSS selector. If not specified, the activate() method of your main export will be called when your package is loaded.
    • activationHooks: an Array of Strings identifying hooks that trigger your package's activation. The loading of your package is delayed until one of these hooks are triggered. Currently, there are three activation hooks:
      • core:loaded-shell-environment for when Pulsar has finished loading the shell environment variables
      • scope.name:root-scope-used for when a file is opened from the specified language (e.g. source.ruby:root-scope-used)
      • language-package-name:grammar-used for when a specific language package is used (e.g., my-special-language-javascript:grammar-used)
    • workspaceOpeners: An Array of Strings identifying URIs that trigger your package's activation. For example, say your package registers a custom opener for atom://my-custom-panel. By including that string in workspaceOpeners, your package will defer its activation until that URI is opened.

    The package.json in the package we've just generated looks like this currently:

    {
    +	"name": "your-name-word-count",
    +	"main": "./lib/your-name-word-count",
    +	"version": "0.0.0",
    +	"description": "A short description of your package",
    +	"activationCommands": {
    +		"atom-workspace": "your-name-word-count:toggle"
    +	},
    +	"repository": "https://github.com/pulsar-edit/your-name-word-count",
    +	"license": "MIT",
    +	"engines": {
    +		"atom": ">=1.0.0 <2.0.0"
    +	},
    +	"dependencies": {}
    +}
    +

    If you wanted to use activationHooks, you might have:

    {
    +	"name": "your-name-word-count",
    +	"main": "./lib/your-name-word-count",
    +	"version": "0.0.0",
    +	"description": "A short description of your package",
    +	"activationHooks": [
    +		"language-javascript:grammar-used",
    +		"language-coffee-script:grammar-used"
    +	],
    +	"repository": "https://github.com/pulsar-edit/your-name-word-count",
    +	"license": "MIT",
    +	"engines": {
    +		"atom": ">=1.0.0 <2.0.0"
    +	},
    +	"dependencies": {}
    +}
    +

    One of the first things you should do is ensure that this information is filled out. The name, description, repository URL the project will be at, and the license can all be filled out immediately. The other information we'll get into more detail on as we go.

    WARNING

    Do not forget to update the repository URL. The one generated for you is invalid by design and will prevent you from publishing your package until updated.

    Source Code

    If you want to extend Pulsar's behavior, your package should contain a single top-level module, which you export from whichever file is indicated by the main key in your package.json file. In the package we just generated, the main package file is lib/your-name-word-count.js. The remainder of your code should be placed in the lib directory, and required from your top-level file. If the main key is not in your package.json file, it will look for index.js or index.coffee as the main entry point.

    Your package's top-level module is a singleton object that manages the lifecycle of your extensions to Pulsar. Even if your package creates ten different views and appends them to different parts of the DOM, it's all managed from your top-level object.

    Your package's top-level module can implement the following basic methods:

    • activate(state): This optional method is called when your package is activated. It is passed the state data from the last time the window was serialized if your module implements the serialize() method. Use this to do initialization work when your package is started (like setting up DOM elements or binding events). If this method returns a promise the package will be considered loading until the promise resolves (or rejects).
    • initialize(state): This optional method is similar to activate() but is called earlier. Whereas activation occurs after the workspace has been deserialized (and can therefore happen after your package's deserializers have been called), initialize() is guaranteed to be called before everything. Use activate() if you want to be sure that the workspace is ready; use initialize() if you need to do some setup prior to your deserializers or view providers being invoked.
    • serialize(): This optional method is called when the window is shutting down, allowing you to return JSON to represent the state of your component. When the window is later restored, the data you returned is passed to your module's activate method so you can restore your view to where the user left off.
    • deactivate(): This optional method is called when the window is shutting down and when the package is disabled. If your package is watching any files or holding external resources in any other way, release them here. You should also dispose of all subscriptions you're holding on to.

    Style Sheets

    `,13),D=n("code",null,"styles",-1),E={href:"http://lesscss.org",target:"_blank",rel:"noopener noreferrer"},L=t("

    Ideally, you won't need much in the way of styling. Pulsar provides a standard set of components which define both the colors and UI elements for any package that fits into Pulsar seamlessly. You can view all of Pulsar's UI components by opening the styleguide: open the command palette LNX/WIN: Ctrl+Shift+P - MAC: Cmd+Shift+P and search for styleguide, or type LNX/WIN: Ctrl+Shift+G - MAC: Cmd+Ctrl+Shift+G

    ",1),z=n("em",null,"do",-1),Y=n("em",null,"must",-1),O={href:"https://github.com/pulsar-edit/atom-dark-ui/blob/master/styles/ui-variables.less",target:"_blank",rel:"noopener noreferrer"},M=t(`

    An optional styleSheets array in your package.json can list the style sheets by name to specify a loading order; otherwise, style sheets are loaded alphabetically.

    Keymaps

    You can provide key bindings for commonly used actions for your extension, especially if you're also adding a new command. In our new package, we have a keymap filled in for us already in the keymaps/your-name-word-count.json file:

    {
    +  "atom-workspace": {
    +    "ctrl-alt-o": "your-name-word-count:toggle"
    +  }
    +}
    +

    This means that if you press Alt+Ctrl+O, our package will run the your-name-word-count:toggle command. We'll look at that code next, but if you want to change the default key mapping, you can do that in this file.

    Keymaps are placed in the keymaps subdirectory. By default, all keymaps are loaded in alphabetical order. An optional keymaps array in your package.json can specify which keymaps to load and in what order.

    Keybindings are executed by determining which element the keypress occurred on. In the example above, the your-name-word-count:toggle command is executed when pressing Alt+Ctrl+O on the atom-workspace element. Because the atom-workspace element is the parent of the entire Pulsar UI, this means the key combination will work anywhere in the application.

    We'll cover more advanced keybinding stuff a bit later in Keymaps in Depth.

    Menus are placed in the menus subdirectory. This defines menu elements like what pops up when you right click a context-menu or would go in the application menu to trigger functionality in your package.

    By default, all menus are loaded in alphabetical order. An optional menus array in your package.json can specify which menus to load and in what order.

    Application Menu

    It's recommended that you create an application menu item under the Packages menu for common actions with your package that aren't tied to a specific element. If we look in the menus/your-name-word-count.json file that was generated for us, we'll see a section that looks like this:

    
    +"menu": [
    +  {
    +    "label": "Packages",
    +    "submenu": [
    +      {
    +        "label": "Word Count",
    +        "submenu": [
    +          {
    +            "label": "Toggle",
    +            "command": "your-name-word-count:toggle"
    +          }
    +        ]
    +      }
    +    ]
    +  }
    +]
    +
    +

    This section puts a "Toggle" menu item under a menu group named "Your Name Word Count" in the "Packages" menu.

    Application Menu Item

    When you select that menu item, it will run the your-name-word-count:toggle command, which we'll look at in a bit.

    The menu templates you specify are merged with all other templates provided by other packages in the order which they were loaded.

    Context Menu

    It's recommended to specify a context menu item for commands that are linked to specific parts of the interface. In our menus/your-name-word-count.json file, we can see an auto-generated section that looks like this:

    "context-menu": {
    +    "atom-text-editor": [
    +      {
    +        "label": "Toggle your-name-word-count",
    +        "command": "your-name-word-count:toggle"
    +      }
    +    ]
    +  }
    +

    This adds a "Toggle Word Count" menu option to the menu that pops up when you right-click in an Pulsar text editor pane.

    Context Menu Entry

    When you click that it will again run the your-name-word-count:toggle method in your code.

    Context menus are created by determining which element was selected and then adding all of the menu items whose selectors match that element (in the order which they were loaded). The process is then repeated for the elements until reaching the top of the DOM tree.

    You can also add separators and submenus to your context menus. To add a submenu, provide a submenu key instead of a command. To add a separator, add an item with a single type: 'separator' key/value pair. For instance, you could do something like this:

    {
    +  "context-menu": {
    +    "atom-workspace": [
    +      {
    +        "label": "Text",
    +        "submenu": [
    +          {
    +            "label": "Inspect Element",
    +            "command": "core:inspect"
    +          },
    +          {
    +            "type": "separator"
    +          },
    +          {
    +            "label": "Selector All",
    +            "command": "core:select-all"
    +          },
    +          {
    +            "type": "separator"
    +          },
    +          {
    +            "label": "Deleted Selected Text",
    +            "command": "core:delete"
    +          }
    +        ]
    +      }
    +    ]
    +  }
    +}
    +

    Developing Our Package

    Currently with the generated package we have, if we run that your-name-word-count:toggle command through the menu or the command palette, we'll get a dialog that says "The YourNameWordCount package is Alive! It's ALIVE!".

    Wordcount Package is Alive Dialog

    Understanding the Generated Code

    Let's take a look at the code in our lib directory and see what is happening.

    There are two files in our lib directory. One is the main file (lib/your-name-word-count.js), which is pointed to in the package.json file as the main file to execute for this package. This file handles the logic of the whole package.

    The second file is a View class, lib/your-name-word-count-view.js, which handles the UI elements of the package. Let's look at this file first, since it's pretty simple.

    export default class YourNameWordCountView {
    +	constructor(serializedState) {
    +		// Create root element
    +		this.element = document.createElement("div");
    +		this.element.classList.add("your-name-word-count");
    +
    +		// Create message element
    +		const message = document.createElement("div");
    +		message.textContent = "The YourNameWordCount package is Alive! It's ALIVE!";
    +		message.classList.add("message");
    +		this.element.appendChild(message);
    +	}
    +
    +	// Returns an object that can be retrieved when package is activated
    +	serialize() {}
    +
    +	// Tear down any state and detach
    +	destroy() {
    +		this.element.remove();
    +	}
    +
    +	getElement() {
    +		return this.element;
    +	}
    +}
    +

    Basically the only thing happening here is that when the View class is created, it creates a simple div element and adds the your-name-word-count class to it (so we can find or style it later) and then adds the "Your Name Word Count package is Alive!" text to it. There is also a getElement method which returns that div. The serialize and destroy methods don't do anything and we won't have to worry about that until another example.

    Notice that we're simply using the basic browser DOM methods: createElement() and appendChild().

    The second file we have is the main entry point to the package. Again, because it's referenced in the package.json file. Let's take a look at that file.

    import YourNameWordCountView from "./your-name-word-count-view";
    +import { CompositeDisposable } from "atom";
    +
    +export default {
    +	yourNameWordCountView: null,
    +	modalPanel: null,
    +	subscriptions: null,
    +
    +	activate(state) {
    +		this.yourNameWordCountView = new YourNameWordCountView(
    +			state.yourNameWordCountViewState
    +		);
    +		this.modalPanel = atom.workspace.addModalPanel({
    +			item: this.yourNameWordCountView.getElement(),
    +			visible: false,
    +		});
    +
    +		// Events subscribed to in Pulsar's system can be easily cleaned up with a CompositeDisposable
    +		this.subscriptions = new CompositeDisposable();
    +
    +		// Register command that toggles this view
    +		this.subscriptions.add(
    +			atom.commands.add("atom-workspace", {
    +				"your-name-word-count:toggle": () => this.toggle(),
    +			})
    +		);
    +	},
    +
    +	deactivate() {
    +		this.modalPanel.destroy();
    +		this.subscriptions.dispose();
    +		this.yourNameWordCountView.destroy();
    +	},
    +
    +	serialize() {
    +		return {
    +			yourNameWordCountViewState: this.yourNameWordCountView.serialize(),
    +		};
    +	},
    +
    +	toggle() {
    +		console.log("YourNameWordCount was toggled!");
    +		return this.modalPanel.isVisible()
    +			? this.modalPanel.hide()
    +			: this.modalPanel.show();
    +	},
    +};
    +

    There is a bit more going on here. First of all we can see that we are defining four methods. The only required one is activate. The deactivate and serialize methods are expected by Pulsar but optional. The toggle method is one Pulsar is not looking for, so we'll have to invoke it somewhere for it to be called, which you may recall we do both in the activationCommands section of the package.json file and in the action we have in the menu file.

    The deactivate method simply destroys the various class instances we've created and the serialize method simply passes on the serialization to the View class. Nothing too exciting here.

    The activate command does a number of things. For one, it is not called automatically when Pulsar starts up, it is first called when one of the activationCommands as defined in the package.json file are called. In this case, activate is only called the first time the toggle command is called. If nobody ever invokes the menu item or hotkey, this code is never called.

    This method does two things. The first is that it creates an instance of the View class we have and adds the element that it creates to a hidden modal panel in the Pulsar workspace.

    this.yourNameWordCountView = new YourNameWordCountView(
    +	state.yourNameWordCountViewState
    +);
    +this.modalPanel = atom.workspace.addModalPanel({
    +	item: this.yourNameWordCountView.getElement(),
    +	visible: false,
    +});
    +

    We'll ignore the state stuff for now, since it's not important for this simple package. The rest should be fairly straightforward.

    The next thing this method does is create an instance of the CompositeDisposable class so it can register all the commands that can be called from the package so other packages could subscribe to these events.

    // Events subscribed to in Pulsar's system can be easily cleaned up with a CompositeDisposable
    +this.subscriptions = new CompositeDisposable();
    +
    +// Register command that toggles this view
    +this.subscriptions.add(
    +	atom.commands.add("atom-workspace", {
    +		"your-name-word-count:toggle": () => this.toggle(),
    +	})
    +);
    +

    Next we have the toggle method. This method simply toggles the visibility of the modal panel that we created in the activate method.

    toggle() {
    +  console.log('YourNameWordCount was toggled!');
    +  return (
    +    this.modalPanel.isVisible() ?
    +    this.modalPanel.hide() :
    +    this.modalPanel.show()
    +  );
    +}
    +

    This should be fairly simple to understand. We're looking to see if the modal element is visible and hiding or showing it depending on its current state.

    The Flow

    So, let's review the actual flow in this package.

    1. Pulsar starts up
    2. Pulsar starts loading packages
    3. Pulsar reads your package.json
    4. Pulsar loads keymaps, menus, styles and the main module
    5. Pulsar finishes loading packages
    6. At some point, the user executes your package command your-name-word-count:toggle
    7. Pulsar executes the activate method in your main module which sets up the UI by creating the hidden modal view
    8. Pulsar executes the package command your-name-word-count:toggle which reveals the hidden modal view
    9. At some point, the user executes the your-name-word-count:toggle command again
    10. Pulsar executes the command which hides the modal view
    11. Eventually, Pulsar is shut down which can trigger any serializations that your package has defined

    Tip

    Keep in mind that the flow will be slightly different if you choose not to use activationCommands in your package.

    Counting the Words

    So now that we understand what is happening, let's modify the code so that our little modal box shows us the current word count instead of static text.

    We'll do this in a very simple way. When the dialog is toggled, we'll count the words right before displaying the modal. So let's do this in the toggle command. If we add some code to count the words and ask the view to update itself, we'll have something like this:

    toggle() {
    +  if (this.modalPanel.isVisible()) {
    +    this.modalPanel.hide();
    +  } else {
    +    const editor = atom.workspace.getActiveTextEditor();
    +    const words = editor.getText().split(/\\s+/).length;
    +    this.yourNameWordCountView.setCount(words);
    +    this.modalPanel.show();
    +  }
    +}
    +
    `,58),U={href:"https://atom.io/docs/api/latest/Workspace#instance-getActiveTextEditor",target:"_blank",rel:"noopener noreferrer"},R=n("code",null,"atom.workspace.getActiveTextEditor()",-1),B={href:"https://atom.io/docs/api/latest/TextEditor#instance-getText",target:"_blank",rel:"noopener noreferrer"},F=n("code",null,"getText()",-1),G=t(`

    Finally, we tell our view to update the word count it displays by calling the setCount() method on our view and then showing the modal again. Since that method doesn't yet exist, let's create it now.

    We can add this code to the end of our your-name-word-count-view.js file:

    setCount(count) {
    +  const displayText = \`There are \${count} words.\`;
    +  this.element.children[0].textContent = displayText;
    +}
    +

    Pretty simple! We take the count number that was passed in and place it into a string that we then stick into the element that our view is controlling.

    Note

    To see your changes, you'll need to reload the code. You can do this by reloading the window (The window:reload command in the Command Palette). A common practice is to have two Pulsar windows, one for developing your package, and one for testing and reloading.

    Word Count Working

    Basic Debugging

    You'll notice a few console.log statements in the code. One of the cool things about Pulsar being built on Chromium is that you can use some of the same debugging tools available to you that you have when doing web development.

    To open up the Developer Console, press LNX/WIN: Ctrl+Shift+I - MAC: Alt+Cmd+I or choose the menu option View > Developer > Toggle Developer Tools.

    Developer Tools Debugging

    From here you can inspect objects, run code and view console output just as though you were debugging a web site.

    Testing

    Your package should have tests, and if they're placed in the spec directory, they can be run by Pulsar.

    ',13),K={href:"https://jasmine.github.io/archives/1.3/introduction",target:"_blank",rel:"noopener noreferrer"},X=t('

    Running Tests

    Once you've got your test suite written, you can run it by pressing LNX/WIN: Alt+Ctrl+P - MAC: Alt+Cmd+Ctrl+P or via the View > Developer > Run Package Specs menu. Our generated package comes with an example test suite, so you can run this right now to see what happens.

    Spec Suite Results

    You can also use the pulsar --test spec command to run them from the command line. It prints the test output and results to the console and returns the proper status code depending on whether the tests passed or failed.

    Summary

    We've now generated, customized and tested our first package for Pulsar. Congratulations! Now let's go ahead and publish it so it's available to the world.

    ',6);function H(J,$){const a=v("ExternalLinkIcon");return m(),h("div",null,[y,b,w,n("p",null,[s("The simplest way to start a package is to use the built-in package generator that ships with Pulsar. As you might expect by now, this generator is itself a separate package implemented in "),n("a",f,[s("package-generator"),e(a)]),s(".")]),q,n("div",x,[_,n("p",null,[s("You may encounter a situation where your package is not loaded. That is because a new package using the same name as an actual package hosted on "),n("a",j,[s("pulsar-edit.dev"),e(a)]),s(' (e.g. "wordcount" and "word-count") is not being loaded as you expected. If you follow our suggestion above of using the '),C,s(" package name, you "),T,s(" be safe \u{1F600}")])]),P,n("p",null,[s("Similar to "),n("a",I,[s("Node modules"),e(a)]),s(", Pulsar packages contain a "),N,s(' file in their top-level directory. This file contains metadata about the package, such as the path to its "main" module, library dependencies, and manifests specifying the order in which its resources should be loaded.')]),n("p",null,[s("In addition to some of the regular "),n("a",W,[s("Node "),A,s(" keys"),e(a)]),s(" available, Pulsar "),S,s(" files have their own additions.")]),V,n("p",null,[s("Style sheets for your package should be placed in the "),D,s(" directory. Any style sheets in this directory will be loaded and attached to the DOM when your package is activated. Style sheets can be written as CSS or "),n("a",E,[s("Less"),e(a)]),s(", but Less is recommended.")]),L,n("p",null,[s("If you "),z,s(" need special styling, try to keep only structural styles in the package style sheets. If you "),Y,s(" specify colors and sizing, these should be taken from the active theme's "),n("a",O,[s("ui-variables.less"),e(a)]),s(".")]),M,n("p",null,[s("Let's look at the 3 lines we've added. First we get an instance of the current editor object (where our text to count is) by calling "),n("a",U,[R,e(a)]),s(". "),o("TODO: Update when API is documented")]),n("p",null,[s("Next we get the number of words by calling "),n("a",B,[F,e(a)]),o("TODO: Update when API is documented"),s(" on our new editor object, then splitting that text on whitespace with a regular expression and then getting the length of that array.")]),G,n("p",null,[s("Under the hood, "),n("a",K,[s("Jasmine v1.3"),e(a)]),s(" executes your tests, so you can assume that any DSL available there is also available to your package.")]),X])}const sn=k(g,[["render",H],["__file","package-word-count.html.vue"]]);export{sn as default}; diff --git a/assets/package.055df86c.png b/assets/package.055df86c.png new file mode 100644 index 0000000000..ab89679191 Binary files /dev/null and b/assets/package.055df86c.png differ diff --git a/assets/package.4e7449ae.js b/assets/package.4e7449ae.js new file mode 100644 index 0000000000..5c666f5414 --- /dev/null +++ b/assets/package.4e7449ae.js @@ -0,0 +1 @@ +const s="/assets/package.d325e49c.png";export{s as _}; diff --git a/assets/package.d325e49c.png b/assets/package.d325e49c.png new file mode 100644 index 0000000000..4580b69b8f Binary files /dev/null and b/assets/package.d325e49c.png differ diff --git a/assets/packages-install.5546715f.png b/assets/packages-install.5546715f.png new file mode 100644 index 0000000000..ba036413c0 Binary files /dev/null and b/assets/packages-install.5546715f.png differ diff --git a/assets/panes.a7cec271.js b/assets/panes.a7cec271.js new file mode 100644 index 0000000000..bd065cb688 --- /dev/null +++ b/assets/panes.a7cec271.js @@ -0,0 +1 @@ +const s="/assets/panes.bea7c577.png";export{s as _}; diff --git a/assets/panes.bea7c577.png b/assets/panes.bea7c577.png new file mode 100644 index 0000000000..11a99696f1 Binary files /dev/null and b/assets/panes.bea7c577.png differ diff --git a/assets/panes.html.4b274c2a.js b/assets/panes.html.4b274c2a.js new file mode 100644 index 0000000000..e87ce0f794 --- /dev/null +++ b/assets/panes.html.4b274c2a.js @@ -0,0 +1 @@ +import{_ as o}from"./panes.a7cec271.js";import{_ as n,o as s,c as i,a as e,b as t,d as l,f as r,r as c}from"./app.0e1565ce.js";const p={},d=r('

    Panes

    You can split any editor pane horizontally or vertically by using Cmd+KCtrl+K Up/Down/Left/Right where the direction key is the direction to split the pane. Once you have a split pane, you can switch between them with Cmd+KCtrl+K Cmd+Up/Down/Left/RightCtrl+Up/Down/Left/Right where the direction is the direction the focus should move to.

    Multiple panes

    Each pane has its own "pane items", which are represented by tabs. You can move the files from pane to pane by dragging them with the mouse and dropping them in the pane you want that file to be in.

    ',4),h={class:"custom-container tip"},m=e("p",{class:"custom-container-title"},"Tip",-1),u={href:"https://github.com/atom/tabs",target:"_blank",rel:"noopener noreferrer"},f=e("p",null,[t("To close a pane, you can close all pane items with "),e("kbd",{class:"platform-mac"},"Cmd+W"),e("kbd",{class:"platform-windows platform-linux"},"Ctrl+W"),t('. You can configure whether panes auto-close when empty in the Settings View with the "Remove Empty Panes" setting under Core Settings.')],-1);function b(w,_){const a=c("ExternalLinkIcon");return s(),i("div",null,[d,e("div",h,[m,e("p",null,[t("If you don't like using tabs, you don't have to. You can disable the "),e("a",u,[t("tabs package"),l(a)]),t(" and each pane will still support multiple pane items. You just won't have tabs to use to click between them.")])]),f])}const y=n(p,[["render",b],["__file","panes.html.vue"]]);export{y as default}; diff --git a/assets/panes.html.690697a2.js b/assets/panes.html.690697a2.js new file mode 100644 index 0000000000..9faacf7945 --- /dev/null +++ b/assets/panes.html.690697a2.js @@ -0,0 +1 @@ +import{_ as i}from"./panes.a7cec271.js";import{_ as r,o as u,c as d,d as l,w as n,a as e,b as t,r as h}from"./app.0e1565ce.js";const m={},w=e("h2",{id:"panes",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#panes","aria-hidden":"true"},"#"),t(" Panes")],-1),_=e("p",null,[t("You can split any editor pane horizontally or vertically by using "),e("kbd",null,"Ctrl+K"),t(),e("kbd",null,"Up/Down/Left/Right"),t(" where the direction key is the direction to split the pane. Once you have a split pane, you can switch between them with "),e("kbd",null,"Ctrl+K"),t(),e("kbd",null,"Ctrl+Up/Down/Left/Right"),t(" where the direction is the direction the focus should move to.")],-1),b=e("p",null,[e("img",{src:i,alt:"Multiple panes",title:"Multiple panes"})],-1),g=e("p",null,'Each pane has its own "pane items", which are represented by tabs. You can move the files from pane to pane by dragging them with the mouse and dropping them in the pane you want that file to be in.',-1),y=e("p",null,[t("To close a pane, you can close all pane items with "),e("kbd",null,"Ctrl+W"),t('. You can configure whether panes auto-close when empty in the Settings View with the "Remove Empty Panes" setting under Core Settings.')],-1),f=e("p",null,[t("You can split any editor pane horizontally or vertically by using "),e("kbd",null,"Cmd+K"),t(),e("kbd",null,"Up/Down/Left/Right"),t(" where the direction key is the direction to split the pane. Once you have a split pane, you can switch between them with "),e("kbd",null,"Cmd+K"),t(),e("kbd",null,"Cmd+Up/Down/Left/Right"),t(" where the direction is the direction the focus should move to.")],-1),v=e("p",null,[e("img",{src:i,alt:"Multiple panes",title:"Multiple panes"})],-1),k=e("p",null,'Each pane has its own "pane items", which are represented by tabs. You can move the files from pane to pane by dragging them with the mouse and dropping them in the pane you want that file to be in.',-1),C=e("p",null,[t("To close a pane, you can close all pane items with "),e("kbd",null,"Cmd+W"),t('. You can configure whether panes auto-close when empty in the Settings View with the "Remove Empty Panes" setting under Core Settings.')],-1),Y=e("p",null,[t("You can split any editor pane horizontally or vertically by using "),e("kbd",null,"Ctrl+K"),t(),e("kbd",null,"Up/Down/Left/Right"),t(" where the direction key is the direction to split the pane. Once you have a split pane, you can switch between them with "),e("kbd",null,"Ctrl+K"),t(),e("kbd",null,"Ctrl+Up/Down/Left/Right"),t(" where the direction is the direction the focus should move to.")],-1),x=e("p",null,[e("img",{src:i,alt:"Multiple panes",title:"Multiple panes"})],-1),E=e("p",null,'Each pane has its own "pane items", which are represented by tabs. You can move the files from pane to pane by dragging them with the mouse and dropping them in the pane you want that file to be in.',-1),R=e("p",null,[t("To close a pane, you can close all pane items with "),e("kbd",null,"Ctrl+W"),t('. You can configure whether panes auto-close when empty in the Settings View with the "Remove Empty Panes" setting under Core Settings.')],-1),L={class:"custom-container tip"},S=e("p",{class:"custom-container-title"},"Tip",-1),T={href:"https://github.com/pulsar-edit/tabs",target:"_blank",rel:"noopener noreferrer"};function D(K,M){const c=h("Tabs"),p=h("ExternalLinkIcon");return u(),d("div",null,[w,l(c,{id:"3",data:[{title:"linux"},{title:"macOS"},{title:"Windows"}],"tab-id":"using-pulsar"},{tab0:n(({title:o,value:a,isActive:s})=>[_,b,g,y]),tab1:n(({title:o,value:a,isActive:s})=>[f,v,k,C]),tab2:n(({title:o,value:a,isActive:s})=>[Y,x,E,R]),_:1}),e("div",L,[S,e("p",null,[t("If you don't like using tabs, you don't have to. You can disable the "),e("a",T,[t("tabs package"),l(p)]),t(" and each pane will still support multiple pane items. You just won't have tabs to use to click between them.")])])])}const O=r(m,[["render",D],["__file","panes.html.vue"]]);export{O as default}; diff --git a/assets/panes.html.7c7f5559.js b/assets/panes.html.7c7f5559.js new file mode 100644 index 0000000000..3e4c5212d4 --- /dev/null +++ b/assets/panes.html.7c7f5559.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-76058cbd","path":"/docs/launch-manual/sections/using-pulsar/sections/panes.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Panes","slug":"panes","link":"#panes","children":[]}],"git":{"updatedTime":1669229414000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":2}]},"readingTime":{"minutes":1.71,"words":512},"filePathRelative":"docs/launch-manual/sections/using-pulsar/sections/panes.md"}');export{e as data}; diff --git a/assets/panes.html.8ce248a9.js b/assets/panes.html.8ce248a9.js new file mode 100644 index 0000000000..2d6d020dab --- /dev/null +++ b/assets/panes.html.8ce248a9.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-0d49e5a8","path":"/docs/atom-archive/using-atom/sections/panes.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"Panes","slug":"panes","link":"#panes","children":[]}],"git":{"updatedTime":1664634443000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1},{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":0.84,"words":252},"filePathRelative":"docs/atom-archive/using-atom/sections/panes.md"}');export{e as data}; diff --git a/assets/pending-pane-items.html.0021df08.js b/assets/pending-pane-items.html.0021df08.js new file mode 100644 index 0000000000..e426808e04 --- /dev/null +++ b/assets/pending-pane-items.html.0021df08.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-52fd2aae","path":"/docs/atom-archive/using-atom/sections/pending-pane-items.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"Pending Pane Items","slug":"pending-pane-items","link":"#pending-pane-items","children":[]}],"git":{"updatedTime":1664634443000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1},{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":0.76,"words":227},"filePathRelative":"docs/atom-archive/using-atom/sections/pending-pane-items.md"}');export{e as data}; diff --git a/assets/pending-pane-items.html.4cd88c45.js b/assets/pending-pane-items.html.4cd88c45.js new file mode 100644 index 0000000000..fe709bf078 --- /dev/null +++ b/assets/pending-pane-items.html.4cd88c45.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-67da6db9","path":"/docs/launch-manual/sections/using-pulsar/sections/pending-pane-items.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Pending Pane Items","slug":"pending-pane-items","link":"#pending-pane-items","children":[{"level":3,"title":"Disabling Pending Pane Items","slug":"disabling-pending-pane-items","link":"#disabling-pending-pane-items","children":[]}]}],"git":{"updatedTime":1669229414000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":2}]},"readingTime":{"minutes":0.76,"words":227},"filePathRelative":"docs/launch-manual/sections/using-pulsar/sections/pending-pane-items.md"}');export{e as data}; diff --git a/assets/pending-pane-items.html.71de0d56.js b/assets/pending-pane-items.html.71de0d56.js new file mode 100644 index 0000000000..c45f0516e3 --- /dev/null +++ b/assets/pending-pane-items.html.71de0d56.js @@ -0,0 +1 @@ +import{_ as e}from"./allow-pending-pane-items.0fe3dd4a.js";import{_ as i,o as n,c as t,f as l}from"./app.0e1565ce.js";const o={},a=l('

    Pending Pane Items

    "Pending Pane Items" were formerly referred to as "Preview Tabs"

    When you open a new file by single-clicking in the Tree View, it will open in a new tab with an italic title. This indicates that the file is "pending". When a file is pending, it will be replaced by the next pending file that is opened. This allows you to click through a bunch of files to find something without having to go back and close them all.

    You can confirm a pending file by doing any of the following:

    • Double-clicking the tab of the file
    • Double-clicking the file in the tree view
    • Editing the contents of the file
    • Saving the file

    You can also open a file already confirmed by double-clicking it in the tree view instead of single-clicking it.

    Disabling Pending Pane Items

    Allow Pending Pane Items setting

    If you would prefer to not have files open in pending form, you can disable this behavior by unchecking "Allow Pending Pane Items" in the Core Settings section of the Settings View. With pending pane items disabled, single-clicking a file in the Tree View will select the file but not open it. You will have to double-click the file to open it.

    ',9),s=[a];function c(d,h){return n(),t("div",null,s)}const g=i(o,[["render",c],["__file","pending-pane-items.html.vue"]]);export{g as default}; diff --git a/assets/pending-pane-items.html.b95fbf84.js b/assets/pending-pane-items.html.b95fbf84.js new file mode 100644 index 0000000000..af0ff9d102 --- /dev/null +++ b/assets/pending-pane-items.html.b95fbf84.js @@ -0,0 +1 @@ +import{_ as e}from"./allow-pending-pane-items.0fe3dd4a.js";import{_ as i,o as n,c as t,f as l}from"./app.0e1565ce.js";const o={},a=l('

    Pending Pane Items

    "Pending Pane Items" were formerly referred to as "Preview Tabs"

    When you open a new file by single-clicking in the Tree View, it will open in a new tab with an italic title. This indicates that the file is "pending". When a file is pending, it will be replaced by the next pending file that is opened. This allows you to click through a bunch of files to find something without having to go back and close them all.

    You can confirm a pending file by doing any of the following:

    • Double-clicking the tab of the file
    • Double-clicking the file in the tree view
    • Editing the contents of the file
    • Saving the file

    You can also open a file already confirmed by double-clicking it in the tree view instead of single-clicking it.

    Disabling Pending Pane Items

    Allow Pending Pane Items setting

    If you would prefer to not have files open in pending form, you can disable this behavior by unchecking "Allow Pending Pane Items" in the Core Settings section of the Settings View. With pending pane items disabled, single-clicking a file in the Tree View will select the file but not open it. You will have to double-click the file to open it.

    ',9),s=[a];function c(d,h){return n(),t("div",null,s)}const g=i(o,[["render",c],["__file","pending-pane-items.html.vue"]]);export{g as default}; diff --git a/assets/photoswipe.esm.09e03fed.js b/assets/photoswipe.esm.09e03fed.js new file mode 100644 index 0000000000..55ad7d5215 --- /dev/null +++ b/assets/photoswipe.esm.09e03fed.js @@ -0,0 +1,4 @@ +/*! + * PhotoSwipe 5.3.3 - https://photoswipe.com + * (c) 2022 Dmytro Semenov + */function m(o,t,e){const i=document.createElement(t||"div");return o&&(i.className=o),e&&e.appendChild(i),i}function u(o,t){return o.x=t.x,o.y=t.y,t.id!==void 0&&(o.id=t.id),o}function M(o){o.x=Math.round(o.x),o.y=Math.round(o.y)}function C(o,t){const e=Math.abs(o.x-t.x),i=Math.abs(o.y-t.y);return Math.sqrt(e*e+i*i)}function I(o,t){return o.x===t.x&&o.y===t.y}function b(o,t,e){return Math.min(Math.max(o,t),e)}function L(o,t,e){let i="translate3d("+o+"px,"+(t||0)+"px,0)";return e!==void 0&&(i+=" scale3d("+e+","+e+",1)"),i}function y(o,t,e,i){o.style.transform=L(t,e,i)}const U="cubic-bezier(.4,0,.22,1)";function R(o,t,e,i){o.style.transition=t?t+" "+e+"ms "+(i||U):"none"}function A(o,t,e){o.style.width=typeof t=="number"?t+"px":t,o.style.height=typeof e=="number"?e+"px":e}function q(o){R(o)}function G(o){return"decode"in o?o.decode().catch(()=>{}):o.complete?Promise.resolve(o):new Promise((t,e)=>{o.onload=()=>t(o),o.onerror=e})}const f={IDLE:"idle",LOADING:"loading",LOADED:"loaded",ERROR:"error"};function X(o){if(o.which===2||o.ctrlKey||o.metaKey||o.altKey||o.shiftKey)return!0}function K(o,t,e=document){let i=[];if(o instanceof Element)i=[o];else if(o instanceof NodeList||Array.isArray(o))i=Array.from(o);else{const s=typeof o=="string"?o:t;s&&(i=Array.from(e.querySelectorAll(s)))}return i}function x(){return!!(navigator.vendor&&navigator.vendor.match(/apple/i))}let F=!1;try{window.addEventListener("test",null,Object.defineProperty({},"passive",{get:()=>{F=!0}}))}catch{}class Y{constructor(){this._pool=[]}add(t,e,i,s){this._toggleListener(t,e,i,s)}remove(t,e,i,s){this._toggleListener(t,e,i,s,!0)}removeAll(){this._pool.forEach(t=>{this._toggleListener(t.target,t.type,t.listener,t.passive,!0,!0)}),this._pool=[]}_toggleListener(t,e,i,s,n,r){if(!t)return;const a=n?"removeEventListener":"addEventListener";e.split(" ").forEach(h=>{if(h){r||(n?this._pool=this._pool.filter(p=>p.type!==h||p.listener!==i||p.target!==t):this._pool.push({target:t,type:h,listener:i,passive:s}));const c=F?{passive:s||!1}:!1;t[a](h,i,c)}})}}function B(o,t){if(o.getViewportSizeFn){const e=o.getViewportSizeFn(o,t);if(e)return e}return{x:document.documentElement.clientWidth,y:window.innerHeight}}function S(o,t,e,i,s){let n;if(t.paddingFn)n=t.paddingFn(e,i,s)[o];else if(t.padding)n=t.padding[o];else{const r="padding"+o[0].toUpperCase()+o.slice(1);t[r]&&(n=t[r])}return n||0}function N(o,t,e,i){return{x:t.x-S("left",o,t,e,i)-S("right",o,t,e,i),y:t.y-S("top",o,t,e,i)-S("bottom",o,t,e,i)}}class j{constructor(t){this.slide=t,this.currZoomLevel=1,this.center={},this.max={},this.min={},this.reset()}update(t){this.currZoomLevel=t,this.slide.width?(this._updateAxis("x"),this._updateAxis("y"),this.slide.pswp.dispatch("calcBounds",{slide:this.slide})):this.reset()}_updateAxis(t){const{pswp:e}=this.slide,i=this.slide[t==="x"?"width":"height"]*this.currZoomLevel,n=S(t==="x"?"left":"top",e.options,e.viewportSize,this.slide.data,this.slide.index),r=this.slide.panAreaSize[t];this.center[t]=Math.round((r-i)/2)+n,this.max[t]=i>r?Math.round(r-i)+n:this.center[t],this.min[t]=i>r?n:this.center[t]}reset(){this.center.x=0,this.center.y=0,this.max.x=0,this.max.y=0,this.min.x=0,this.min.y=0}correctPan(t,e){return b(e,this.max[t],this.min[t])}}const z=4e3;class k{constructor(t,e,i,s){this.pswp=s,this.options=t,this.itemData=e,this.index=i}update(t,e,i){this.elementSize={x:t,y:e},this.panAreaSize=i;const s=this.panAreaSize.x/this.elementSize.x,n=this.panAreaSize.y/this.elementSize.y;this.fit=Math.min(1,sn?s:n),this.vFill=Math.min(1,n),this.initial=this._getInitial(),this.secondary=this._getSecondary(),this.max=Math.max(this.initial,this.secondary,this._getMax()),this.min=Math.min(this.fit,this.initial,this.secondary),this.pswp&&this.pswp.dispatch("zoomLevelsUpdate",{zoomLevels:this,slideData:this.itemData})}_parseZoomLevelOption(t){const e=t+"ZoomLevel",i=this.options[e];if(!!i)return typeof i=="function"?i(this):i==="fill"?this.fill:i==="fit"?this.fit:Number(i)}_getSecondary(){let t=this._parseZoomLevelOption("secondary");return t||(t=Math.min(1,this.fit*3),t*this.elementSize.x>z&&(t=z/this.elementSize.x),t)}_getInitial(){return this._parseZoomLevelOption("initial")||this.fit}_getMax(){const t=this._parseZoomLevelOption("max");return t||Math.max(1,this.fit*4)}}class ${constructor(t,e,i){this.data=t,this.index=e,this.pswp=i,this.isActive=e===i.currIndex,this.currentResolution=0,this.panAreaSize={},this.isFirstSlide=this.isActive&&!i.opener.isOpen,this.zoomLevels=new k(i.options,t,e,i),this.pswp.dispatch("gettingData",{slide:this,data:this.data,index:e}),this.pan={x:0,y:0},this.content=this.pswp.contentLoader.getContentBySlide(this),this.container=m("pswp__zoom-wrap"),this.currZoomLevel=1,this.width=this.content.width,this.height=this.content.height,this.bounds=new j(this),this.prevDisplayedWidth=-1,this.prevDisplayedHeight=-1,this.pswp.dispatch("slideInit",{slide:this})}setIsActive(t){t&&!this.isActive?this.activate():!t&&this.isActive&&this.deactivate()}append(t){this.holderElement=t,this.container.style.transformOrigin="0 0",this.data&&(this.calculateSize(),this.load(),this.updateContentSize(),this.appendHeavy(),this.holderElement.appendChild(this.container),this.zoomAndPanToInitial(),this.pswp.dispatch("firstZoomPan",{slide:this}),this.applyCurrentZoomPan(),this.pswp.dispatch("afterSetContent",{slide:this}),this.isActive&&this.activate())}load(){this.content.load(),this.pswp.dispatch("slideLoad",{slide:this})}appendHeavy(){const{pswp:t}=this,e=!0;this.heavyAppended||!t.opener.isOpen||t.mainScroll.isShifted()||!this.isActive&&!e||this.pswp.dispatch("appendHeavy",{slide:this}).defaultPrevented||(this.heavyAppended=!0,this.content.append(),this.pswp.dispatch("appendHeavyContent",{slide:this}))}activate(){this.isActive=!0,this.appendHeavy(),this.content.activate(),this.pswp.dispatch("slideActivate",{slide:this})}deactivate(){this.isActive=!1,this.content.deactivate(),this.currZoomLevel!==this.zoomLevels.initial&&this.calculateSize(),this.currentResolution=0,this.zoomAndPanToInitial(),this.applyCurrentZoomPan(),this.updateContentSize(),this.pswp.dispatch("slideDeactivate",{slide:this})}destroy(){this.content.hasSlide=!1,this.content.remove(),this.container.remove(),this.pswp.dispatch("slideDestroy",{slide:this})}resize(){this.currZoomLevel===this.zoomLevels.initial||!this.isActive?(this.calculateSize(),this.currentResolution=0,this.zoomAndPanToInitial(),this.applyCurrentZoomPan(),this.updateContentSize()):(this.calculateSize(),this.bounds.update(this.currZoomLevel),this.panTo(this.pan.x,this.pan.y))}updateContentSize(t){const e=this.currentResolution||this.zoomLevels.initial;if(!e)return;const i=Math.round(this.width*e)||this.pswp.viewportSize.x,s=Math.round(this.height*e)||this.pswp.viewportSize.y;!this.sizeChanged(i,s)&&!t||this.content.setDisplayedSize(i,s)}sizeChanged(t,e){return t!==this.prevDisplayedWidth||e!==this.prevDisplayedHeight?(this.prevDisplayedWidth=t,this.prevDisplayedHeight=e,!0):!1}getPlaceholderElement(){if(this.content.placeholder)return this.content.placeholder.element}zoomTo(t,e,i,s){const{pswp:n}=this;if(!this.isZoomable()||n.mainScroll.isShifted())return;n.dispatch("beforeZoomTo",{destZoomLevel:t,centerPoint:e,transitionDuration:i}),n.animations.stopAllPan();const r=this.currZoomLevel;s||(t=b(t,this.zoomLevels.min,this.zoomLevels.max)),this.setZoomLevel(t),this.pan.x=this.calculateZoomToPanOffset("x",e,r),this.pan.y=this.calculateZoomToPanOffset("y",e,r),M(this.pan);const a=()=>{this._setResolution(t),this.applyCurrentZoomPan()};i?n.animations.startTransition({isPan:!0,name:"zoomTo",target:this.container,transform:this.getCurrentTransform(),onComplete:a,duration:i,easing:n.options.easing}):a()}toggleZoom(t){this.zoomTo(this.currZoomLevel===this.zoomLevels.initial?this.zoomLevels.secondary:this.zoomLevels.initial,t,this.pswp.options.zoomAnimationDuration)}setZoomLevel(t){this.currZoomLevel=t,this.bounds.update(this.currZoomLevel)}calculateZoomToPanOffset(t,e,i){if(this.bounds.max[t]-this.bounds.min[t]===0)return this.bounds.center[t];e||(e=this.pswp.getViewportCenterPoint());const n=this.currZoomLevel/i;return this.bounds.correctPan(t,(this.pan[t]-e[t])*n+e[t])}panTo(t,e){this.pan.x=this.bounds.correctPan("x",t),this.pan.y=this.bounds.correctPan("y",e),this.applyCurrentZoomPan()}isPannable(){return this.width&&this.currZoomLevel>this.zoomLevels.fit}isZoomable(){return this.width&&this.content.isZoomable()}applyCurrentZoomPan(){this._applyZoomTransform(this.pan.x,this.pan.y,this.currZoomLevel),this===this.pswp.currSlide&&this.pswp.dispatch("zoomPanUpdate",{slide:this})}zoomAndPanToInitial(){this.currZoomLevel=this.zoomLevels.initial,this.bounds.update(this.currZoomLevel),u(this.pan,this.bounds.center),this.pswp.dispatch("initialZoomPan",{slide:this})}_applyZoomTransform(t,e,i){i/=this.currentResolution||this.zoomLevels.initial,y(this.container,t,e,i)}calculateSize(){const{pswp:t}=this;u(this.panAreaSize,N(t.options,t.viewportSize,this.data,this.index)),this.zoomLevels.update(this.width,this.height,this.panAreaSize),t.dispatch("calcSlideSize",{slide:this})}getCurrentTransform(){const t=this.currZoomLevel/(this.currentResolution||this.zoomLevels.initial);return L(this.pan.x,this.pan.y,t)}_setResolution(t){t!==this.currentResolution&&(this.currentResolution=t,this.updateContentSize(),this.pswp.dispatch("resolutionChanged"))}}const Q=.35,J=.6,T=.4,O=.5;function tt(o,t){return o*t/(1-t)}class et{constructor(t){this.gestures=t,this.pswp=t.pswp,this.startPan={}}start(){u(this.startPan,this.pswp.currSlide.pan),this.pswp.animations.stopAll()}change(){const{p1:t,prevP1:e,dragAxis:i,pswp:s}=this.gestures,{currSlide:n}=s;if(i==="y"&&s.options.closeOnVerticalDrag&&n.currZoomLevel<=n.zoomLevels.fit&&!this.gestures.isMultitouch){const r=n.pan.y+(t.y-e.y);if(!s.dispatch("verticalDrag",{panY:r}).defaultPrevented){this._setPanWithFriction("y",r,J);const a=1-Math.abs(this._getVerticalDragRatio(n.pan.y));s.applyBgOpacity(a),n.applyCurrentZoomPan()}}else this._panOrMoveMainScroll("x")||(this._panOrMoveMainScroll("y"),M(n.pan),n.applyCurrentZoomPan())}end(){const{pswp:t,velocity:e}=this.gestures,{mainScroll:i}=t;let s=0;if(t.animations.stopAll(),i.isShifted()){const r=(i.x-i.getCurrSlideX())/t.viewportSize.x;e.x<-O&&r<0||e.x<.1&&r<-.5?(s=1,e.x=Math.min(e.x,0)):(e.x>O&&r>0||e.x>-.1&&r>.5)&&(s=-1,e.x=Math.max(e.x,0)),i.moveIndexBy(s,!0,e.x)}t.currSlide.currZoomLevel>t.currSlide.zoomLevels.max||this.gestures.isMultitouch?this.gestures.zoomLevels.correctZoomPan(!0):(this._finishPanGestureForAxis("x"),this._finishPanGestureForAxis("y"))}_finishPanGestureForAxis(t){const{pswp:e}=this,{currSlide:i}=e,{velocity:s}=this.gestures,{pan:n,bounds:r}=i,a=n[t],l=e.bgOpacity<1&&t==="y",h=.995,c=a+tt(s[t],h);if(l){const g=this._getVerticalDragRatio(a),v=this._getVerticalDragRatio(c);if(g<0&&v<-T||g>0&&v>T){e.close();return}}const p=r.correctPan(t,c);if(a===p)return;const d=p===c?1:.82,_=e.bgOpacity,w=p-a;e.animations.startSpring({name:"panGesture"+t,isPan:!0,start:a,end:p,velocity:s[t],dampingRatio:d,onUpdate:g=>{if(l&&e.bgOpacity<1){const v=1-(p-g)/w;e.applyBgOpacity(b(_+(1-_)*v,0,1))}n[t]=Math.floor(g),i.applyCurrentZoomPan()}})}_panOrMoveMainScroll(t){const{p1:e,pswp:i,dragAxis:s,prevP1:n,isMultitouch:r}=this.gestures,{currSlide:a,mainScroll:l}=i,h=e[t]-n[t],c=l.x+h;if(!h)return;if(t==="x"&&!a.isPannable()&&!r)return l.moveTo(c,!0),!0;const{bounds:p}=a,d=a.pan[t]+h;if(i.options.allowPanToNext&&s==="x"&&t==="x"&&!r){const _=l.getCurrSlideX(),w=l.x-_,g=h>0,v=!g;if(d>p.min[t]&&g){if(p.min[t]<=this.startPan[t])return l.moveTo(c,!0),!0;this._setPanWithFriction(t,d)}else if(d0)return l.moveTo(Math.max(c,_),!0),!0;if(w<0)return l.moveTo(Math.min(c,_),!0),!0}else this._setPanWithFriction(t,d)}else t==="y"?!l.isShifted()&&p.min.y!==p.max.y&&this._setPanWithFriction(t,d):this._setPanWithFriction(t,d)}_getVerticalDragRatio(t){return(t-this.pswp.currSlide.bounds.center.y)/(this.pswp.viewportSize.y/3)}_setPanWithFriction(t,e,i){const{pan:s,bounds:n}=this.pswp.currSlide;if(n.correctPan(t,e)!==e||i){const a=Math.round(e-s[t]);s[t]+=a*(i||Q)}else s[t]=e}}const it=.05,st=.15;function E(o,t,e){return o.x=(t.x+e.x)/2,o.y=(t.y+e.y)/2,o}class nt{constructor(t){this.gestures=t,this.pswp=this.gestures.pswp,this._startPan={},this._startZoomPoint={},this._zoomPoint={}}start(){this._startZoomLevel=this.pswp.currSlide.currZoomLevel,u(this._startPan,this.pswp.currSlide.pan),this.pswp.animations.stopAllPan(),this._wasOverFitZoomLevel=!1}change(){const{p1:t,startP1:e,p2:i,startP2:s,pswp:n}=this.gestures,{currSlide:r}=n,a=r.zoomLevels.min,l=r.zoomLevels.max;if(!r.isZoomable()||n.mainScroll.isShifted())return;E(this._startZoomPoint,e,s),E(this._zoomPoint,t,i);let h=1/C(e,s)*C(t,i)*this._startZoomLevel;if(h>r.zoomLevels.initial+r.zoomLevels.initial/15&&(this._wasOverFitZoomLevel=!0),hl&&(h=l+(h-l)*it);r.pan.x=this._calculatePanForZoomLevel("x",h),r.pan.y=this._calculatePanForZoomLevel("y",h),r.setZoomLevel(h),r.applyCurrentZoomPan()}end(){const{pswp:t}=this,{currSlide:e}=t;e.currZoomLeveli.zoomLevels.max?n=i.zoomLevels.max:(r=!1,n=s);const a=e.bgOpacity,l=e.bgOpacity<1,h=u({},i.pan);let c=u({},h);t&&(this._zoomPoint.x=0,this._zoomPoint.y=0,this._startZoomPoint.x=0,this._startZoomPoint.y=0,this._startZoomLevel=s,u(this._startPan,h)),r&&(c={x:this._calculatePanForZoomLevel("x",n),y:this._calculatePanForZoomLevel("y",n)}),i.setZoomLevel(n),c={x:i.bounds.correctPan("x",c.x),y:i.bounds.correctPan("y",c.y)},i.setZoomLevel(s);let p=!0;if(I(c,h)&&(p=!1),!p&&!r&&!l){i._setResolution(n),i.applyCurrentZoomPan();return}e.animations.stopAllPan(),e.animations.startSpring({isPan:!0,start:0,end:1e3,velocity:0,dampingRatio:1,naturalFrequency:40,onUpdate:d=>{if(d/=1e3,p||r){if(p&&(i.pan.x=h.x+(c.x-h.x)*d,i.pan.y=h.y+(c.y-h.y)*d),r){const _=s+(n-s)*d;i.setZoomLevel(_)}i.applyCurrentZoomPan()}l&&e.bgOpacity<1&&e.applyBgOpacity(b(a+(1-a)*d,0,1))},onComplete:()=>{i._setResolution(n),i.applyCurrentZoomPan()}})}}function Z(o){return!!o.target.closest(".pswp__container")}class ot{constructor(t){this.gestures=t}click(t,e){const i=e.target.classList,s=i.contains("pswp__img"),n=i.contains("pswp__item")||i.contains("pswp__zoom-wrap");s?this._doClickOrTapAction("imageClick",t,e):n&&this._doClickOrTapAction("bgClick",t,e)}tap(t,e){Z(e)&&this._doClickOrTapAction("tap",t,e)}doubleTap(t,e){Z(e)&&this._doClickOrTapAction("doubleTap",t,e)}_doClickOrTapAction(t,e,i){const{pswp:s}=this.gestures,{currSlide:n}=s,r=t+"Action",a=s.options[r];if(!s.dispatch(r,{point:e,originalEvent:i}).defaultPrevented){if(typeof a=="function"){a.call(s,e,i);return}switch(a){case"close":case"next":s[a]();break;case"zoom":n.toggleZoom(e);break;case"zoom-or-close":n.isZoomable()&&n.zoomLevels.secondary!==n.zoomLevels.initial?n.toggleZoom(e):s.options.clickToCloseNonZoomable&&s.close();break;case"toggle-controls":this.gestures.pswp.element.classList.toggle("pswp--ui-visible");break}}}}const rt=10,at=300,ht=25;class lt{constructor(t){this.pswp=t,this.dragAxis=void 0,this.p1={},this.p2={},this.prevP1={},this.prevP2={},this.startP1={},this.startP2={},this.velocity={},this._lastStartP1={},this._intervalP1={},this._numActivePoints=0,this._ongoingPointers=[],this._touchEventEnabled="ontouchstart"in window,this._pointerEventEnabled=!!window.PointerEvent,this.supportsTouch=this._touchEventEnabled||this._pointerEventEnabled&&navigator.maxTouchPoints>1,this.supportsTouch||(t.options.allowPanToNext=!1),this.drag=new et(this),this.zoomLevels=new nt(this),this.tapHandler=new ot(this),t.on("bindEvents",()=>{t.events.add(t.scrollWrap,"click",e=>this._onClick(e)),this._pointerEventEnabled?this._bindEvents("pointer","down","up","cancel"):this._touchEventEnabled?(this._bindEvents("touch","start","end","cancel"),t.scrollWrap.ontouchmove=()=>{},t.scrollWrap.ontouchend=()=>{}):this._bindEvents("mouse","down","up")})}_bindEvents(t,e,i,s){const{pswp:n}=this,{events:r}=n,a=s?t+s:"";r.add(n.scrollWrap,t+e,this.onPointerDown.bind(this)),r.add(window,t+"move",this.onPointerMove.bind(this)),r.add(window,t+i,this.onPointerUp.bind(this)),a&&r.add(n.scrollWrap,a,this.onPointerUp.bind(this))}onPointerDown(t){let e;if((t.type==="mousedown"||t.pointerType==="mouse")&&(e=!0),e&&t.button>0)return;const{pswp:i}=this;if(!i.opener.isOpen){t.preventDefault();return}i.dispatch("pointerDown",{originalEvent:t}).defaultPrevented||(e&&(i.mouseDetected(),this._preventPointerEventBehaviour(t)),i.animations.stopAll(),this._updatePoints(t,"down"),this.pointerDown=!0,this._numActivePoints===1&&(this.dragAxis=null,u(this.startP1,this.p1)),this._numActivePoints>1?(this._clearTapTimer(),this.isMultitouch=!0):this.isMultitouch=!1)}onPointerMove(t){t.preventDefault(),this._numActivePoints&&(this._updatePoints(t,"move"),!this.pswp.dispatch("pointerMove",{originalEvent:t}).defaultPrevented&&(this._numActivePoints===1&&!this.isDragging?(this.dragAxis||this._calculateDragDirection(),this.dragAxis&&!this.isDragging&&(this.isZooming&&(this.isZooming=!1,this.zoomLevels.end()),this.isDragging=!0,this._clearTapTimer(),this._updateStartPoints(),this._intervalTime=Date.now(),this._velocityCalculated=!1,u(this._intervalP1,this.p1),this.velocity.x=0,this.velocity.y=0,this.drag.start(),this._rafStopLoop(),this._rafRenderLoop())):this._numActivePoints>1&&!this.isZooming&&(this._finishDrag(),this.isZooming=!0,this._updateStartPoints(),this.zoomLevels.start(),this._rafStopLoop(),this._rafRenderLoop())))}_finishDrag(){this.isDragging&&(this.isDragging=!1,this._velocityCalculated||this._updateVelocity(!0),this.drag.end(),this.dragAxis=null)}onPointerUp(t){!this._numActivePoints||(this._updatePoints(t,"up"),!this.pswp.dispatch("pointerUp",{originalEvent:t}).defaultPrevented&&(this._numActivePoints===0&&(this.pointerDown=!1,this._rafStopLoop(),this.isDragging?this._finishDrag():!this.isZooming&&!this.isMultitouch&&this._finishTap(t)),this._numActivePoints<2&&this.isZooming&&(this.isZooming=!1,this.zoomLevels.end(),this._numActivePoints===1&&(this.dragAxis=null,this._updateStartPoints()))))}_rafRenderLoop(){(this.isDragging||this.isZooming)&&(this._updateVelocity(),this.isDragging?I(this.p1,this.prevP1)||this.drag.change():(!I(this.p1,this.prevP1)||!I(this.p2,this.prevP2))&&this.zoomLevels.change(),this._updatePrevPoints(),this.raf=requestAnimationFrame(this._rafRenderLoop.bind(this)))}_updateVelocity(t){const e=Date.now(),i=e-this._intervalTime;i<50&&!t||(this.velocity.x=this._getVelocity("x",i),this.velocity.y=this._getVelocity("y",i),this._intervalTime=e,u(this._intervalP1,this.p1),this._velocityCalculated=!0)}_finishTap(t){const{mainScroll:e}=this.pswp;if(e.isShifted()){e.moveIndexBy(0,!0);return}if(t.type.indexOf("cancel")>0)return;if(t.type==="mouseup"||t.pointerType==="mouse"){this.tapHandler.click(this.startP1,t);return}const i=this.pswp.options.doubleTapAction?at:0;this._tapTimer?(this._clearTapTimer(),C(this._lastStartP1,this.startP1){this.tapHandler.tap(this.startP1,t),this._clearTapTimer()},i))}_clearTapTimer(){this._tapTimer&&(clearTimeout(this._tapTimer),this._tapTimer=null)}_getVelocity(t,e){const i=this.p1[t]-this._intervalP1[t];return Math.abs(i)>1&&e>5?i/e:0}_rafStopLoop(){this.raf&&(cancelAnimationFrame(this.raf),this.raf=null)}_preventPointerEventBehaviour(t){return t.preventDefault(),!0}_updatePoints(t,e){if(this._pointerEventEnabled){const i=t,s=this._ongoingPointers.findIndex(n=>n.id===i.pointerId);e==="up"&&s>-1?this._ongoingPointers.splice(s,1):e==="down"&&s===-1?this._ongoingPointers.push(this._convertEventPosToPoint(i,{})):s>-1&&this._convertEventPosToPoint(i,this._ongoingPointers[s]),this._numActivePoints=this._ongoingPointers.length,this._numActivePoints>0&&u(this.p1,this._ongoingPointers[0]),this._numActivePoints>1&&u(this.p2,this._ongoingPointers[1])}else{const i=t;this._numActivePoints=0,i.type.indexOf("touch")>-1?i.touches&&i.touches.length>0&&(this._convertEventPosToPoint(i.touches[0],this.p1),this._numActivePoints++,i.touches.length>1&&(this._convertEventPosToPoint(i.touches[1],this.p2),this._numActivePoints++)):(this._convertEventPosToPoint(t,this.p1),e==="up"?this._numActivePoints=0:this._numActivePoints++)}}_updatePrevPoints(){u(this.prevP1,this.p1),u(this.prevP2,this.p2)}_updateStartPoints(){u(this.startP1,this.p1),u(this.startP2,this.p2),this._updatePrevPoints()}_calculateDragDirection(){if(this.pswp.mainScroll.isShifted())this.dragAxis="x";else{const t=Math.abs(this.p1.x-this.startP1.x)-Math.abs(this.p1.y-this.startP1.y);if(t!==0){const e=t>0?"x":"y";Math.abs(this.p1[e]-this.startP1[e])>=rt&&(this.dragAxis=e)}}}_convertEventPosToPoint(t,e){return e.x=t.pageX-this.pswp.offset.x,e.y=t.pageY-this.pswp.offset.y,"pointerId"in t?e.id=t.pointerId:t.identifier!==void 0&&(e.id=t.identifier),e}_onClick(t){this.pswp.mainScroll.isShifted()&&(t.preventDefault(),t.stopPropagation())}}const ct=.35;class pt{constructor(t){this.pswp=t,this.x=0,this.slideWidth=void 0,this.itemHolders=void 0,this.resetPosition()}resize(t){const{pswp:e}=this,i=Math.round(e.viewportSize.x+e.viewportSize.x*e.options.spacing),s=i!==this.slideWidth;s&&(this.slideWidth=i,this.moveTo(this.getCurrSlideX())),this.itemHolders.forEach((n,r)=>{s&&y(n.el,(r+this._containerShiftIndex)*this.slideWidth),t&&n.slide&&n.slide.resize()})}resetPosition(){this._currPositionIndex=0,this._prevPositionIndex=0,this.slideWidth=0,this._containerShiftIndex=-1}appendHolders(){this.itemHolders=[];for(let t=0;t<3;t++){const e=m("pswp__item",!1,this.pswp.container);e.style.display=t===1?"block":"none",this.itemHolders.push({el:e})}}canBeSwiped(){return this.pswp.getNumItems()>1}moveIndexBy(t,e,i){const{pswp:s}=this;let n=s.potentialIndex+t;const r=s.getNumItems();if(s.canLoop()){n=s.getLoopedIndex(n);const l=(t+r)%r;l<=r/2?t=l:t=l-r}else n<0?n=0:n>=r&&(n=r-1),t=n-s.potentialIndex;s.potentialIndex=n,this._currPositionIndex-=t,s.animations.stopMainScroll();const a=this.getCurrSlideX();if(!e)this.moveTo(a),this.updateCurrItem();else{s.animations.startSpring({isMainScroll:!0,start:this.x,end:a,velocity:i||0,naturalFrequency:30,dampingRatio:1,onUpdate:h=>{this.moveTo(h)},onComplete:()=>{this.updateCurrItem(),s.appendHeavy()}});let l=s.potentialIndex-s.currIndex;if(s.canLoop()){const h=(l+r)%r;h<=r/2?l=h:l=h-r}Math.abs(l)>1&&this.updateCurrItem()}if(t)return!0}getCurrSlideX(){return this.slideWidth*this._currPositionIndex}isShifted(){return this.x!==this.getCurrSlideX()}updateCurrItem(){const{pswp:t}=this,e=this._prevPositionIndex-this._currPositionIndex;if(!e)return;this._prevPositionIndex=this._currPositionIndex,t.currIndex=t.potentialIndex;let i=Math.abs(e),s;i>=3&&(this._containerShiftIndex+=e+(e>0?-3:3),i=3);for(let n=0;n0?(s=this.itemHolders.shift(),this.itemHolders[2]=s,this._containerShiftIndex++,y(s.el,(this._containerShiftIndex+2)*this.slideWidth),t.setContent(s,t.currIndex-i+n+2)):(s=this.itemHolders.pop(),this.itemHolders.unshift(s),this._containerShiftIndex--,y(s.el,this._containerShiftIndex*this.slideWidth),t.setContent(s,t.currIndex+i-n-2));Math.abs(this._containerShiftIndex)>50&&!this.isShifted()&&(this.resetPosition(),this.resize()),t.animations.stopAllPan(),this.itemHolders.forEach((n,r)=>{n.slide&&n.slide.setIsActive(r===1)}),t.currSlide=this.itemHolders[1].slide,t.contentLoader.updateLazy(e),t.currSlide&&t.currSlide.applyCurrentZoomPan(),t.dispatch("change")}moveTo(t,e){let i,s;!this.pswp.canLoop()&&e&&(i=(this.slideWidth*this._currPositionIndex-t)/this.slideWidth,i+=this.pswp.currIndex,s=Math.round(t-this.x),(i<0&&s>0||i>=this.pswp.getNumItems()-1&&s<0)&&(t=this.x+s*ct)),this.x=t,y(this.pswp.container,t),this.pswp.dispatch("moveMainScroll",{x:t,dragging:e})}}class dt{constructor(t){this.pswp=t,t.on("bindEvents",()=>{t.options.initialPointerPos||this._focusRoot(),t.events.add(document,"focusin",this._onFocusIn.bind(this)),t.events.add(document,"keydown",this._onKeyDown.bind(this))});const e=document.activeElement;t.on("destroy",()=>{t.options.returnFocus&&e&&this._wasFocused&&e.focus()})}_focusRoot(){this._wasFocused||(this.pswp.element.focus(),this._wasFocused=!0)}_onKeyDown(t){const{pswp:e}=this;if(e.dispatch("keydown",{originalEvent:t}).defaultPrevented||X(t))return;let i,s,n;switch(t.keyCode){case 27:e.options.escKey&&(i="close");break;case 90:i="toggleZoom";break;case 37:s="x";break;case 38:s="y";break;case 39:s="x",n=!0;break;case 40:n=!0,s="y";break;case 9:this._focusRoot();break}if(s){t.preventDefault();const{currSlide:r}=e;e.options.arrowKeys&&s==="x"&&e.getNumItems()>1?i=n?"next":"prev":r&&r.currZoomLevel>r.zoomLevels.fit&&(r.pan[s]+=n?-80:80,r.panTo(r.pan.x,r.pan.y))}i&&(t.preventDefault(),e[i]())}_onFocusIn(t){const{template:e}=this.pswp;document!==t.target&&e!==t.target&&!e.contains(t.target)&&e.focus()}}const ut="cubic-bezier(.4,0,.22,1)";class mt{constructor(t){this.props=t;const{target:e,onComplete:i,transform:s,onFinish:n}=t;let{duration:r,easing:a}=t;this.onFinish=n;const l=s?"transform":"opacity",h=t[l];this._target=e,this._onComplete=i,r=r||333,a=a||ut,this._onTransitionEnd=this._onTransitionEnd.bind(this),this._helperTimeout=setTimeout(()=>{R(e,l,r,a),this._helperTimeout=setTimeout(()=>{e.addEventListener("transitionend",this._onTransitionEnd,!1),e.addEventListener("transitioncancel",this._onTransitionEnd,!1),this._helperTimeout=setTimeout(()=>{this._finalizeAnimation()},r+500),e.style[l]=h},30)},0)}_onTransitionEnd(t){t.target===this._target&&this._finalizeAnimation()}_finalizeAnimation(){this._finished||(this._finished=!0,this.onFinish(),this._onComplete&&this._onComplete())}destroy(){this._helperTimeout&&clearTimeout(this._helperTimeout),q(this._target),this._target.removeEventListener("transitionend",this._onTransitionEnd,!1),this._target.removeEventListener("transitioncancel",this._onTransitionEnd,!1),this._finished||this._finalizeAnimation()}}const ft=12,_t=.75;class gt{constructor(t,e,i){this.velocity=t*1e3,this._dampingRatio=e||_t,this._naturalFrequency=i||ft,this._dampingRatio<1&&(this._dampedFrequency=this._naturalFrequency*Math.sqrt(1-this._dampingRatio*this._dampingRatio))}easeFrame(t,e){let i=0,s;e/=1e3;const n=Math.E**(-this._dampingRatio*this._naturalFrequency*e);if(this._dampingRatio===1)s=this.velocity+this._naturalFrequency*t,i=(t+s*e)*n,this.velocity=i*-this._naturalFrequency+s*n;else if(this._dampingRatio<1){s=1/this._dampedFrequency*(this._dampingRatio*this._naturalFrequency*t+this.velocity);const r=Math.cos(this._dampedFrequency*e),a=Math.sin(this._dampedFrequency*e);i=n*(t*r+s*a),this.velocity=i*-this._naturalFrequency*this._dampingRatio+n*(-this._dampedFrequency*t*a+this._dampedFrequency*s*r)}return i}}class yt{constructor(t){this.props=t;const{start:e,end:i,velocity:s,onUpdate:n,onComplete:r,onFinish:a,dampingRatio:l,naturalFrequency:h}=t;this.onFinish=a;const c=new gt(s,l,h);let p=Date.now(),d=e-i;const _=()=>{this._raf&&(d=c.easeFrame(d,Date.now()-p),Math.abs(d)<1&&Math.abs(c.velocity)<50?(n(i),r&&r(),this.onFinish()):(p=Date.now(),n(d+i),this._raf=requestAnimationFrame(_)))};this._raf=requestAnimationFrame(_)}destroy(){this._raf>=0&&cancelAnimationFrame(this._raf),this._raf=null}}class vt{constructor(){this.activeAnimations=[]}startSpring(t){this._start(t,!0)}startTransition(t){this._start(t)}_start(t,e){let i;return e?i=new yt(t):i=new mt(t),this.activeAnimations.push(i),i.onFinish=()=>this.stop(i),i}stop(t){t.destroy();const e=this.activeAnimations.indexOf(t);e>-1&&this.activeAnimations.splice(e,1)}stopAll(){this.activeAnimations.forEach(t=>{t.destroy()}),this.activeAnimations=[]}stopAllPan(){this.activeAnimations=this.activeAnimations.filter(t=>t.props.isPan?(t.destroy(),!1):!0)}stopMainScroll(){this.activeAnimations=this.activeAnimations.filter(t=>t.props.isMainScroll?(t.destroy(),!1):!0)}isPanRunning(){return this.activeAnimations.some(t=>t.props.isPan)}}class wt{constructor(t){this.pswp=t,t.events.add(t.element,"wheel",this._onWheel.bind(this))}_onWheel(t){t.preventDefault();const{currSlide:e}=this.pswp;let{deltaX:i,deltaY:s}=t;if(!!e&&!this.pswp.dispatch("wheel",{originalEvent:t}).defaultPrevented)if(t.ctrlKey||this.pswp.options.wheelToZoom){if(e.isZoomable()){let n=-s;t.deltaMode===1?n*=.05:n*=t.deltaMode?1:.002,n=2**n;const r=e.currZoomLevel*n;e.zoomTo(r,{x:t.clientX,y:t.clientY})}}else e.isPannable()&&(t.deltaMode===1&&(i*=18,s*=18),e.panTo(e.pan.x-i,e.pan.y-s))}}function Pt(o){if(typeof o=="string")return o;if(!o||!o.isCustomSVG)return"";const t=o;let e='",e}class St{constructor(t,e){const i=e.name||e.className;let s=e.html;if(t.options[i]===!1)return;typeof t.options[i+"SVG"]=="string"&&(s=t.options[i+"SVG"]),t.dispatch("uiElementCreate",{data:e});let n="";e.isButton?(n+="pswp__button ",n+=e.className||`pswp__button--${e.name}`):n+=e.className||`pswp__${e.name}`;let r,a=e.isButton?e.tagName||"button":e.tagName||"div";if(a=a.toLowerCase(),r=m(n,a),e.isButton){r=m(n,a),a==="button"&&(r.type="button");let{title:c}=e;const{ariaLabel:p}=e;typeof t.options[i+"Title"]=="string"&&(c=t.options[i+"Title"]),c&&(r.title=c),(p||c)&&r.setAttribute("aria-label",p||c)}r.innerHTML=Pt(s),e.onInit&&e.onInit(r,t),e.onClick&&(r.onclick=c=>{typeof e.onClick=="string"?t[e.onClick]():e.onClick(c,r,t)});const l=e.appendTo||"bar";let h;l==="bar"?(t.topBar||(t.topBar=m("pswp__top-bar pswp__hide-on-close","div",t.scrollWrap)),h=t.topBar):(r.classList.add("pswp__hide-on-close"),l==="wrapper"?h=t.scrollWrap:h=t.element),h.appendChild(t.applyFilters("uiElement",r,e))}}function H(o,t,e){o.classList.add("pswp__button--arrow"),t.on("change",()=>{t.options.loop||(e?o.disabled=!(t.currIndex0))})}const It={name:"arrowPrev",className:"pswp__button--arrow--prev",title:"Previous",order:10,isButton:!0,appendTo:"wrapper",html:{isCustomSVG:!0,size:60,inner:'',outlineID:"pswp__icn-arrow"},onClick:"prev",onInit:H},Lt={name:"arrowNext",className:"pswp__button--arrow--next",title:"Next",order:11,isButton:!0,appendTo:"wrapper",html:{isCustomSVG:!0,size:60,inner:'',outlineID:"pswp__icn-arrow"},onClick:"next",onInit:(o,t)=>{H(o,t,!0)}},bt={name:"close",title:"Close",order:20,isButton:!0,html:{isCustomSVG:!0,inner:'',outlineID:"pswp__icn-close"},onClick:"close"},Ct={name:"zoom",title:"Zoom",order:10,isButton:!0,html:{isCustomSVG:!0,inner:'',outlineID:"pswp__icn-zoom"},onClick:"toggleZoom"},At={name:"preloader",appendTo:"bar",order:7,html:{isCustomSVG:!0,inner:'',outlineID:"pswp__icn-loading"},onInit:(o,t)=>{let e,i;const s=(a,l)=>{o.classList[l?"add":"remove"]("pswp__preloader--"+a)},n=a=>{e!==a&&(e=a,s("active",a))},r=()=>{if(!t.currSlide.content.isLoading()){n(!1),i&&(clearTimeout(i),i=null);return}i||(i=setTimeout(()=>{n(t.currSlide.content.isLoading()),i=null},t.options.preloaderDelay))};t.on("change",r),t.on("loadComplete",a=>{t.currSlide===a.slide&&r()}),t.ui.updatePreloaderVisibility=r}},xt={name:"counter",order:5,onInit:(o,t)=>{t.on("change",()=>{o.innerText=t.currIndex+1+t.options.indexIndicatorSep+t.getNumItems()})}};function D(o,t){o.classList[t?"add":"remove"]("pswp--zoomed-in")}class zt{constructor(t){this.pswp=t,this.updatePreloaderVisibility=void 0,this._lastUpdatedZoomLevel=void 0}init(){const{pswp:t}=this;this.isRegistered=!1,this.uiElementsData=[bt,It,Lt,Ct,At,xt],t.dispatch("uiRegister"),this.uiElementsData.sort((e,i)=>(e.order||0)-(i.order||0)),this.items=[],this.isRegistered=!0,this.uiElementsData.forEach(e=>{this.registerElement(e)}),t.on("change",()=>{t.element.classList[t.getNumItems()===1?"add":"remove"]("pswp--one-slide")}),t.on("zoomPanUpdate",()=>this._onZoomPanUpdate())}registerElement(t){this.isRegistered?this.items.push(new St(this.pswp,t)):this.uiElementsData.push(t)}_onZoomPanUpdate(){const{template:t,currSlide:e,options:i}=this.pswp;let{currZoomLevel:s}=e;if(this.pswp.opener.isClosing||(this.pswp.opener.isOpen||(s=e.zoomLevels.initial),s===this._lastUpdatedZoomLevel))return;this._lastUpdatedZoomLevel=s;const n=e.zoomLevels.initial-e.zoomLevels.secondary;if(Math.abs(n)<.01||!e.isZoomable()){D(t,!1),t.classList.remove("pswp--zoom-allowed");return}t.classList.add("pswp--zoom-allowed");const r=s===e.zoomLevels.initial?e.zoomLevels.secondary:e.zoomLevels.initial;D(t,r<=s),(i.imageClickAction==="zoom"||i.imageClickAction==="zoom-or-close")&&t.classList.add("pswp--click-to-zoom")}}function Tt(o){const t=o.getBoundingClientRect();return{x:t.left,y:t.top,w:t.width}}function Ot(o,t,e){const i=o.getBoundingClientRect(),s=i.width/t,n=i.height/e,r=s>n?s:n,a=(i.width-t*r)/2,l=(i.height-e*r)/2,h={x:i.left+a,y:i.top+l,w:t*r};return h.innerRect={w:i.width,h:i.height,x:a,y:l},h}function Et(o,t,e){const i=e.dispatch("thumbBounds",{index:o,itemData:t,instance:e});if(i.thumbBounds)return i.thumbBounds;const{element:s}=t;let n,r;if(s&&e.options.thumbSelector!==!1){const a=e.options.thumbSelector||"img";r=s.matches(a)?s:s.querySelector(a)}return r=e.applyFilters("thumbEl",r,t,o),r&&(t.thumbCropped?n=Ot(r,t.width||t.w,t.height||t.h):n=Tt(r)),e.applyFilters("thumbBounds",n,t,o)}class Zt{constructor(t,e){this.type=t,e&&Object.assign(this,e)}preventDefault(){this.defaultPrevented=!0}}class Dt{constructor(){this._listeners={},this._filters={},this.pswp=void 0,this.options=void 0}addFilter(t,e,i=100){this._filters[t]||(this._filters[t]=[]),this._filters[t].push({fn:e,priority:i}),this._filters[t].sort((s,n)=>s.priority-n.priority),this.pswp&&this.pswp.addFilter(t,e,i)}removeFilter(t,e){this._filters[t]&&(this._filters[t]=this._filters[t].filter(i=>i.fn!==e)),this.pswp&&this.pswp.removeFilter(t,e)}applyFilters(t,...e){return this._filters[t]&&this._filters[t].forEach(i=>{e[0]=i.fn.apply(this,e)}),e[0]}on(t,e){this._listeners[t]||(this._listeners[t]=[]),this._listeners[t].push(e),this.pswp&&this.pswp.on(t,e)}off(t,e){this._listeners[t]&&(this._listeners[t]=this._listeners[t].filter(i=>e!==i)),this.pswp&&this.pswp.off(t,e)}dispatch(t,e){if(this.pswp)return this.pswp.dispatch(t,e);const i=new Zt(t,e);return this._listeners&&this._listeners[t]&&this._listeners[t].forEach(s=>{s.call(this,i)}),i}}class Mt{constructor(t,e){this.element=m("pswp__img pswp__img--placeholder",t?"img":"",e),t&&(this.element.decoding="async",this.element.alt="",this.element.src=t,this.element.setAttribute("role","presentation")),this.element.setAttribute("aria-hidden","true")}setDisplayedSize(t,e){!this.element||(this.element.tagName==="IMG"?(A(this.element,250,"auto"),this.element.style.transformOrigin="0 0",this.element.style.transform=L(0,0,t/250)):A(this.element,t,e))}destroy(){this.element.parentNode&&this.element.remove(),this.element=null}}class Rt{constructor(t,e,i){this.instance=e,this.data=t,this.index=i,this.element=void 0,this.displayedImageWidth=0,this.displayedImageHeight=0,this.width=Number(this.data.w)||Number(this.data.width)||0,this.height=Number(this.data.h)||Number(this.data.height)||0,this.isAttached=!1,this.hasSlide=!1,this.state=f.IDLE,this.data.type?this.type=this.data.type:this.data.src?this.type="image":this.type="html",this.instance.dispatch("contentInit",{content:this})}removePlaceholder(){this.placeholder&&!this.keepPlaceholder()&&setTimeout(()=>{this.placeholder&&(this.placeholder.destroy(),this.placeholder=null)},1e3)}load(t,e){if(this.slide&&this.usePlaceholder())if(this.placeholder){const i=this.placeholder.element;i&&!i.parentElement&&this.slide.container.prepend(i)}else{const i=this.instance.applyFilters("placeholderSrc",this.data.msrc&&this.slide.isFirstSlide?this.data.msrc:!1,this);this.placeholder=new Mt(i,this.slide.container)}this.element&&!e||this.instance.dispatch("contentLoad",{content:this,isLazy:t}).defaultPrevented||(this.isImageContent()?(this.element=m("pswp__img","img"),this.displayedImageWidth&&this.loadImage(t)):(this.element=m("pswp__content"),this.element.innerHTML=this.data.html||""),e&&this.slide&&this.slide.updateContentSize(!0))}loadImage(t){const e=this.element;this.instance.dispatch("contentLoadImage",{content:this,isLazy:t}).defaultPrevented||(this.updateSrcsetSizes(),this.data.srcset&&(e.srcset=this.data.srcset),e.src=this.data.src,e.alt=this.data.alt||"",this.state=f.LOADING,e.complete?this.onLoaded():(e.onload=()=>{this.onLoaded()},e.onerror=()=>{this.onError()}))}setSlide(t){this.slide=t,this.hasSlide=!0,this.instance=t.pswp}onLoaded(){this.state=f.LOADED,this.slide&&(this.instance.dispatch("loadComplete",{slide:this.slide,content:this}),this.slide.isActive&&this.slide.heavyAppended&&!this.element.parentNode&&(this.append(),this.slide.updateContentSize(!0)),(this.state===f.LOADED||this.state===f.ERROR)&&this.removePlaceholder())}onError(){this.state=f.ERROR,this.slide&&(this.displayError(),this.instance.dispatch("loadComplete",{slide:this.slide,isError:!0,content:this}),this.instance.dispatch("loadError",{slide:this.slide,content:this}))}isLoading(){return this.instance.applyFilters("isContentLoading",this.state===f.LOADING,this)}isError(){return this.state===f.ERROR}isImageContent(){return this.type==="image"}setDisplayedSize(t,e){if(!!this.element&&(this.placeholder&&this.placeholder.setDisplayedSize(t,e),!this.instance.dispatch("contentResize",{content:this,width:t,height:e}).defaultPrevented&&(A(this.element,t,e),this.isImageContent()&&!this.isError()))){const i=!this.displayedImageWidth&&t;this.displayedImageWidth=t,this.displayedImageHeight=e,i?this.loadImage(!1):this.updateSrcsetSizes(),this.slide&&this.instance.dispatch("imageSizeChange",{slide:this.slide,width:t,height:e,content:this})}}isZoomable(){return this.instance.applyFilters("isContentZoomable",this.isImageContent()&&this.state!==f.ERROR,this)}updateSrcsetSizes(){if(this.data.srcset){const t=this.element,e=this.instance.applyFilters("srcsetSizesWidth",this.displayedImageWidth,this);(!t.dataset.largestUsedSize||e>parseInt(t.dataset.largestUsedSize,10))&&(t.sizes=e+"px",t.dataset.largestUsedSize=String(e))}}usePlaceholder(){return this.instance.applyFilters("useContentPlaceholder",this.isImageContent(),this)}lazyLoad(){this.instance.dispatch("contentLazyLoad",{content:this}).defaultPrevented||this.load(!0)}keepPlaceholder(){return this.instance.applyFilters("isKeepingPlaceholder",this.isLoading(),this)}destroy(){this.hasSlide=!1,this.slide=null,!this.instance.dispatch("contentDestroy",{content:this}).defaultPrevented&&(this.remove(),this.placeholder&&(this.placeholder.destroy(),this.placeholder=null),this.isImageContent()&&this.element&&(this.element.onload=null,this.element.onerror=null,this.element=null))}displayError(){if(this.slide){let t=m("pswp__error-msg");t.innerText=this.instance.options.errorMsg,t=this.instance.applyFilters("contentErrorElement",t,this),this.element=m("pswp__content pswp__error-msg-container"),this.element.appendChild(t),this.slide.container.innerText="",this.slide.container.appendChild(this.element),this.slide.updateContentSize(!0),this.removePlaceholder()}}append(){if(this.isAttached)return;if(this.isAttached=!0,this.state===f.ERROR){this.displayError();return}if(this.instance.dispatch("contentAppend",{content:this}).defaultPrevented)return;const t="decode"in this.element;this.isImageContent()?t&&this.slide&&(!this.slide.isActive||x())?(this.isDecoding=!0,this.element.decode().catch(()=>{}).finally(()=>{this.isDecoding=!1,this.appendImage()})):this.appendImage():this.element&&!this.element.parentNode&&this.slide.container.appendChild(this.element)}activate(){this.instance.dispatch("contentActivate",{content:this}).defaultPrevented||this.slide&&(this.isImageContent()&&this.isDecoding&&!x()?this.appendImage():this.isError()&&this.load(!1,!0))}deactivate(){this.instance.dispatch("contentDeactivate",{content:this})}remove(){this.isAttached=!1,!this.instance.dispatch("contentRemove",{content:this}).defaultPrevented&&(this.element&&this.element.parentNode&&this.element.remove(),this.placeholder&&this.placeholder.element&&this.placeholder.element.remove())}appendImage(){!this.isAttached||this.instance.dispatch("contentAppendImage",{content:this}).defaultPrevented||(this.slide&&this.element&&!this.element.parentNode&&this.slide.container.appendChild(this.element),(this.state===f.LOADED||this.state===f.ERROR)&&this.removePlaceholder())}}const Ft=5;function W(o,t,e){const i=t.createContentFromData(o,e);if(!i||!i.lazyLoad)return;const{options:s}=t,n=t.viewportSize||B(s,t),r=N(s,n,o,e),a=new k(s,o,-1);return a.update(i.width,i.height,r),i.lazyLoad(),i.setDisplayedSize(Math.ceil(i.width*a.initial),Math.ceil(i.height*a.initial)),i}function Bt(o,t){const e=t.getItemData(o);if(!t.dispatch("lazyLoadSlide",{index:o,itemData:e}).defaultPrevented)return W(e,t,o)}class Nt{constructor(t){this.pswp=t,this.limit=Math.max(t.options.preload[0]+t.options.preload[1]+1,Ft),this._cachedItems=[]}updateLazy(t){const{pswp:e}=this;if(e.dispatch("lazyLoad").defaultPrevented)return;const{preload:i}=e.options,s=t===void 0?!0:t>=0;let n;for(n=0;n<=i[1];n++)this.loadSlideByIndex(e.currIndex+(s?n:-n));for(n=1;n<=i[0];n++)this.loadSlideByIndex(e.currIndex+(s?-n:n))}loadSlideByIndex(t){t=this.pswp.getLoopedIndex(t);let e=this.getContentByIndex(t);e||(e=Bt(t,this.pswp),e&&this.addToCache(e))}getContentBySlide(t){let e=this.getContentByIndex(t.index);return e||(e=this.pswp.createContentFromData(t.data,t.index),e&&this.addToCache(e)),e&&e.setSlide(t),e}addToCache(t){if(this.removeByIndex(t.index),this._cachedItems.push(t),this._cachedItems.length>this.limit){const e=this._cachedItems.findIndex(i=>!i.isAttached&&!i.hasSlide);e!==-1&&this._cachedItems.splice(e,1)[0].destroy()}}removeByIndex(t){const e=this._cachedItems.findIndex(i=>i.index===t);e!==-1&&this._cachedItems.splice(e,1)}getContentByIndex(t){return this._cachedItems.find(e=>e.index===t)}destroy(){this._cachedItems.forEach(t=>t.destroy()),this._cachedItems=null}}class kt extends Dt{getNumItems(){let t;const{dataSource:e}=this.options;e?"length"in e?t=e.length:"gallery"in e&&(e.items||(e.items=this._getGalleryDOMElements(e.gallery)),e.items&&(t=e.items.length)):t=0;const i=this.dispatch("numItems",{dataSource:e,numItems:t});return this.applyFilters("numItems",i.numItems,e)}createContentFromData(t,e){return new Rt(t,this,e)}getItemData(t){const{dataSource:e}=this.options;let i;Array.isArray(e)?i=e[t]:e&&e.gallery&&(e.items||(e.items=this._getGalleryDOMElements(e.gallery)),i=e.items[t]);let s=i;s instanceof Element&&(s=this._domElementToItemData(s));const n=this.dispatch("itemData",{itemData:s||{},index:t});return this.applyFilters("itemData",n.itemData,t)}_getGalleryDOMElements(t){return this.options.children||this.options.childSelector?K(this.options.children,this.options.childSelector,t)||[]:[t]}_domElementToItemData(t){const e={element:t},i=t.tagName==="A"?t:t.querySelector("a");if(i){e.src=i.dataset.pswpSrc||i.href,i.dataset.pswpSrcset&&(e.srcset=i.dataset.pswpSrcset),e.width=parseInt(i.dataset.pswpWidth,10),e.height=parseInt(i.dataset.pswpHeight,10),e.w=e.width,e.h=e.height,i.dataset.pswpType&&(e.type=i.dataset.pswpType);const s=t.querySelector("img");s&&(e.msrc=s.currentSrc||s.src,e.alt=s.getAttribute("alt")),(i.dataset.pswpCropped||i.dataset.cropped)&&(e.thumbCropped=!0)}return this.applyFilters("domItemData",e,t,i)}lazyLoadData(t,e){return W(t,this,e)}}const P=.003;class Ht{constructor(t){this.pswp=t,this.isClosed=!0,this._prepareOpen=this._prepareOpen.bind(this),this._thumbBounds=void 0,t.on("firstZoomPan",this._prepareOpen)}open(){this._prepareOpen(),this._start()}close(){if(this.isClosed||this.isClosing||this.isOpening)return!1;const t=this.pswp.currSlide;return this.isOpen=!1,this.isOpening=!1,this.isClosing=!0,this._duration=this.pswp.options.hideAnimationDuration,t&&t.currZoomLevel*t.width>=this.pswp.options.maxWidthToAnimate&&(this._duration=0),this._applyStartProps(),setTimeout(()=>{this._start()},this._croppedZoom?30:0),!0}_prepareOpen(){if(this.pswp.off("firstZoomPan",this._prepareOpen),!this.isOpening){const t=this.pswp.currSlide;this.isOpening=!0,this.isClosing=!1,this._duration=this.pswp.options.showAnimationDuration,t&&t.zoomLevels.initial*t.width>=this.pswp.options.maxWidthToAnimate&&(this._duration=0),this._applyStartProps()}}_applyStartProps(){const{pswp:t}=this,e=this.pswp.currSlide,{options:i}=t;if(i.showHideAnimationType==="fade"?(i.showHideOpacity=!0,this._thumbBounds=!1):i.showHideAnimationType==="none"?(i.showHideOpacity=!1,this._duration=0,this._thumbBounds=!1):this.isOpening&&t._initialThumbBounds?this._thumbBounds=t._initialThumbBounds:this._thumbBounds=this.pswp.getThumbBounds(),this._placeholder=e.getPlaceholderElement(),t.animations.stopAll(),this._useAnimation=this._duration>50,this._animateZoom=Boolean(this._thumbBounds)&&e.content&&e.content.usePlaceholder()&&(!this.isClosing||!t.mainScroll.isShifted()),this._animateZoom?this._animateRootOpacity=i.showHideOpacity:(this._animateRootOpacity=!0,this.isOpening&&(e.zoomAndPanToInitial(),e.applyCurrentZoomPan())),this._animateBgOpacity=!this._animateRootOpacity&&this.pswp.options.bgOpacity>P,this._opacityElement=this._animateRootOpacity?t.element:t.bg,!this._useAnimation){this._duration=0,this._animateZoom=!1,this._animateBgOpacity=!1,this._animateRootOpacity=!0,this.isOpening&&(t.element.style.opacity=String(P),t.applyBgOpacity(1));return}this._animateZoom&&this._thumbBounds&&this._thumbBounds.innerRect?(this._croppedZoom=!0,this._cropContainer1=this.pswp.container,this._cropContainer2=this.pswp.currSlide.holderElement,t.container.style.overflow="hidden",t.container.style.width=t.viewportSize.x+"px"):this._croppedZoom=!1,this.isOpening?(this._animateRootOpacity?(t.element.style.opacity=String(P),t.applyBgOpacity(1)):(this._animateBgOpacity&&(t.bg.style.opacity=String(P)),t.element.style.opacity="1"),this._animateZoom&&(this._setClosedStateZoomPan(),this._placeholder&&(this._placeholder.style.willChange="transform",this._placeholder.style.opacity=String(P)))):this.isClosing&&(t.mainScroll.itemHolders[0].el.style.display="none",t.mainScroll.itemHolders[2].el.style.display="none",this._croppedZoom&&t.mainScroll.x!==0&&(t.mainScroll.resetPosition(),t.mainScroll.resize()))}_start(){this.isOpening&&this._useAnimation&&this._placeholder&&this._placeholder.tagName==="IMG"?new Promise(t=>{let e=!1,i=!0;G(this._placeholder).finally(()=>{e=!0,i||t()}),setTimeout(()=>{i=!1,e&&t()},50),setTimeout(t,250)}).finally(()=>this._initiate()):this._initiate()}_initiate(){this.pswp.element.style.setProperty("--pswp-transition-duration",this._duration+"ms"),this.pswp.dispatch(this.isOpening?"openingAnimationStart":"closingAnimationStart"),this.pswp.dispatch("initialZoom"+(this.isOpening?"In":"Out")),this.pswp.element.classList[this.isOpening?"add":"remove"]("pswp--ui-visible"),this.isOpening?(this._placeholder&&(this._placeholder.style.opacity="1"),this._animateToOpenState()):this.isClosing&&this._animateToClosedState(),this._useAnimation||this._onAnimationComplete()}_onAnimationComplete(){const{pswp:t}=this;this.isOpen=this.isOpening,this.isClosed=this.isClosing,this.isOpening=!1,this.isClosing=!1,t.dispatch(this.isOpen?"openingAnimationEnd":"closingAnimationEnd"),t.dispatch("initialZoom"+(this.isOpen?"InEnd":"OutEnd")),this.isClosed?t.destroy():this.isOpen&&(this._animateZoom&&(t.container.style.overflow="visible",t.container.style.width="100%"),t.currSlide.applyCurrentZoomPan())}_animateToOpenState(){const{pswp:t}=this;this._animateZoom&&(this._croppedZoom&&(this._animateTo(this._cropContainer1,"transform","translate3d(0,0,0)"),this._animateTo(this._cropContainer2,"transform","none")),t.currSlide.zoomAndPanToInitial(),this._animateTo(t.currSlide.container,"transform",t.currSlide.getCurrentTransform())),this._animateBgOpacity&&this._animateTo(t.bg,"opacity",String(t.options.bgOpacity)),this._animateRootOpacity&&this._animateTo(t.element,"opacity","1")}_animateToClosedState(){const{pswp:t}=this;this._animateZoom&&this._setClosedStateZoomPan(!0),this._animateBgOpacity&&t.bgOpacity>.01&&this._animateTo(t.bg,"opacity","0"),this._animateRootOpacity&&this._animateTo(t.element,"opacity","0")}_setClosedStateZoomPan(t){if(!this._thumbBounds)return;const{pswp:e}=this,{innerRect:i}=this._thumbBounds,{currSlide:s,viewportSize:n}=e;if(this._croppedZoom){const r=-n.x+(this._thumbBounds.x-i.x)+i.w,a=-n.y+(this._thumbBounds.y-i.y)+i.h,l=n.x-i.w,h=n.y-i.h;t?(this._animateTo(this._cropContainer1,"transform",L(r,a)),this._animateTo(this._cropContainer2,"transform",L(l,h))):(y(this._cropContainer1,r,a),y(this._cropContainer2,l,h))}u(s.pan,i||this._thumbBounds),s.currZoomLevel=this._thumbBounds.w/s.width,t?this._animateTo(s.container,"transform",s.getCurrentTransform()):s.applyCurrentZoomPan()}_animateTo(t,e,i){if(!this._duration){t.style[e]=i;return}const{animations:s}=this.pswp,n={duration:this._duration,easing:this.pswp.options.easing,onComplete:()=>{s.activeAnimations.length||this._onAnimationComplete()},target:t};n[e]=i,s.startTransition(n)}}const Wt={allowPanToNext:!0,spacing:.1,loop:!0,pinchToClose:!0,closeOnVerticalDrag:!0,hideAnimationDuration:333,showAnimationDuration:333,zoomAnimationDuration:333,escKey:!0,arrowKeys:!0,returnFocus:!0,maxWidthToAnimate:4e3,clickToCloseNonZoomable:!0,imageClickAction:"zoom-or-close",bgClickAction:"close",tapAction:"toggle-controls",doubleTapAction:"zoom",indexIndicatorSep:" / ",preloaderDelay:2e3,bgOpacity:.8,index:0,errorMsg:"The image cannot be loaded",preload:[1,2],easing:"cubic-bezier(.4,0,.22,1)"};class Vt extends kt{constructor(t){super(),this._prepareOptions(t),this.offset={},this._prevViewportSize={},this.viewportSize={},this.bgOpacity=1,this.topBar=void 0,this.events=new Y,this.animations=new vt,this.mainScroll=new pt(this),this.gestures=new lt(this),this.opener=new Ht(this),this.keyboard=new dt(this),this.contentLoader=new Nt(this)}init(){if(this.isOpen||this.isDestroying)return;this.isOpen=!0,this.dispatch("init"),this.dispatch("beforeOpen"),this._createMainStructure();let t="pswp--open";return this.gestures.supportsTouch&&(t+=" pswp--touch"),this.options.mainClass&&(t+=" "+this.options.mainClass),this.element.className+=" "+t,this.currIndex=this.options.index||0,this.potentialIndex=this.currIndex,this.dispatch("firstUpdate"),this.scrollWheel=new wt(this),(Number.isNaN(this.currIndex)||this.currIndex<0||this.currIndex>=this.getNumItems())&&(this.currIndex=0),this.gestures.supportsTouch||this.mouseDetected(),this.updateSize(),this.offset.y=window.pageYOffset,this._initialItemData=this.getItemData(this.currIndex),this.dispatch("gettingData",{index:this.currIndex,data:this._initialItemData,slide:void 0}),this._initialThumbBounds=this.getThumbBounds(),this.dispatch("initialLayout"),this.on("openingAnimationEnd",()=>{this.mainScroll.itemHolders[0].el.style.display="block",this.mainScroll.itemHolders[2].el.style.display="block",this.setContent(this.mainScroll.itemHolders[0],this.currIndex-1),this.setContent(this.mainScroll.itemHolders[2],this.currIndex+1),this.appendHeavy(),this.contentLoader.updateLazy(),this.events.add(window,"resize",this._handlePageResize.bind(this)),this.events.add(window,"scroll",this._updatePageScrollOffset.bind(this)),this.dispatch("bindEvents")}),this.setContent(this.mainScroll.itemHolders[1],this.currIndex),this.dispatch("change"),this.opener.open(),this.dispatch("afterInit"),!0}getLoopedIndex(t){const e=this.getNumItems();return this.options.loop&&(t>e-1&&(t-=e),t<0&&(t+=e)),t=b(t,0,e-1),t}appendHeavy(){this.mainScroll.itemHolders.forEach(t=>{t.slide&&t.slide.appendHeavy()})}goTo(t){this.mainScroll.moveIndexBy(this.getLoopedIndex(t)-this.potentialIndex)}next(){this.goTo(this.potentialIndex+1)}prev(){this.goTo(this.potentialIndex-1)}zoomTo(...t){this.currSlide.zoomTo(...t)}toggleZoom(){this.currSlide.toggleZoom()}close(){!this.opener.isOpen||this.isDestroying||(this.isDestroying=!0,this.dispatch("close"),this.events.removeAll(),this.opener.close())}destroy(){if(!this.isDestroying){this.options.showHideAnimationType="none",this.close();return}this.dispatch("destroy"),this.listeners=null,this.scrollWrap.ontouchmove=null,this.scrollWrap.ontouchend=null,this.element.remove(),this.mainScroll.itemHolders.forEach(t=>{t.slide&&t.slide.destroy()}),this.contentLoader.destroy(),this.events.removeAll()}refreshSlideContent(t){this.contentLoader.removeByIndex(t),this.mainScroll.itemHolders.forEach((e,i)=>{let s=this.currSlide.index-1+i;this.canLoop()&&(s=this.getLoopedIndex(s)),s===t&&(this.setContent(e,t,!0),i===1&&(this.currSlide=e.slide,e.slide.setIsActive(!0)))}),this.dispatch("change")}setContent(t,e,i){if(this.canLoop()&&(e=this.getLoopedIndex(e)),t.slide){if(t.slide.index===e&&!i)return;t.slide.destroy(),t.slide=null}if(!this.canLoop()&&(e<0||e>=this.getNumItems()))return;const s=this.getItemData(e);t.slide=new $(s,e,this),e===this.currIndex&&(this.currSlide=t.slide),t.slide.append(t.el)}getViewportCenterPoint(){return{x:this.viewportSize.x/2,y:this.viewportSize.y/2}}updateSize(t){if(this.isDestroying)return;const e=B(this.options,this);!t&&I(e,this._prevViewportSize)||(u(this._prevViewportSize,e),this.dispatch("beforeResize"),u(this.viewportSize,this._prevViewportSize),this._updatePageScrollOffset(),this.dispatch("viewportSize"),this.mainScroll.resize(this.opener.isOpen),!this.hasMouse&&window.matchMedia("(any-hover: hover)").matches&&this.mouseDetected(),this.dispatch("resize"))}applyBgOpacity(t){this.bgOpacity=Math.max(t,0),this.bg.style.opacity=String(this.bgOpacity*this.options.bgOpacity)}mouseDetected(){this.hasMouse||(this.hasMouse=!0,this.element.classList.add("pswp--has_mouse"))}_handlePageResize(){this.updateSize(),/iPhone|iPad|iPod/i.test(window.navigator.userAgent)&&setTimeout(()=>{this.updateSize()},500)}_updatePageScrollOffset(){this.setScrollOffset(0,window.pageYOffset)}setScrollOffset(t,e){this.offset.x=t,this.offset.y=e,this.dispatch("updateScrollOffset")}_createMainStructure(){this.element=m("pswp"),this.element.setAttribute("tabindex","-1"),this.element.setAttribute("role","dialog"),this.template=this.element,this.bg=m("pswp__bg",!1,this.element),this.scrollWrap=m("pswp__scroll-wrap",!1,this.element),this.container=m("pswp__container",!1,this.scrollWrap),this.mainScroll.appendHolders(),this.ui=new zt(this),this.ui.init(),(this.options.appendToEl||document.body).appendChild(this.element)}getThumbBounds(){return Et(this.currIndex,this.currSlide?this.currSlide.data:this._initialItemData,this)}canLoop(){return this.options.loop&&this.getNumItems()>2}_prepareOptions(t){window.matchMedia("(prefers-reduced-motion), (update: slow)").matches&&(t.showHideAnimationType="none",t.zoomAnimationDuration=0),this.options={...Wt,...t}}}export{Vt as default}; diff --git a/assets/platform-selector.697b4dd8.js b/assets/platform-selector.697b4dd8.js new file mode 100644 index 0000000000..815cb158e4 --- /dev/null +++ b/assets/platform-selector.697b4dd8.js @@ -0,0 +1 @@ +const s="/assets/platform-selector.99129dff.png";export{s as _}; diff --git a/assets/platform-selector.99129dff.png b/assets/platform-selector.99129dff.png new file mode 100644 index 0000000000..1d11d60c7a Binary files /dev/null and b/assets/platform-selector.99129dff.png differ diff --git a/assets/portable-mode-folder.90669a9a.js b/assets/portable-mode-folder.90669a9a.js new file mode 100644 index 0000000000..e4635c4067 --- /dev/null +++ b/assets/portable-mode-folder.90669a9a.js @@ -0,0 +1 @@ +const s="/assets/devtools.5a855fb4.png",t="/assets/python-settings.491ac01e.png",o="/assets/python-grammar.e7564a96.png",a="/assets/cursor-scope.9586ad1c.png",e="/assets/portable-mode-folder.e0ef41a8.png";export{s as _,t as a,o as b,a as c,e as d}; diff --git a/assets/portable-mode-folder.e0ef41a8.png b/assets/portable-mode-folder.e0ef41a8.png new file mode 100644 index 0000000000..34685ee770 Binary files /dev/null and b/assets/portable-mode-folder.e0ef41a8.png differ diff --git a/assets/preview.21df42c6.png b/assets/preview.21df42c6.png new file mode 100644 index 0000000000..901940396a Binary files /dev/null and b/assets/preview.21df42c6.png differ diff --git a/assets/preview.2eaf146a.js b/assets/preview.2eaf146a.js new file mode 100644 index 0000000000..ab1083b1bf --- /dev/null +++ b/assets/preview.2eaf146a.js @@ -0,0 +1 @@ +const s="/assets/spellcheck.603fa2d2.png",e="/assets/preview.21df42c6.png";export{s as _,e as a}; diff --git a/assets/project-symbols.f73386c6.png b/assets/project-symbols.f73386c6.png new file mode 100644 index 0000000000..d52fe2838d Binary files /dev/null and b/assets/project-symbols.f73386c6.png differ diff --git a/assets/project-view.e5e09e11.png b/assets/project-view.e5e09e11.png new file mode 100644 index 0000000000..d136d3b6e5 Binary files /dev/null and b/assets/project-view.e5e09e11.png differ diff --git a/assets/provider-api.html.c71bfab3.js b/assets/provider-api.html.c71bfab3.js new file mode 100644 index 0000000000..3eb265b51c --- /dev/null +++ b/assets/provider-api.html.c71bfab3.js @@ -0,0 +1,94 @@ +import{_ as t,o as i,c as p,a as s,b as e,d as a,f as o,r}from"./app.0e1565ce.js";const l="/assets/autocomplete-description.1efc24d3.jpg",c={},u=o(`

    Provider API

    Note

    Please note that its possible this is outdated, as its original version was published by @'Nathan Sobo' on Jan 3, 2018.

    The Provider API allows you to make autocomplete+ awesome. The Pulsar community will ultimately judge the quality of Pulsar's autocomplete experience by the breadth and depth of the provider ecosystem. We're so excited that you're here reading about how to make Pulsar awesome.

    The following examples are in CoffeeScript. If you would like to add JavaScript examples, please feel free to edit this page!

    Defining A Provider

    provider =
    +  # This will work on JavaScript and CoffeeScript files, but not in js comments.
    +  selector: '.source.js, .source.coffee'
    +  disableForSelector: '.source.js .comment'
    +
    +  # This will take priority over the default provider, which has an inclusionPriority of 0.
    +  # \`excludeLowerPriority\` will suppress any providers with a lower priority
    +  # i.e. The default provider will be suppressed
    +  inclusionPriority: 1
    +  excludeLowerPriority: true
    +
    +  # This will be suggested before the default provider, which has a suggestionPriority of 1.
    +  suggestionPriority: 2
    +
    +  # Let autocomplete+ filter and sort the suggestions you provide.
    +  filterSuggestions: true
    +
    +  # Required: Return a promise, an array of suggestions, or null.
    +  getSuggestions: ({editor, bufferPosition, scopeDescriptor, prefix, activatedManually}) ->
    +    new Promise (resolve) ->
    +      resolve([text: 'something'])
    +
    +  # (optional): (*experimental*) called when user the user selects a suggestion for the purpose of loading additional information about the suggestion.
    +  getSuggestionDetailsOnSelect: (suggestion) ->
    +    new Promise (resolve) ->
    +      resolve(newSuggestion)
    +
    +  # (optional): called _after_ the suggestion \`replacementPrefix\` is replaced
    +  # by the suggestion \`text\` in the buffer
    +  onDidInsertSuggestion: ({editor, triggerPosition, suggestion}) ->
    +
    +  # (optional): called when your provider needs to be cleaned up. Unsubscribe
    +  # from things, kill any processes, etc.
    +  dispose: ->
    +

    The properties of a provider:

    `,7),d=o("
  • selector (required): Defines the scope selector(s) (can be comma-separated) for which your provider should receive suggestion requests
  • getSuggestions (required): Is called when a suggestion request has been dispatched by autocomplete+ to your provider. Return an array of suggestions (if any) in the order you would like them displayed to the user. Returning a Promise of an array of suggestions is also supported.
  • getSuggestionDetailsOnSelect (optional): (experimental) Is called when a suggestion is selected by the user for the purpose of loading more information about the suggestion. Return a Promise of the new suggestion to replace it with or return null if no change is needed.
  • disableForSelector (optional): Defines the scope selector(s) (can be comma-separated) for which your provider should not be used
  • inclusionPriority (optional): A number to indicate its priority to be included in a suggestions request. The default provider has an inclusion priority of 0. Higher priority providers can suppress lower priority providers with excludeLowerPriority.
  • excludeLowerPriority (optional): Will not use lower priority providers when this provider is used.
  • suggestionPriority (optional): A number to determine the sort order of suggestions. The default provider has an suggestion priority of 1
  • filterSuggestions (optional): If set to true, autocomplete+ will perform fuzzy filtering and sorting on the list of matches returned by getSuggestions.
  • dispose (optional): Will be called if your provider is being destroyed by autocomplete+
  • ",9),h=s("code",null,"onDidInsertSuggestion",-1),g=s("code",null,"editor",-1),m={href:"https://atom.io/docs/api/latest/TextEditor",target:"_blank",rel:"noopener noreferrer"},k=s("code",null,"triggerPosition",-1),v={href:"https://atom.io/docs/api/latest/Point",target:"_blank",rel:"noopener noreferrer"},f=s("li",null,[s("code",null,"suggestion"),e(": The suggestion object that was inserted.")],-1),b=o(`

    Support For Asynchronous Request Handling

    Some providers satisfy a suggestion request in an asynchronous way (e.g. it may need to dispatch requests to an external process to get suggestions). To asynchronously provide suggestions, simply return a Promise from your getSuggestions:

    getSuggestions: (options) ->
    +  return new Promise (resolve) ->
    +    # Build your suggestions here asynchronously...
    +    resolve(suggestions) # When you are done, call resolve and pass your suggestions to it
    +

    The Suggestion Request's Options Object

    An options object will be passed to your getSuggestions function, with the following properties:

    `,5),y=s("li",null,[s("code",null,"editor"),e(": The current "),s("code",null,"TextEditor")],-1),_=s("li",null,[s("code",null,"bufferPosition"),e(": The position of the cursor")],-1),w=s("code",null,"scopeDescriptor",-1),x={href:"http://flight-manual.atom.io/behind-atom/sections/scoped-settings-scopes-and-scope-descriptors/#scope-descriptors",target:"_blank",rel:"noopener noreferrer"},P=s("li",null,[s("code",null,"prefix"),e(": The word characters immediately preceding the current cursor position")],-1),T=s("li",null,[s("code",null,"activatedManually"),e(": Whether the autocomplete request was initiated by the user (e.g. with ctrl+space)")],-1),S=o(`

    Suggestions

    provider =
    +  selector: '.source.js, .source.coffee'
    +  disableForSelector: '.source.js .comment'
    +
    +  getSuggestions: ({editor, bufferPosition, scopeDescriptor, prefix}) ->
    +    new Promise (resolve) ->
    +      # Find your suggestions here
    +      suggestion =
    +        text: 'someText' # OR
    +        snippet: 'someText(\${1:myArg})'
    +        displayText: 'someText' # (optional)
    +        replacementPrefix: 'so' # (optional)
    +        type: 'function' # (optional)
    +        leftLabel: '' # (optional)
    +        leftLabelHTML: '' # (optional)
    +        rightLabel: '' # (optional)
    +        rightLabelHTML: '' # (optional)
    +        className: '' # (optional)
    +        iconHTML: '' # (optional)
    +        description: '' # (optional)
    +        descriptionMoreURL: '' # (optional)
    +        characterMatchIndices: [0, 1, 2] # (optional)
    +      resolve([suggestion])
    +

    Your suggestions should be returned from getSuggestions as an array of objects with the following properties:

    `,3),q=s("li",null,[s("code",null,"text"),e(" (required; or "),s("code",null,"snippet"),e("): The text which will be inserted into the editor, in place of the prefix")],-1),L=s("code",null,"snippet",-1),A=s("code",null,"text",-1),j=s("code",null,"'myFunction(${1:arg1}, ${2:arg2})'",-1),I={href:"https://github.com/pulsar-edit/snippets",target:"_blank",rel:"noopener noreferrer"},M=o("
  • displayText (optional): A string that will show in the UI for this suggestion. When not set, snippet || text is displayed. This is useful when snippet or text displays too much, and you want to simplify. e.g. {type: 'attribute', snippet: 'class="$0"$1', displayText: 'class'}
  • replacementPrefix (optional): The text immediately preceding the cursor, which will be replaced by the text. If not provided, the prefix passed into getSuggestions will be used.
  • ",2),H=s("code",null,"type",-1),R={href:"https://github.com/atom-community/autocomplete-plus/pull/334",target:"_blank",rel:"noopener noreferrer"},W=s("code",null,"variable",-1),D=s("code",null,"constant",-1),F=s("code",null,"property",-1),N=s("code",null,"value",-1),C=s("code",null,"method",-1),E=s("code",null,"function",-1),U=s("code",null,"class",-1),O=s("code",null,"type",-1),$=s("code",null,"keyword",-1),B=s("code",null,"tag",-1),V=s("code",null,"snippet",-1),z=s("code",null,"import",-1),J=s("code",null,"require",-1),Y=s("code",null,"leftLabel",-1),G={href:"https://github.com/atom-community/autocomplete-plus/pull/334",target:"_blank",rel:"noopener noreferrer"},K=o("
  • leftLabelHTML (optional): Use this instead of leftLabel if you want to use html for the left label.
  • rightLabel (optional): An indicator (e.g. function, variable) denoting the "kind" of suggestion this represents
  • rightLabelHTML (optional): Use this instead of rightLabel if you want to use html for the right label.
  • className (optional): Class name for the suggestion in the suggestion list. Allows you to style your suggestion via CSS, if desired
  • ",4),Q=s("code",null,"iconHTML",-1),X=s("code",null,` iconHTML: ''`,-1),Z={href:"https://github.com/atom-community/autocomplete-plus/pull/334",target:"_blank",rel:"noopener noreferrer"},ss=s("code",null,"type",-1),es=s("li",null,[s("code",null,"description"),e(" (optional): A doc-string summary or short description of the suggestion. When specified, it will be displayed at the bottom of the suggestions list.")],-1),ns=s("li",null,[s("code",null,"descriptionMoreURL"),e(" (optional): A url to the documentation or more information about this suggestion. When specified, a "),s("code",null,"More.."),e(" link will be displayed in the description area. "),s("img",{src:l,alt:"autocomplete-description"})],-1),as=s("li",null,[s("code",null,"characterMatchIndices"),e(` (optional): A list of indexes where the characters in the prefix appear in this suggestion's text. e.g. "foo" in "foo_bar" would be `),s("code",null,"[0, 1, 2]"),e(".")],-1),os=o(`

    Registering Your Provider With autocomplete+

    API 4.0.0

    In your package.json, add:

    "providedServices": {
    +  "autocomplete.provider": {
    +    "versions": {
    +      "4.0.0": "provide"
    +    }
    +  }
    +}
    +

    Then, in your main.coffee (or whatever file you define as your main in package.json i.e. "main": "./lib/your-main" would imply your-main.coffee), add the following:

    For a single provider:

    module.exports =
    +  provide: -> @yourProviderHere
    +

    For multiple providers, just return an array:

    module.exports =
    +  provide: -> [@yourProviderHere, @yourOtherProviderHere]
    +

    Provider Examples

    We've taken to making each provider its own clean repo:

    `,11),ts={href:"https://github.com/pulsar-edit/autocomplete-css",target:"_blank",rel:"noopener noreferrer"},is={href:"https://github.com/pular-edit/autocomplete-html",target:"_blank",rel:"noopener noreferrer"},ps={href:"https://github.com/pulsar-edit/autocomplete-snippets",target:"_blank",rel:"noopener noreferrer"},rs=o(`

    Check out the lib directory in each of these for the code!

    Tips

    Generating a new prefix

    The prefix passed to getSuggestions may not be sufficient for your language. You may need to generate your own prefix. Here is a pattern to use your own prefix:

    provider =
    +  selector: '.source.js, .source.coffee'
    +
    +  getSuggestions: ({editor, bufferPosition}) ->
    +    prefix = @getPrefix(editor, bufferPosition)
    +
    +    new Promise (resolve) ->
    +      suggestion =
    +        text: 'someText'
    +        replacementPrefix: prefix
    +      resolve([suggestion])
    +
    +  getPrefix: (editor, bufferPosition) ->
    +    # Whatever your prefix regex might be
    +    regex = /[\\w0-9_-]+$/
    +
    +    # Get the text for the line up to the triggered buffer position
    +    line = editor.getTextInRange([[bufferPosition.row, 0], bufferPosition])
    +
    +    # Match the regex to the line, and return the match
    +    line.match(regex)?[0] or ''
    +
    `,5);function ls(cs,us){const n=r("ExternalLinkIcon");return i(),p("div",null,[u,s("ul",null,[d,s("li",null,[h,e(" (optional): Function that is called when a suggestion from your provider was inserted into the buffer "),s("ul",null,[s("li",null,[g,e(": the "),s("a",m,[e("TextEditor"),a(n)]),e(" your suggestion was inserted in")]),s("li",null,[k,e(": A "),s("a",v,[e("Point"),a(n)]),e(" where autocomplete was triggered")]),f])])]),b,s("ul",null,[y,_,s("li",null,[w,e(": The "),s("a",x,[e("scope descriptor"),a(n)]),e(" for the current cursor position")]),P,T]),S,s("ul",null,[q,s("li",null,[L,e(" (required; or "),A,e("): A snippet string. This will allow users to tab through function arguments or other options. e.g. "),j,e(". See the "),s("a",I,[e("snippets"),a(n)]),e(" package for more information.")]),M,s("li",null,[H,e(" (optional): The suggestion type. It will be converted into an icon shown against the suggestion. "),s("a",R,[e("screenshot"),a(n)]),e(". Predefined styles exist for "),W,e(", "),D,e(", "),F,e(", "),N,e(", "),C,e(", "),E,e(", "),U,e(", "),O,e(", "),$,e(", "),B,e(", "),V,e(", "),z,e(", "),J,e(". This list represents nearly everything being colorized.")]),s("li",null,[Y,e(" (optional): This is shown before the suggestion. Useful for return values. "),s("a",G,[e("screenshot"),a(n)])]),K,s("li",null,[Q,e(" (optional): If you want complete control over the icon shown against the suggestion. e.g. "),X,e(),s("a",Z,[e("screenshot"),a(n)]),e(". The background color of the icon will still be determined (by default) from the "),ss,e(".")]),es,ns,as]),os,s("ul",null,[s("li",null,[s("a",ts,[e("Autocomplete CSS"),a(n)])]),s("li",null,[s("a",is,[e("Autocomplete HTML"),a(n)])]),s("li",null,[s("a",ps,[e("Autocomplete Snippets"),a(n)])])]),rs])}const hs=t(c,[["render",ls],["__file","provider-api.html.vue"]]);export{hs as default}; diff --git a/assets/provider-api.html.dd1fb570.js b/assets/provider-api.html.dd1fb570.js new file mode 100644 index 0000000000..43fdd273a1 --- /dev/null +++ b/assets/provider-api.html.dd1fb570.js @@ -0,0 +1 @@ +const e=JSON.parse(`{"key":"v-5d8b44fc","path":"/docs/packages/core/autocomplete-plus/provider-api.html","title":"Provider API","lang":"en-us","frontmatter":{"lang":"en-us","title":"Provider API"},"excerpt":"","headers":[{"level":2,"title":"Defining A Provider","slug":"defining-a-provider","link":"#defining-a-provider","children":[]},{"level":2,"title":"Support For Asynchronous Request Handling","slug":"support-for-asynchronous-request-handling","link":"#support-for-asynchronous-request-handling","children":[]},{"level":2,"title":"The Suggestion Request's Options Object","slug":"the-suggestion-request-s-options-object","link":"#the-suggestion-request-s-options-object","children":[]},{"level":2,"title":"Suggestions","slug":"suggestions","link":"#suggestions","children":[]},{"level":2,"title":"Registering Your Provider With autocomplete+","slug":"registering-your-provider-with-autocomplete","link":"#registering-your-provider-with-autocomplete","children":[{"level":3,"title":"API 4.0.0","slug":"api-4-0-0","link":"#api-4-0-0","children":[]}]},{"level":2,"title":"Provider Examples","slug":"provider-examples","link":"#provider-examples","children":[]},{"level":2,"title":"Tips","slug":"tips","link":"#tips","children":[{"level":3,"title":"Generating a new prefix","slug":"generating-a-new-prefix","link":"#generating-a-new-prefix","children":[]}]}],"git":{"updatedTime":1664399092000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":2}]},"readingTime":{"minutes":4.68,"words":1405},"filePathRelative":"docs/packages/core/autocomplete-plus/provider-api.md"}`);export{e as data}; diff --git a/assets/publishing.html.2e08f39b.js b/assets/publishing.html.2e08f39b.js new file mode 100644 index 0000000000..b88fe68a0a --- /dev/null +++ b/assets/publishing.html.2e08f39b.js @@ -0,0 +1,5 @@ +import{_ as r,o as l,c,a as o,b as e,d as a,w as d,e as u,f as n,r as t}from"./app.0e1565ce.js";const p={},h=o("h2",{id:"publishing",tabindex:"-1"},[o("a",{class:"header-anchor",href:"#publishing","aria-hidden":"true"},"#"),e(" Publishing")],-1),g=o("code",null,"ppm",-1),m=o("code",null,"pulsar",-1),b=o("code",null,"-p",-1),_=o("code",null,"--package",-1),f=o("code",null,"pulsar -p",-1),k=o("p",null,[e("See more in "),o("a",{href:"#using-ppm"},"Using PPM"),e(".")],-1),v=o("h3",{id:"prepare-your-package",tabindex:"-1"},[o("a",{class:"header-anchor",href:"#prepare-your-package","aria-hidden":"true"},"#"),e(" Prepare Your Package")],-1),y=o("p",null,"There are a few things you should double check before publishing:",-1),w=n("
  • Your package.json file has name, description, and repository fields.
  • Your package.json name is URL Safe, as in it's not an emoji or special character.
  • Your package.json file has a version field with a value of "0.0.0".
  • ",3),P=o("code",null,"package.json",-1),x=o("code",null,"version",-1),R={href:"https://semver.org/spec/v2.0.0.html",target:"_blank",rel:"noopener noreferrer"},Y=n("
  • Your package.json file has an engines field that contains an entry for atom such as: "engines": {"atom": ">=1.0.0 <2.0.0"}.
  • Your package has a README.md file at the root.
  • Your repository URL in the package.json file is the same as the URL of your repository.
  • ",3),j={href:"https://github.com",target:"_blank",rel:"noopener noreferrer"},q={href:"https://help.github.com/articles/importing-a-git-repository-using-the-command-line/",target:"_blank",rel:"noopener noreferrer"},T=o("h3",{id:"publish-your-package",tabindex:"-1"},[o("a",{class:"header-anchor",href:"#publish-your-package","aria-hidden":"true"},"#"),e(" Publish Your Package")],-1),I={href:"https://web.pulsar-edit.dev/packages",target:"_blank",rel:"noopener noreferrer"},L=o("code",null,"https://web.pulsar-edit.dev/packages/your-package-name",-1),N=o("p",null,[e("Now let's review what the "),o("code",null,"pulsar -p publish"),e(" command does:")],-1),C=o("li",null,"Registers the package name on Pulsar Package Repository if it is being published for the first time.",-1),G=o("li",null,[e("Updates the "),o("code",null,"version"),e(" field in the "),o("code",null,"package.json"),e(" file and commits it.")],-1),H={href:"https://git-scm.com/book/en/Git-Basics-Tagging",target:"_blank",rel:"noopener noreferrer"},U=o("li",null,"Pushes the tag and current branch up to GitHub.",-1),V=o("li",null,"Updates Pulsar Package Repository with the new version being published.",-1),A=n(`

    Now run the following commands to publish your package:

    $ cd path-to-your-package
    +$ pulsar -p publish minor
    +
    `,2),B=o("code",null,"pulsar -p publish",-1),E={href:"https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/",target:"_blank",rel:"noopener noreferrer"},S={href:"https://en.wikipedia.org/wiki/Keychain_(software)",target:"_blank",rel:"noopener noreferrer"},M=n(`

    Your package is now published and available on Pulsar Package Repository. Head on over to https://web.pulsar-edit.dev/packages/your-package-name to see your package's page.

    With pulsar -p publish, you can bump the version and publish by using

    $ pulsar -p publish <version-type>
    +

    where version-type can be major, minor and patch.

    • MAJOR version when you make incompatible API changes
    • MINOR version when you add functionality in a backwards compatible manner
    • PATCH version when you make backwards compatible bug fixes

    i.e. to bump a package from v1.0.0 to v1.1.0:

    $ pulsar -p publish minor
    +
    `,7),O={href:"https://semver.org/",target:"_blank",rel:"noopener noreferrer"},$=o("p",null,[e("You can also run "),o("code",null,"pulsar -p help publish"),e(" to see all the available options and "),o("code",null,"pulsar -p help"),e(" to see all the other available commands.")],-1);function D(F,J){const i=t("RouterLink"),s=t("ExternalLinkIcon");return l(),c("div",null,[h,o("p",null,[e("Pulsar bundles a command line utility called "),g,e(" which we first used back in "),a(i,{to:"/docs/launch-manual/sections/using-pulsar/#command-line"},{default:d(()=>[e("Command Line")]),_:1}),e(" to search for and install packages via the command line. This is invoked by using the "),m,e(" command with the "),b,e(" or "),_,e(" option. The "),f,e(" command can also be used to publish Pulsar packages to the public registry and update them.")]),k,v,y,o("ul",null,[w,o("li",null,[e("Your "),P,e(),x,e(" field is "),o("a",R,[e("Semver V2"),a(s)]),e(" compliant.")]),Y,o("li",null,[e("Your package is in a Git repository that has been pushed to "),o("a",j,[e("GitHub"),a(s)]),e(". Follow "),o("a",q,[e("this guide"),a(s)]),e(" if your package isn't already on GitHub.")])]),T,o("p",null,[e("Before you publish a package it is a good idea to check ahead of time if a package with the same name has already been published to "),o("a",I,[e("the Pulsar Package Repository"),a(s)]),e(". You can do that by visiting "),L,e(" to see if the package already exists. If it does, update your package's name to something that is available before proceeding.")]),N,o("ol",null,[C,G,o("li",null,[e("Creates a new "),o("a",H,[e("Git tag"),a(s)]),e(" for the version being published.")]),U,V]),A,u(" TODO: Rewrite this Section once Authentication Information is Public "),o("p",null,[e("If this is the first package you are publishing, the "),B,e(" command may prompt you for your GitHub username and password. If you have two-factor authentication enabled, use a "),o("a",E,[e("personal access token"),a(s)]),e(" in lieu of a password. This is required to publish and you only need to enter this information the first time you publish. The credentials are stored securely in your "),o("a",S,[e("keychain"),a(s)]),e(" once you login.")]),M,o("p",null,[e("Check out "),o("a",O,[e("semantic versioning"),a(s)]),e(" to learn more about best practices for versioning your package releases.")]),$])}const W=r(p,[["render",D],["__file","publishing.html.vue"]]);export{W as default}; diff --git a/assets/publishing.html.6359d45b.js b/assets/publishing.html.6359d45b.js new file mode 100644 index 0000000000..df79a17f43 --- /dev/null +++ b/assets/publishing.html.6359d45b.js @@ -0,0 +1,4 @@ +import{_ as r,o as c,c as l,a as o,b as e,d as a,w as d,f as i,r as n}from"./app.0e1565ce.js";const h={},u=o("h3",{id:"publishing",tabindex:"-1"},[o("a",{class:"header-anchor",href:"#publishing","aria-hidden":"true"},"#"),e(" Publishing")],-1),p=o("code",null,"apm",-1),m=o("code",null,"apm",-1),g=o("h4",{id:"prepare-your-package",tabindex:"-1"},[o("a",{class:"header-anchor",href:"#prepare-your-package","aria-hidden":"true"},"#"),e(" Prepare Your Package")],-1),b=o("p",null,"There are a few things you should double check before publishing:",-1),_=i("
  • Your package.json file has name, description, and repository fields.
  • Your package.json file has a version field with a value of "0.0.0".
  • Your package.json file has an engines field that contains an entry for Atom such as: "engines": {"atom": ">=1.0.0 <2.0.0"}.
  • Your package has a README.md file at the root.
  • Your repository URL in the package.json file is the same as the URL of your repository.
  • ",5),f={href:"https://github.com",target:"_blank",rel:"noopener noreferrer"},k={href:"https://help.github.com/articles/importing-a-git-repository-using-the-command-line/",target:"_blank",rel:"noopener noreferrer"},y=o("h4",{id:"publish-your-package",tabindex:"-1"},[o("a",{class:"header-anchor",href:"#publish-your-package","aria-hidden":"true"},"#"),e(" Publish Your Package")],-1),v={href:"https://atom.io/packages",target:"_blank",rel:"noopener noreferrer"},w=o("code",null,"https://atom.io/packages/your-package-name",-1),x=o("p",null,[e("Now let's review what the "),o("code",null,"apm publish"),e(" command does:")],-1),Y=o("li",null,"Registers the package name on atom.io if it is being published for the first time.",-1),G=o("li",null,[e("Updates the "),o("code",null,"version"),e(" field in the "),o("code",null,"package.json"),e(" file and commits it.")],-1),q={href:"https://git-scm.com/book/en/Git-Basics-Tagging",target:"_blank",rel:"noopener noreferrer"},T=o("li",null,"Pushes the tag and current branch up to GitHub.",-1),j=o("li",null,"Updates atom.io with the new version being published.",-1),L=i(`

    Now run the following commands to publish your package:

    $ cd path-to-your-package
    +$ apm publish minor
    +
    `,2),U=o("code",null,"apm publish",-1),N={href:"https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/",target:"_blank",rel:"noopener noreferrer"},P={href:"https://en.wikipedia.org/wiki/Keychain_(Apple)",target:"_blank",rel:"noopener noreferrer"},R=i(`

    Your package is now published and available on atom.io. Head on over to https://atom.io/packages/your-package-name to see your package's page.

    With apm publish, you can bump the version and publish by using

    $ apm publish <em>version-type</em>
    +

    where version-type can be major, minor and patch.

    The major option to the publish command tells apm to increment the first number of the version before publishing so the published version will be 1.0.0 and the Git tag created will be v1.0.0.

    The minor option to the publish command tells apm to increment the second number of the version before publishing so the published version will be 0.1.0 and the Git tag created will be v0.1.0.

    The patch option to the publish command tells apm to increment the third number of the version before publishing so the published version will be 0.0.1 and the Git tag created will be v0.0.1.

    `,7),A=o("code",null,"major",-1),B=o("code",null,"minor",-1),C=o("code",null,"patch",-1),E={href:"https://semver.org",target:"_blank",rel:"noopener noreferrer"},H=o("p",null,[e("You can also run "),o("code",null,"apm help publish"),e(" to see all the available options and "),o("code",null,"apm help"),e(" to see all the other available commands.")],-1);function I(V,$){const s=n("RouterLink"),t=n("ExternalLinkIcon");return c(),l("div",null,[u,o("p",null,[e("Atom bundles a command line utility called "),p,e(" which we first used back in "),a(s,{to:"/using-atom/sections/atom-packages/#command-line"},{default:d(()=>[e("Command Line")]),_:1}),e(" to search for and install packages via the command line. The "),m,e(" command can also be used to publish Atom packages to the public registry and update them.")]),g,b,o("ul",null,[_,o("li",null,[e("Your package is in a Git repository that has been pushed to "),o("a",f,[e("GitHub"),a(t)]),e(". Follow "),o("a",k,[e("this guide"),a(t)]),e(" if your package isn't already on GitHub.")])]),y,o("p",null,[e("Before you publish a package it is a good idea to check ahead of time if a package with the same name has already been published to "),o("a",v,[e("the atom.io package registry"),a(t)]),e(". You can do that by visiting "),w,e(" to see if the package already exists. If it does, update your package's name to something that is available before proceeding.")]),x,o("ol",null,[Y,G,o("li",null,[e("Creates a new "),o("a",q,[e("Git tag"),a(t)]),e(" for the version being published.")]),T,j]),L,o("p",null,[e("If this is the first package you are publishing, the "),U,e(" command may prompt you for your GitHub username and password. If you have two-factor authentication enabled, use a "),o("a",N,[e("personal access token"),a(t)]),e(" in lieu of a password. This is required to publish and you only need to enter this information the first time you publish. The credentials are stored securely in your "),o("a",P,[e("keychain"),a(t)]),e(" once you login.")]),R,o("p",null,[e("Use "),A,e(" when you make a change that breaks backwards compatibility, like changing defaults or removing features. Use "),B,e(" when adding new functionality or options, but without breaking backwards compatibility. Use "),C,e(" when you've changed the implementation of existing features, but without changing the behaviour or options of your package. Check out "),o("a",E,[e("semantic versioning"),a(t)]),e(" to learn more about best practices for versioning your package releases.")]),H])}const F=r(h,[["render",I],["__file","publishing.html.vue"]]);export{F as default}; diff --git a/assets/publishing.html.68438e12.js b/assets/publishing.html.68438e12.js new file mode 100644 index 0000000000..d067e9072d --- /dev/null +++ b/assets/publishing.html.68438e12.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-3ce2332a","path":"/docs/launch-manual/sections/core-hacking/sections/publishing.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Publishing","slug":"publishing","link":"#publishing","children":[{"level":3,"title":"Prepare Your Package","slug":"prepare-your-package","link":"#prepare-your-package","children":[]},{"level":3,"title":"Publish Your Package","slug":"publish-your-package","link":"#publish-your-package","children":[]}]}],"git":{"updatedTime":1670864609000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":5}]},"readingTime":{"minutes":2.13,"words":639},"filePathRelative":"docs/launch-manual/sections/core-hacking/sections/publishing.md"}');export{e as data}; diff --git a/assets/publishing.html.76babe74.js b/assets/publishing.html.76babe74.js new file mode 100644 index 0000000000..2d68ac266b --- /dev/null +++ b/assets/publishing.html.76babe74.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-5d0a102e","path":"/docs/atom-archive/hacking-atom/sections/publishing.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"Publishing","slug":"publishing","link":"#publishing","children":[]}],"git":{"updatedTime":1664050274000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":2}]},"readingTime":{"minutes":2.26,"words":678},"filePathRelative":"docs/atom-archive/hacking-atom/sections/publishing.md"}');export{e as data}; diff --git a/assets/pulsar-basics.html.47a95a49.js b/assets/pulsar-basics.html.47a95a49.js new file mode 100644 index 0000000000..0c8c860e60 --- /dev/null +++ b/assets/pulsar-basics.html.47a95a49.js @@ -0,0 +1,18 @@ +import{d as p,_ as m,a as f,b as g,c as y,e as b,f as w,g as u}from"./finder.04f876a5.js";import{_ as k,o as v,c as _,a as e,b as t,d as o,w as n,f as l,r as c}from"./app.0e1565ce.js";const P={},S=l('

    Pulsar Basics

    Now that Pulsar is installed on your system, let's fire it up, configure it and get acquainted with the editor.

    When you launch Pulsar for the first time, you should get a screen that looks like this:

    Pulsar's welcome screen

    This is the Pulsar welcome screen and gives you a pretty good starting point for how to get started with the editor.

    Terminology

    ',6),C=l('

    Command Palette

    In that welcome screen, we are introduced to probably the most important command in Pulsar, the Command Palette. If you press Cmd+Shift+PCtrl+Shift+P while focused in an editor pane, the command palette will pop up.

    Note:

    Throughout the book, we will use shortcut keybindings like LNX: Ctrl+Shift+P - MAC: Cmd+Shift+P - WIN: Ctrl+Shift+P to demonstrate how to run a command and tabbed sections where necessary where instructions for different platforms may differ.

    If you have customized your Pulsar keymap, you can always see the keybinding you have mapped in the Command Palette or the Keybindings tab in the Settings View.

    This search-driven menu can do just about any major task that is possible in Pulsar. Instead of clicking around all the application menus to look for something, you can press LNX/WIN: Ctrl+Shift+P - MAC: Cmd+Shift+P and search for the command.

    Command Palette

    Not only can you see and quickly search through thousands of possible commands, but you can also see if there is a keybinding associated with it. This is great because it means you can guess your way to doing interesting things while also learning the shortcut key strokes for doing it.

    For the rest of the book, we will try to be clear as to the text you can search for in the Command Palette in addition to the keybinding for different commands.

    Settings and Preferences

    Pulsar has a number of settings and preferences you can modify in the Settings View.

    Settings View

    This includes things like changing the theme, specifying how to handle wrapping, font settings, tab size, scroll speed and much more. You can also use this screen to install new packages and themes, which we'll cover in Pulsar Packages.

    To open the Settings View, you can:

    • Use the LNX: Edit > Preferences - MAC: Pulsar > Preferences - WIN: File > Settings menu item in the menu bar
    • Search for settings-view:open in the Command Palette
    • Use the LNX/WIN: Ctrl+, - MAC: Cmd+, keybinding.

    Changing the Theme

    The Settings View also lets you change the themes for Pulsar. Pulsar ships with 4 different UI themes, dark and light variants of the Pulsar and One theme, as well as 8 different syntax themes. You can modify the active theme by clicking on the Themes tab in the sidebar of the Settings View, or you can install new themes by clicking the Install tab.

    Changing the theme from the Settings View

    The UI themes control the style of UI elements like the tabs and the tree view, while the syntax themes control the syntax highlighting of text you load into the editor. To change the syntax or UI theme, simply pick something different in the appropriate dropdown list.

    ',17),T={href:"https://web.pulsar-edit.dev",target:"_blank",rel:"noopener noreferrer"},F=e("h4",{id:"soft-wrap",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#soft-wrap","aria-hidden":"true"},"#"),t(" Soft Wrap")],-1),x=e("p",null,"You can use the Settings View to specify your whitespace and wrapping preferences.",-1),z=e("p",null,[e("img",{src:p,alt:"Whitespace and wrapping preferences settings"})],-1),A=e("p",null,[t('Enabling "Soft Tabs" will insert spaces instead of actual tab characters when you press the '),e("kbd",null,"Tab"),t(' key and the "Tab Length" setting specifies how many spaces to insert when you do so, or how many spaces are used to represent a tab if "Soft Tabs" is disabled.')],-1),I=e("p",null,'The "Soft Wrap" option will wrap lines that are too long to fit in your current window. If soft wrapping is disabled, the lines will simply run off the side of the screen and you will have to scroll the window to see the rest of the content. If "Soft Wrap At Preferred Line Length" is toggled, the lines will wrap at 80 characters instead of the end of the screen. You can also change the default line length to a value other than 80 on this screen.',-1),O=l('

    Opening, Modifying, and Saving Files

    Now that your editor is looking and acting how you want, let's start opening up and editing files. This is a text editor after all, right?

    Opening a File

    There are several ways to open a file in Pulsar. You can do it by choosing File > Open from the menu bar or by pressing

    LNX/WIN: Ctrl+O - MAC: Cmd+O

    to choose a file from the standard dialog.

    Open file by dialog

    This is useful for opening a file that is not contained in the project you're currently in (more on that next), or if you're starting from a new window for some reason.

    Another way to open a file in Pulsar is from the command line using the pulsar command.

    ',9),j={class:"custom-container note"},N=e("p",{class:"custom-container-title"},"Note",-1),Y=e("p",null,[e("strong",null,"Install Shell Commands on macOS")],-1),V=e("p",null,[t('The Pulsar menu has an item named "Install Shell Commands" which installs the '),e("code",null,"pulsar"),t(" and "),e("code",null,"ppm"),t(" commands if Pulsar wasn't able to install them itself on a macOS system.")],-1),W={href:"https://github.com/pulsar-edit/pulsar/issues/187",target:"_blank",rel:"noopener noreferrer"},L=l(`

    You can run the pulsar command with one or more file paths to open up those files in Pulsar.

    $ pulsar --help
    +> Pulsar Editor v1.100.0
    +
    +> Usage: pulsar [options] [path ...]
    +
    +> One or more paths to files or folders may be specified. If there is an
    +> existing Pulsar window that contains all of the given folders, the paths
    +> will be opened in that window. Otherwise, they will be opened in a new
    +> window.
    +
    +> ...
    +

    This is a great tool if you're used to the terminal or you work from the terminal a lot. Just fire off pulsar [files] and you're ready to start editing. You can even open a file at a certain line (and optionally column) so the cursor will be positioned exactly where you want. For example, you may search some keyword in a repository to find the line you want to edit:

    $ git grep -n 'Opening a File$'
    +getting-started/sections/pulsar-basics.md:130:##### Opening a File
    +

    and then jump to the beginning of that line by appending a colon and the line number to the file path:

    $ pulsar getting-started/sections/pulsar-basics.md:130
    +

    Sometimes you may want the cursor to jump to the exact column position of the searched keyword. Just append another colon plus the column number:

    $ git grep -n --column 'Windows Explorer'
    +getting-started/sections/pulsar-basics.md.md:150:722
    +$ pulsar getting-started/sections/pulsar-basics.md:150:722
    +

    Editing and Saving a File

    `,9),q={href:"https://web.pulsar-edit.dev",target:"_blank",rel:"noopener noreferrer"},B=e("p",null,[t("To save a file you can choose "),e("em",null,"File > Save"),t(" from the menu bar or "),e("kbd",null,"Ctrl+S"),t(" to save the file. If you choose "),e("em",null,"File > Save As"),t(" or press "),e("kbd",null,"Ctrl+Shift+S"),t(" then you can save the current content in your editor under a different file name. Finally,you can choose "),e("em",null,"File > Save All"),t(" to save all the open files in Pulsar.")],-1),M=e("p",null,[t("To save a file you can choose "),e("em",null,"File > Save"),t(" from the menu bar or "),e("kbd",null,"Cmd+S"),t(" to save the file. If you choose "),e("em",null,"File > Save As"),t(" or press "),e("kbd",null,"Cmd+Shift+S"),t(" then you can save the current content in your editor under a different file name. Finally,you can choose "),e("em",null,"File > Save All"),t(" or press "),e("kbd",null,"Alt+Cmd+S"),t(" to save all the open files in Pulsar.")],-1),E=e("p",null,[e("kbd",null,"Ctrl+S"),t(" to save the file. If you choose "),e("em",null,"File > Save As"),t(" or press "),e("kbd",null,"Ctrl+Shift+S"),t(" then you can save the current content in your editor under a different file name. Finally,you can choose "),e("em",null,"File > Save All"),t(" to save all the open files in Pulsar.")],-1),U=e("h3",{id:"opening-directories",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#opening-directories","aria-hidden":"true"},"#"),t(" Opening Directories")],-1),G=e("p",null,[t("Pulsar doesn't just work with single files though; you will most likely spend most of your time working on projects with multiple files. To open a directory, choose the menu item "),e("em",null,"File > Open Folder"),t(" and select a directory from the dialog. You can also add more than one directory to your current Pulsar window, by choosing "),e("em",null,"File > Add Project Folder"),t(" from the menu bar or pressing "),e("kbd",null,"Ctrl+Shift+A"),t(".")],-1),$=e("p",null,[t("Pulsar doesn't just work with single files though; you will most likely spend most of your time working on projects with multiple files. To open a directory, choose the menu item "),e("em",null,"File > Open"),t(" and select a directory from the dialog. You can also add more than one directory to your current Pulsar window, by choosing "),e("em",null,"File > Add Project Folder"),t(" from the menu bar or pressing "),e("kbd",null,"Cmd+Shift+O"),t(".")],-1),R=e("p",null,[t("Pulsar doesn't just work with single files though; you will most likely spend most of your time working on projects with multiple files. To open a directory, choose the menu item "),e("em",null,"File > Open Folder"),t(" and select a directory from the dialog. You can also add more than one directory to your current Pulsar window, by choosing "),e("em",null,"File > Add Project Folder"),t(" from the menu bar or pressing "),e("kbd",null,"Ctrl+Shift+A"),t(".")],-1),X=l('

    You can open any number of directories from the command line by passing their paths to the pulsar command line tool. For example, you could run the command pulsar ./hopes ./dreams to open both the hopes and the dreams directories at the same time.

    When you open Pulsar with one or more directories, you will automatically get a Tree View on the side of your window.

    Tree View in an open project

    The Tree View allows you to explore and modify the file and directory structure of your project. You can open, rename, delete and create new files from this view.

    ',4),D=e("p",null,[t("You can also hide and show it with "),e("kbd",null,"Ctrl+\\"),t(" or the "),e("code",null,"tree-view: toggle"),t(" command from the Command Palette, and "),e("kbd",null,"Alt+\\"),t(" will focus it. When the Tree view has focus you can press "),e("kbd",null,"A"),t(", "),e("kbd",null,"M"),t(", or "),e("kbd",null,"Delete"),t(" to add, move or delete files and folders. You can also right-click on a file or folder in the Tree view to see many of the various options, including all of these plus showing the file in your native filesystem or copying the file path to the clipboard.")],-1),J=e("p",null,[t("You can also hide and show it with "),e("kbd",null,"Cmd+\\"),t(" or the "),e("code",null,"tree-view: toggle"),t(" command from the Command Palette, and "),e("kbd",null,"Ctrl+0"),t(" will focus it. When the Tree view has focus you can press "),e("kbd",null,"A"),t(", "),e("kbd",null,"M"),t(", or "),e("kbd",null,"Delete"),t(" to add, move or delete files and folders. You can also right-click on a file or folder in the Tree view to see many of the various options, including all of these plus showing the file in Finder or copying the file path to the clipboard.")],-1),H=e("p",null,[t("You can also hide and show it with "),e("kbd",null,"Ctrl+\\"),t(" or the "),e("code",null,"tree-view: toggle"),t(" command from the Command Palette, and "),e("kbd",null,"Alt+\\"),t(" will focus it. When the Tree view has focus you can press "),e("kbd",null,"A"),t(", "),e("kbd",null,"M"),t(", or "),e("kbd",null,"Delete"),t(" to add, move or delete files and folders. You can also right-click on a file or folder in the Tree view to see many of the various options, including all of these plus showing the file in Windows Explorer or copying the file path to the clipboard.")],-1),K=e("div",{class:"custom-container note"},[e("p",{class:"custom-container-title"},"Note"),e("p",null,[e("strong",null,"Pulsar Packages")]),e("p",null,"Like many parts of Pulsar, the Tree View is not built directly into the editor, but is its own standalone package that is shipped with Pulsar by default. Packages that are bundled with Pulsar are referred to as Core packages. Ones that aren't bundled with Pulsar are referred to as Community packages."),e("p",null,"You can find the source code to the Tree View on GitHub at https://github.com/pulsar-edit/tree-view."),e("p",null,"This is one of the interesting things about Pulsar. Many of its core features are actually just packages implemented the same way you would implement any other functionality. This means that if you don't like the Tree View for example, you could write your own implementation of that functionality and replace it entirely.")],-1),Q=e("h4",{id:"opening-a-file-in-a-project",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#opening-a-file-in-a-project","aria-hidden":"true"},"#"),t(" Opening a File in a Project")],-1),Z=e("p",null,"Once you have a project open in Pulsar, you can easily find and open any file within that project.",-1),ee=e("p",null,[t("If you press "),e("kbd",null,"Ctrl+T"),t(" or "),e("kbd",null,"Ctrl+P"),t(", the Fuzzy Finder will pop up. This will let you quickly search for any file in your project by typing parts of the path.")],-1),te=e("p",null,[e("img",{src:u,alt:"Opening files with the Fuzzy Finder",title:"Opening files with the Fuzzy Finder"})],-1),ne=e("p",null,[t("You can also search through only the files currently opened (rather than every file in your project) with "),e("kbd",null,"Ctrl+B"),t('. This searches through your "buffers" or open files. You can also limit this fuzzy search with '),e("kbd",null,"Ctrl+Shift+B"),t(", which searches only through the files which are new or have been modified since your last Git commit.")],-1),oe=e("p",null,[t("If you press "),e("kbd",null,"Cmd+T"),t(" or "),e("kbd",null,"Cmd+P"),t(", the Fuzzy Finder will pop up. This will let you quickly search for any file in your project by typing parts of the path.")],-1),ie=e("p",null,[e("img",{src:u,alt:"Opening files with the Fuzzy Finder",title:"Opening files with the Fuzzy Finder"})],-1),se=e("p",null,[t("You can also search through only the files currently opened (rather than every file in your project) with "),e("kbd",null,"Cmd+B"),t('. This searches through your "buffers" or open files. You can also limit this fuzzy search with '),e("kbd",null,"Cmd+Shift+B"),t(", which searches only through the files which are new or have been modified since your last Git commit.")],-1),ae=e("p",null,[t("If you press "),e("kbd",null,"Ctrl+T"),t(" or "),e("kbd",null,"Ctrl+P"),t(", the Fuzzy Finder will pop up. This will let you quickly search for any file in your project by typing parts of the path.")],-1),le=e("p",null,[e("img",{src:u,alt:"Opening files with the Fuzzy Finder",title:"Opening files with the Fuzzy Finder"})],-1),re=e("p",null,[t("You can also search through only the files currently opened (rather than every file in your project) with "),e("kbd",null,"Ctrl+B"),t('. This searches through your "buffers" or open files. You can also limit this fuzzy search with '),e("kbd",null,"Ctrl+Shift+B"),t(", which searches only through the files which are new or have been modified since your last Git commit.")],-1),de=e("code",null,"core.ignoredNames",-1),he=e("code",null,"fuzzy-finder.ignoredNames",-1),ce=e("code",null,"core.excludeVCSIgnoredPaths",-1),ue={href:"https://git-scm.com/docs/gitignore",target:"_blank",rel:"noopener noreferrer"},pe=e("code",null,".gitignore",-1),me=e("code",null,"core.ignoredNames",-1),fe=e("code",null,"fuzzy-finder.ignoredNames",-1),ge={href:"https://github.com/isaacs/minimatch",target:"_blank",rel:"noopener noreferrer"},ye=l('

    Tip

    Configuration Setting Notation

    Sometimes you'll see us refer to configuration settings all spelled out like "Ignored Names in Core Settings". Other times you'll see us use the shorthand name like core.ignoredNames. Both of these refer to the same thing. The shorthand is the package name, then a dot ., followed by the "camel-cased" name of the setting.

    If you have a phrase you want to camel-case, follow these steps:

    1. Lowercase the first word
    2. Capitalize the first letter in all other words
    3. Remove the spaces

    So "Ignored Names" becomes "ignoredNames".

    ',1);function be(we,ke){const r=c("RouterLink"),d=c("ExternalLinkIcon"),h=c("Tabs");return v(),_("div",null,[S,e("p",null,[t("You can find definitions for all the various terms that we use throughout the manual in our "),o(r,{to:"/docs/launch-manual/sections/getting-started/resources/glossary/"},{default:n(()=>[t("Glossary")]),_:1}),t(".")]),C,e("p",null,[t("There are also dozens of themes on the "),e("a",T,[t("Pulsar Package Repository"),o(d)]),t(" that you can choose from if you want something different. We will cover customizing a theme in "),o(r,{to:"/docs/launch-manual/sections/using-pulsar/#basic-customization"},{default:n(()=>[t("Style Tweaks")]),_:1}),t(" and creating your own theme in "),o(r,{to:"/docs/launch-manual/sections/core-hacking/#creating-a-theme"},{default:n(()=>[t("Creating a Theme")]),_:1}),t(".")]),F,x,z,A,I,e("p",null,[t("In "),o(r,{to:"/docs/launch-manual/sections/using-pulsar/#basic-customization"},{default:n(()=>[t("Basic Customization")]),_:1}),t(" we will see how to set different wrap preferences for different types of files (for example, if you want to wrap Markdown files but not other files).")]),O,e("div",j,[N,Y,V,e("p",null,[t("On Linux commands are installed automatically as a part of Pulsar's installation process. Windows requires the path to be "),e("a",W,[t("exposed manually"),o(d)]),t(" by the user at this time.")])]),L,e("p",null,[t("Editing a file is pretty straightforward. You can click around and scroll with your mouse and type to change the content. There is no special editing mode or key commands. If you prefer editors with modes or more complex key commands, you should take a look at the "),e("a",q,[t("Pulsar Package Repository"),o(d)]),t(". There are a lot of packages that emulate popular styles.")]),o(h,{id:"172",data:[{title:"Linux"},{title:"macOS"},{title:"Windows"}],"tab-id":"getting-started"},{tab0:n(({title:i,value:s,isActive:a})=>[B]),tab1:n(({title:i,value:s,isActive:a})=>[M]),tab2:n(({title:i,value:s,isActive:a})=>[E]),_:1}),U,o(h,{id:"192",data:[{title:"Linux"},{title:"macOS"},{title:"Windows"}],"tab-id":"getting-started"},{tab0:n(({title:i,value:s,isActive:a})=>[G]),tab1:n(({title:i,value:s,isActive:a})=>[$]),tab2:n(({title:i,value:s,isActive:a})=>[R]),_:1}),X,o(h,{id:"221",data:[{title:"Linux"},{title:"macOS"},{title:"Windows"}],"tab-id":"getting-started"},{tab0:n(({title:i,value:s,isActive:a})=>[D]),tab1:n(({title:i,value:s,isActive:a})=>[J]),tab2:n(({title:i,value:s,isActive:a})=>[H]),_:1}),K,Q,Z,o(h,{id:"258",data:[{title:"Linux"},{title:"macOS"},{title:"Windows"}],"tab-id":"getting-started"},{tab0:n(({title:i,value:s,isActive:a})=>[ee,te,ne]),tab1:n(({title:i,value:s,isActive:a})=>[oe,ie,se]),tab2:n(({title:i,value:s,isActive:a})=>[ae,le,re]),_:1}),e("p",null,[t("The fuzzy finder uses the "),de,t(", "),he,t(" and "),ce,t(" configuration settings to filter out files and folders that will not be shown. If you have a project with tons of files you don't want it to search through, you can add patterns or paths to either of these config settings or your "),e("a",ue,[t("standard "),pe,t(" files"),o(d)]),t(". We'll learn more about config settings in "),o(r,{to:"/docs/launch-manual/sections/using-pulsar/#global-configuration-settings"},{default:n(()=>[t("Global Configuration Settings")]),_:1}),t(", but for now you can easily set these in the Settings View under Core Settings.")]),e("p",null,[t("Both "),me,t(" and "),fe,t(" are interpreted as glob patterns as implemented by the "),e("a",ge,[t("minimatch Node module"),o(d)]),t(".")]),ye])}const Pe=k(P,[["render",be],["__file","pulsar-basics.html.vue"]]);export{Pe as default}; diff --git a/assets/pulsar-basics.html.c9db0718.js b/assets/pulsar-basics.html.c9db0718.js new file mode 100644 index 0000000000..995530ee20 --- /dev/null +++ b/assets/pulsar-basics.html.c9db0718.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-6660d212","path":"/docs/launch-manual/sections/getting-started/sections/pulsar-basics.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Pulsar Basics","slug":"pulsar-basics","link":"#pulsar-basics","children":[{"level":3,"title":"Terminology","slug":"terminology","link":"#terminology","children":[]},{"level":3,"title":"Command Palette","slug":"command-palette","link":"#command-palette","children":[]},{"level":3,"title":"Settings and Preferences","slug":"settings-and-preferences","link":"#settings-and-preferences","children":[]},{"level":3,"title":"Opening, Modifying, and Saving Files","slug":"opening-modifying-and-saving-files","link":"#opening-modifying-and-saving-files","children":[]},{"level":3,"title":"Opening Directories","slug":"opening-directories","link":"#opening-directories","children":[]}]}],"git":{"updatedTime":1683109777000,"contributors":[{"name":"rub\xE9n","email":"66261839+alamo10@users.noreply.github.com","commits":4},{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":9.87,"words":2960},"filePathRelative":"docs/launch-manual/sections/getting-started/sections/pulsar-basics.md"}');export{e as data}; diff --git a/assets/pulsar-lemmy.7d5967b0.png b/assets/pulsar-lemmy.7d5967b0.png new file mode 100644 index 0000000000..f9f86396c7 Binary files /dev/null and b/assets/pulsar-lemmy.7d5967b0.png differ diff --git a/assets/pulsar-packages.html.22e50009.js b/assets/pulsar-packages.html.22e50009.js new file mode 100644 index 0000000000..789a825b94 --- /dev/null +++ b/assets/pulsar-packages.html.22e50009.js @@ -0,0 +1,21 @@ +import{_ as i,a as r,b as p,c}from"./unity-theme.516a5ae9.js";import{_ as u,o as h,c as d,a,b as e,d as s,w as g,e as m,f as t,r as o}from"./app.0e1565ce.js";const k={},b=a("h2",{id:"pulsar-packages",tabindex:"-1"},[a("a",{class:"header-anchor",href:"#pulsar-packages","aria-hidden":"true"},"#"),e(" Pulsar Packages")],-1),f={href:"https://github.com/pulsar-edit/tree-view",target:"_blank",rel:"noopener noreferrer"},v={href:"https://github.com/pulsar-edit/settings-view",target:"_blank",rel:"noopener noreferrer"},_={href:"https://github.com/pulsar-edit/welcome",target:"_blank",rel:"noopener noreferrer"},w={href:"https://github.com/pulsar-edit/spell-check",target:"_blank",rel:"noopener noreferrer"},y={href:"https://github.com/pulsar-edit/one-dark-ui",target:"_blank",rel:"noopener noreferrer"},P={href:"https://github.com/pulsar-edit/fuzzy-finder",target:"_blank",rel:"noopener noreferrer"},x=a("p",null,"This means that packages can be incredibly powerful and can change everything from the very look and feel of the entire interface to the basic operation of even core functionality.",-1),I=a("p",null,[e("In order to install a new package, you can use the Install tab in the now familiar Settings View. Open up the Settings View using "),a("strong",null,[a("em",null,"LNX/WIN")]),e(": "),a("kbd",null,"Ctrl+,"),e(" - "),a("strong",null,[a("em",null,"MAC")]),e(": "),a("kbd",null,"Cmd+,"),e(' click on the "Install" tab and type your search query into the box under Install Packages.')],-1),T={href:"https://web.pulsar-edit.dev",target:"_blank",rel:"noopener noreferrer"},C=t('

    Package install screen

    All of the packages will come up with an "Install" button. Clicking that will download the package and install it. Your editor will now have the functionality that the package provides.

    Package Settings

    Once a package is installed in Pulsar, it will show up in the Settings View under the "Packages" tab, along with all the pre-installed packages that come with Pulsar. To filter the list in order to find one, you can type into search box directly under the "Installed Packages" heading.

    Package settings screen

    Clicking on the "Settings" button for a package will give you the settings screen for that package specifically. Here you have the option of changing some of the default variables for the package, seeing what all the command keybindings are, disabling the package temporarily, looking at the source code, seeing the current version of the package, reporting issues and uninstalling the package.

    If a new version of any of your packages is released, Pulsar will automatically detect it and you can upgrade the package from either this screen or from the "Updates" tab. This helps you easily keep all your installed packages up to date.

    Pulsar Themes

    You can also find and install new themes for Pulsar from the Settings View. These can be either UI themes or syntax themes and you can search for them from the "Install" tab, just like searching for new packages. Make sure to press the "Themes" toggle next to the search box.

    Theme search screen

    Clicking on the theme title will take you to a profile page for the theme on pulsar-edit.dev, which often has a screenshot of the theme. This way you can see what it looks like before installing it.

    Clicking on "Install" will install the theme and make it available in the Theme dropdowns as we saw in Changing the Theme.

    Example of the Unity UI theme with Monokai syntax theme

    Command Line

    You can also install packages or themes from the command line using ppm (Pulsar Package Manager). This is used by running pulsar -p <commmand> or pulsar --package <command>.

    Tip

    Check that you have ppm available by running the following command in your terminal:

    $ pulsar -p help install
    +

    You should see a message print out with details about the pulsar -p install command.

    If you do not, see the Installing Pulsar section for instructions on how to install the pulsar command for your system.

    You can install packages by using the pulsar -p install command:

    • pulsar -p install <package_name> to install the latest version.
    • pulsar -p install <package_name>@<package_version> to install a specific version.
    `,18),q=a("code",null,"pulsar -p install minimap@4.40.0 ",-1),L=a("code",null,"4.40.0",-1),S={href:"https://pulsar-edit.dev/packages/minimap",target:"_blank",rel:"noopener noreferrer"},V=t(`

    You can also use ppm to find new packages to install. If you run pulsar -p search, you can search the package registry for a search term.

    $ pulsar -p search linter
    +> Search Results For 'linter' (30)
    +> \u251C\u2500\u2500 linter A Base Linter with Cow Powers (9863242 downloads, 4757 stars)
    +> \u251C\u2500\u2500 linter-ui-default Default UI for the Linter package (7755748 downloads, 1201 stars)
    +> \u251C\u2500\u2500 linter-eslint Lint JavaScript on the fly, using ESLint (v7 or older) (2418043 downloads, 1660 stars)
    +> \u251C\u2500\u2500 linter-jshint Linter plugin for JavaScript, using jshint (1202044 downloads, 1271 stars)
    +> \u251C\u2500\u2500 linter-gcc Lint C and C++ source files using gcc / g++ (863989 downloads, 194 stars)
    +> ...
    +> \u251C\u2500\u2500 linter-shellcheck Lint Bash on the fly, using shellcheck (136938 downloads, 280 stars)
    +> \u2514\u2500\u2500 linter-rust Lint Rust-files, using rustc and/or cargo (132550 downloads, 91 stars)
    +

    You can use pulsar -p view to see more information about a specific package.

    $ pulsar -p view linter
    +> linter
    +> \u251C\u2500\u2500 3.4.0
    +> \u251C\u2500\u2500 https://github.com/steelbrain/linter
    +> \u251C\u2500\u2500 A Base Linter with Cow Powers
    +> \u251C\u2500\u2500 9863242 downloads
    +> \u2514\u2500\u2500 4757 stars
    +>
    +> Run \`pulsar -p install linter\` to install this package.
    +

    Using ppm to install from other sources

    `,5),R={href:"https://web.pulsar-edit.dev/",target:"_blank",rel:"noopener noreferrer"},U=t('
    Github or Git Remotes

    Pulsar can install from a GitHub repository or any valid git remote url. The -b option can then be used to specify a particular tag or branch.

    Git remote
    pulsar -p install <git_remote> [-b <branch_or_tag>]

    GitHub
    pulsar -p install <github_username>/<github_project> [-b <branch_or_tag>]

    ',4),z={href:"https://github.com/mauricioszabo/generic-lsp/",target:"_blank",rel:"noopener noreferrer"},A=a("p",null,[a("code",null,"pulsar -p install https://github.com/mauricioszabo/generic-lsp/")],-1),B=a("p",null,"or",-1),E=a("p",null,[a("code",null,"pulsar -p install mauricioszabo/generic-lsp")],-1),F=a("p",null,[e("This will use the current HEAD commit of the default branch. If you want to install a specific version from a branch or tag then use the "),a("code",null,"-b"),e(" option:")],-1),G=a("p",null,[e("e.g. "),a("code",null,"pulsar -p install https://github.com/mauricioszabo/generic-lsp/ -b v1.0.3")],-1);function H(N,Y){const n=o("ExternalLinkIcon"),l=o("RouterLink");return h(),d("div",null,[b,a("p",null,[e("First we'll start with the Pulsar package system. As we mentioned previously, Pulsar itself is a very basic core of functionality that ships with a number of useful packages that add new features like the "),a("a",f,[e("Tree View"),s(n)]),e(" and the "),a("a",v,[e("Settings View"),s(n)]),e(".")]),a("p",null,[e("In fact, there are more than 80 packages that comprise all of the functionality that is available in Pulsar by default. For example, the "),a("a",_,[e("Welcome screen"),s(n)]),e(" that you see when you first start Pulsar, the "),a("a",w,[e("spell checker"),s(n)]),e(", the "),a("a",y,[e("themes"),s(n)]),e(" and the "),a("a",P,[e("Fuzzy Finder"),s(n)]),e(" are all packages that are separately maintained and all use the same APIs that you have access to, as we'll see in great detail in "),s(l,{to:"/docs/launch-manual/sections/core-hacking/"},{default:g(()=>[e("Hacking the Core")]),_:1}),e(".")]),x,I,a("p",null,[e("The packages listed here have been published to "),a("a",T,[e("https://web.pulsar-edit.dev"),s(n)]),m("TODO:Change address to final URL (if this is not it)"),e(" which is the official registry for Pulsar packages. Searching on the Settings View will go to the Pulsar package registry and pull in anything that matches your search terms.")]),C,a("p",null,[e("For example "),q,e(" installs the "),L,e(" release of the "),a("a",S,[e("minimap"),s(n)]),e(" package.")]),V,a("p",null,[e("By default ppm will be using the "),a("a",R,[e("Pulsar Package Repository"),s(n)]),e(". However you can also use it to install from other locations which can be useful if you are trying to install a package not published to the Pulsar Package Repository.")]),U,a("p",null,[e("For example to install the "),a("a",z,[e("Generic-LSP"),s(n)]),e(" package from GitHub you could use either:")]),A,B,E,F,G])}const j=u(k,[["render",H],["__file","pulsar-packages.html.vue"]]);export{j as default}; diff --git a/assets/pulsar-packages.html.37ad4142.js b/assets/pulsar-packages.html.37ad4142.js new file mode 100644 index 0000000000..510b1c33dc --- /dev/null +++ b/assets/pulsar-packages.html.37ad4142.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-814e3bea","path":"/docs/launch-manual/sections/using-pulsar/sections/pulsar-packages.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Pulsar Packages","slug":"pulsar-packages","link":"#pulsar-packages","children":[{"level":3,"title":"Package Settings","slug":"package-settings","link":"#package-settings","children":[]},{"level":3,"title":"Pulsar Themes","slug":"pulsar-themes","link":"#pulsar-themes","children":[]},{"level":3,"title":"Command Line","slug":"command-line","link":"#command-line","children":[]}]}],"git":{"updatedTime":1669743486000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":9}]},"readingTime":{"minutes":3.86,"words":1159},"filePathRelative":"docs/launch-manual/sections/using-pulsar/sections/pulsar-packages.md"}');export{e as data}; diff --git a/assets/pulsar-runner.3044a99a.png b/assets/pulsar-runner.3044a99a.png new file mode 100644 index 0000000000..cf06907fca Binary files /dev/null and b/assets/pulsar-runner.3044a99a.png differ diff --git a/assets/pulsar-selections.html.6ca88fd0.js b/assets/pulsar-selections.html.6ca88fd0.js new file mode 100644 index 0000000000..0878c26938 --- /dev/null +++ b/assets/pulsar-selections.html.6ca88fd0.js @@ -0,0 +1 @@ +import{_ as u,o as d,c as r,d as s,w as e,a as l,b as t,r as a}from"./app.0e1565ce.js";const h={},f=l("h2",{id:"pulsar-selections",tabindex:"-1"},[l("a",{class:"header-anchor",href:"#pulsar-selections","aria-hidden":"true"},"#"),t(" Pulsar Selections")],-1),S=l("p",null,"Text selections in Pulsar support a number of actions, such as scoping deletion, indentation and search actions, and marking text for actions such as quoting and bracketing.",-1),b=l("p",null,[t("Selections mirror many of the movement commands. They're actually exactly the same keybindings as the movement commands, but with a "),l("kbd",null,"Shift"),t(" key added in.")],-1),m=l("ul",null,[l("li",null,[l("kbd",null,"Shift+Up"),t(" - Select up")]),l("li",null,[l("kbd",null,"Shift+Down"),t(" - Select down")]),l("li",null,[l("kbd",null,"Shift+Left"),t(" - Select previous character")]),l("li",null,[l("kbd",null,"Shift+Right"),t(" - Select next character")]),l("li",null,[l("kbd",null,"Ctrl+Shift+Left"),t(" - Select to beginning of word")]),l("li",null,[l("kbd",null,"Ctrl+Shift+Right"),t(" - Select to end of word")]),l("li",null,[l("kbd",null,"Shift+End"),t(" - Select to end of line")]),l("li",null,[l("kbd",null,"Shift+Home"),t(" - Select to first character of line")]),l("li",null,[l("kbd",null,"Ctrl+Shift+Home"),t(" - Select to top of file")]),l("li",null,[l("kbd",null,"Ctrl+Shift+End"),t(" - Select to bottom of file")])],-1),k=l("p",null,"In addition to the cursor movement selection commands, there are also a few commands that help with selecting specific areas of content.",-1),_=l("ul",null,[l("li",null,[l("kbd",null,"Ctrl+A"),t(" - Select the entire contents of the file")]),l("li",null,[l("kbd",null,"Ctrl+L"),t(" - Select the entire line")])],-1),p=l("ul",null,[l("li",null,[l("kbd",null,"Shift+Up"),t(" or "),l("kbd",null,"Ctrl+Shift+P"),t(" - Select up")]),l("li",null,[l("kbd",null,"Shift+Down"),t(" or "),l("kbd",null,"Ctrl+Shift+N"),t(" - Select down")]),l("li",null,[l("kbd",null,"Shift+Left"),t(" or "),l("kbd",null,"Ctrl+Shift+B"),t(" - Select previous character")]),l("li",null,[l("kbd",null,"Shift+Right"),t(" or "),l("kbd",null,"Ctrl+Shift+F"),t(" - Select next character")]),l("li",null,[l("kbd",null,"Alt+Shift+Left"),t(" or "),l("kbd",null,"Alt+Shift+B"),t(" - Select to beginning of word")]),l("li",null,[l("kbd",null,"Alt+Shift+Right"),t(" or "),l("kbd",null,"Alt+Shift+F"),t(" - Select to end of word")]),l("li",null,[l("kbd",null,"Cmd+Shift+Right"),t(" or "),l("kbd",null,"Ctrl+Shift+E"),t(" - Select to end of line")]),l("li",null,[l("kbd",null,"Cmd+Shift+Left"),t(" or "),l("kbd",null,"Ctrl+Shift+A"),t(" - Select to first character of line")]),l("li",null,[l("kbd",null,"Cmd+Shift+Up"),t(" - Select to top of file")]),l("li",null,[l("kbd",null,"Cmd+Shift+Down"),t(" - Select to bottom of file")])],-1),C=l("p",null,"In addition to the cursor movement selection commands, there are also a few commands that help with selecting specific areas of content.",-1),w=l("ul",null,[l("li",null,[l("kbd",null,"Cmd+A"),t(" - Select the entire contents of the file")]),l("li",null,[l("kbd",null,"Cmd+L"),t(" - Select the entire line")]),l("li",null,[l("kbd",null,"Ctrl+Shift+W"),t(" - Select the current word")])],-1),g=l("ul",null,[l("li",null,[l("kbd",null,"Shift+Up"),t(" - Select up")]),l("li",null,[l("kbd",null,"Shift+Down"),t(" - Select down")]),l("li",null,[l("kbd",null,"Shift+Left"),t(" - Select previous character")]),l("li",null,[l("kbd",null,"Shift+Right"),t(" - Select next character")]),l("li",null,[l("kbd",null,"Ctrl+Shift+Left"),t(" - Select to beginning of word")]),l("li",null,[l("kbd",null,"Ctrl+Shift+Right"),t(" - Select to end of word")]),l("li",null,[l("kbd",null,"Shift+End"),t(" - Select to end of line")]),l("li",null,[l("kbd",null,"Shift+Home"),t(" - Select to first character of line")]),l("li",null,[l("kbd",null,"Ctrl+Shift+Home"),t(" - Select to top of file")]),l("li",null,[l("kbd",null,"Ctrl+Shift+End"),t(" - Select to bottom of file")])],-1),v=l("p",null,"In addition to the cursor movement selection commands, there are also a few commands that help with selecting specific areas of content.",-1),x=l("ul",null,[l("li",null,[l("kbd",null,"Ctrl+A"),t(" - Select the entire contents of the file")]),l("li",null,[l("kbd",null,"Ctrl+L"),t(" - Select the entire line")])],-1);function A(L,R){const c=a("Tabs");return d(),r("div",null,[f,S,b,s(c,{id:"9",data:[{title:"Linux"},{title:"macOS"},{title:"Windows"}],"tab-id":"using-pulsar"},{tab0:e(({title:n,value:i,isActive:o})=>[m,k,_]),tab1:e(({title:n,value:i,isActive:o})=>[p,C,w]),tab2:e(({title:n,value:i,isActive:o})=>[g,v,x]),_:1})])}const E=u(h,[["render",A],["__file","pulsar-selections.html.vue"]]);export{E as default}; diff --git a/assets/pulsar-selections.html.ecb5890c.js b/assets/pulsar-selections.html.ecb5890c.js new file mode 100644 index 0000000000..16b686e8c4 --- /dev/null +++ b/assets/pulsar-selections.html.ecb5890c.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-bca7e1de","path":"/docs/launch-manual/sections/using-pulsar/sections/pulsar-selections.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Pulsar Selections","slug":"pulsar-selections","link":"#pulsar-selections","children":[]}],"git":{"updatedTime":1669229414000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":2}]},"readingTime":{"minutes":1.67,"words":502},"filePathRelative":"docs/launch-manual/sections/using-pulsar/sections/pulsar-selections.md"}');export{e as data}; diff --git a/assets/python-grammar.e7564a96.png b/assets/python-grammar.e7564a96.png new file mode 100644 index 0000000000..3316f09f6c Binary files /dev/null and b/assets/python-grammar.e7564a96.png differ diff --git a/assets/python-settings.491ac01e.png b/assets/python-settings.491ac01e.png new file mode 100644 index 0000000000..a285ddae95 Binary files /dev/null and b/assets/python-settings.491ac01e.png differ diff --git a/assets/reddit-private.034146a6.png b/assets/reddit-private.034146a6.png new file mode 100644 index 0000000000..e7ed068e93 Binary files /dev/null and b/assets/reddit-private.034146a6.png differ diff --git a/assets/release-process.html.16f6a0ce.js b/assets/release-process.html.16f6a0ce.js new file mode 100644 index 0000000000..f51b09b28f --- /dev/null +++ b/assets/release-process.html.16f6a0ce.js @@ -0,0 +1 @@ +import{_ as s,o as l,c as a,a as e,b as t,d as n,f as r,r as i}from"./app.0e1565ce.js";const c={},d=r('

    Release Process

    Note

    Please note that its possible this is outdated, as its original version was published by @'David Wilson' on Jan 22, 2018.

    Steps

    ',3),h=e("li",null,[t("Pull down the latest changes from "),e("code",null,"master"),t(".")],-1),u={href:"https://github.com/pulsar-edit/atom-languageclient/blob/master/CHANGELOG.md",target:"_blank",rel:"noopener noreferrer"},_=e("code",null,"## N.N.N",-1),p=e("li",null,'Add and commit CHANGELOG.md with message "Updated CHANGELOG for [version in format N.N.N]".',-1),m=e("li",null,[t("Run "),e("code",null,"npm run test"),t(" and verify that no tests failed.")],-1),g=e("li",null,[t("Run "),e("code",null,"npm run flow"),t(" and verify that there are no errors.")],-1),f=e("code",null,"npm version [version type]",-1),b=e("code",null,"major",-1),v=e("code",null,"minor",-1),N=e("code",null,"path",-1),k={href:"https://semver.org/",target:"_blank",rel:"noopener noreferrer"},w=e("li",null,[t("Run "),e("code",null,"git log -1"),t(" and make sure a commit was made for the new version (double-check version in package.json if you like).")],-1),G=e("li",null,[t("If all looks good, run "),e("code",null,"npm publish"),t(" to publish the package.")],-1),x=e("li",null,[t("Run "),e("code",null,"git push --tags"),t(" to push the new version tag to GitHub.")],-1),E={href:"https://github.com/pulsar-edit/atom-languageclient/tags",target:"_blank",rel:"noopener noreferrer"},y=e("li",null,'Paste the contents of your new CHANGELOG.md section, sans version header, into the "Describe this release" box.',-1),A=e("li",null,'Click the "Publish release" button.',-1),C=e("li",null,"\u{1F389}",-1);function L(R,H){const o=i("ExternalLinkIcon");return l(),a("div",null,[d,e("ol",null,[h,e("li",null,[t("Edit "),e("a",u,[t("CHANGELOG.md"),n(o)]),t(" to add relevant release notes for changes wince the previous release with a corresponding "),_,t(" section header.")]),p,m,g,e("li",null,[t("Run "),f,t(" where the version type is "),b,t(", "),v,t(", or "),N,t(" depending on the "),e("a",k,[t("semantic versioning"),n(o)]),t(" impact of the changes.")]),w,G,x,e("li",null,[t("Go to the atom-languageclient "),e("a",E,[t("tags page"),n(o)]),t(' and click "Add Release Notes" for the tag corresponding to the release you just published.')]),y,A,C])])}const O=s(c,[["render",L],["__file","release-process.html.vue"]]);export{O as default}; diff --git a/assets/release-process.html.e8f6cce9.js b/assets/release-process.html.e8f6cce9.js new file mode 100644 index 0000000000..bee440f138 --- /dev/null +++ b/assets/release-process.html.e8f6cce9.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-4da9456e","path":"/docs/packages/core/atom-languageclient/release-process.html","title":"Release Process","lang":"en-US","frontmatter":{"title":"Release Process","prev":"/docs/packages/core/list.html"},"excerpt":"","headers":[{"level":2,"title":"Steps","slug":"steps","link":"#steps","children":[]}],"git":{"updatedTime":1663616942000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":2}]},"readingTime":{"minutes":0.81,"words":242},"filePathRelative":"docs/packages/core/atom-languageclient/release-process.md"}');export{e as data}; diff --git a/assets/removing-shadow-dom-styles.html.58174f11.js b/assets/removing-shadow-dom-styles.html.58174f11.js new file mode 100644 index 0000000000..9fa365bc0f --- /dev/null +++ b/assets/removing-shadow-dom-styles.html.58174f11.js @@ -0,0 +1,54 @@ +import{_ as t,o,c as i,a as s,b as e,d as a,f as l,r as c}from"./app.0e1565ce.js";const d={},r=s("h3",{id:"removing-shadow-dom-styles",tabindex:"-1"},[s("a",{class:"header-anchor",href:"#removing-shadow-dom-styles","aria-hidden":"true"},"#"),e(" Removing Shadow DOM styles")],-1),p=s("code",null,"1.13",-1),u={href:"https://github.com/atom/atom/pull/12903",target:"_blank",rel:"noopener noreferrer"},m=l(`

    Summary

    Here is a quick summary to see all the changes at a glance:

    Before+/-After
    atom-text-editor::shadow {}- ::shadowatom-text-editor {}
    .class /deep/ .class {}- /deep/.class .class {}
    atom-text-editor, :host {}- :hostatom-text-editor {}
    .comment {}+ .syntax--.syntax--comment {}

    Below you'll find more detailed examples.

    UI themes and packages

    ::shadow

    Remove the ::shadow pseudo-element selector from atom-text-editor.

    Before:

    atom-text-editor::shadow .cursor {
    +	border-color: hotpink;
    +}
    +

    After:

    atom-text-editor .cursor {
    +	border-color: hotpink;
    +}
    +
    /deep/

    Remove the /deep/ combinator selector. It didn't get used that often, mainly to customize the scrollbars.

    Before:

    .scrollbars-visible-always /deep/ ::-webkit-scrollbar {
    +	width: 8px;
    +	height: 8px;
    +}
    +

    After:

    .scrollbars-visible-always ::-webkit-scrollbar {
    +	width: 8px;
    +	height: 8px;
    +}
    +

    Syntax themes

    :host

    Remove the :host pseudo-element selector. To scope any styles to the text editor, using atom-text-editor is already enough.

    Before:

    atom-text-editor,
    +:host {
    +	.cursor {
    +		border-color: hotpink;
    +	}
    +}
    +

    After:

    atom-text-editor {
    +	.cursor {
    +		border-color: hotpink;
    +	}
    +}
    +
    syntax--

    Add a syntax-- prefix to all grammar selectors used by language packages. It looks a bit verbose, but it will protect all the grammar selectors from accidental style pollution.

    Before:

    .comment {
    +	color: @mono-3;
    +	font-style: italic;
    +
    +	.markup.link {
    +		color: @mono-3;
    +	}
    +}
    +

    After:

    .syntax--comment {
    +	color: @mono-3;
    +	font-style: italic;
    +
    +	.syntax--markup.syntax--link {
    +		color: @mono-3;
    +	}
    +}
    +

    Note: Selectors like the .gutter, .indent-guide, .cursor among others, that are also part of Syntax themes, don't need a prefix. Only grammar selectors that get used by language packages. For example .syntax--keyword, .syntax--keyword.syntax--operator.syntax--js.

    Context-Targeted Style Sheets

    Atom also allowed you to target a specific shadow DOM context with an entire style sheet by adding .atom-text-editor to the file name. This is now not necessary anymore and can be removed.

    Before:

    my-ui-theme/
    +  styles/
    +    index.atom-text-editor.less
    +

    After:

    my-ui-theme/
    +  styles/
    +    index.less
    +

    I followed the guide, but now my styling is broken!

    By replacing atom-text-editor::shadow with atom-text-editor.editor, specificity might have changed. This can cause the side effect that some of your properties won't be strong enough. To fix that, you can increase specificity on your selector. A simple way is to just repeat your class (in the example below it's .my-class):

    Before:

    atom-text-editor::shadow .my-class {
    +	color: hotpink;
    +}
    +

    After:

    atom-text-editor .my-class.my-class {
    +	color: hotpink;
    +}
    +

    When should I migrate my theme/package?

    `,44),h=s("li",null,[e("If you already want to test the migration on master or Beta channel, make sure to change your "),s("code",null,"package.json"),e(" file to "),s("code",null,'"engines": { "atom": ">=1.13.0 <2.0.0" }'),e(". This will prevent Atom from updating your theme or package before the user also updates Atom to version "),s("code",null,"1.13"),e(".")],-1),v=s("code",null,"1.13",-1),b=s("strong",null,"Stable",-1),k={href:"https://blog.atom.io/",target:"_blank",rel:"noopener noreferrer"},g=s("code",null,"1.13",-1),y={href:"https://github.com/atom/deprecation-cop",target:"_blank",rel:"noopener noreferrer"};function x(f,w){const n=c("ExternalLinkIcon");return o(),i("div",null,[r,s("p",null,[e("In Atom "),p,e(" the Shadow DOM got removed from text editors. For more background on the reasoning, check out the "),s("a",u,[e("Pull Request"),a(n)]),e(" where it was removed. In this guide you will learn how to migrate your theme or package.")]),m,s("ul",null,[h,s("li",null,[e("Or you can wait until Atom "),v,e(" reaches "),b,e(". Check "),s("a",k,[e("blog.atom.io"),a(n)]),e(" to see if "),g,e(" already has been released. Don't worry if you're a bit late, Atom will transform the deprecated selectors automatically to avoid breaking any themes or packages. But users will start to see a deprecation warning in "),s("a",y,[e("deprecation-cop"),a(n)]),e(".")])])])}const A=t(d,[["render",x],["__file","removing-shadow-dom-styles.html.vue"]]);export{A as default}; diff --git a/assets/removing-shadow-dom-styles.html.f6df46a8.js b/assets/removing-shadow-dom-styles.html.f6df46a8.js new file mode 100644 index 0000000000..f15b790fc9 --- /dev/null +++ b/assets/removing-shadow-dom-styles.html.f6df46a8.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-5a059e16","path":"/docs/atom-archive/shadow-dom/sections/removing-shadow-dom-styles.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"Removing Shadow DOM styles","slug":"removing-shadow-dom-styles","link":"#removing-shadow-dom-styles","children":[]}],"git":{"updatedTime":1667691984000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1},{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":1.92,"words":576},"filePathRelative":"docs/atom-archive/shadow-dom/sections/removing-shadow-dom-styles.md"}');export{e as data}; diff --git a/assets/repos.html.47dd31c0.js b/assets/repos.html.47dd31c0.js new file mode 100644 index 0000000000..b6d60ae2d2 --- /dev/null +++ b/assets/repos.html.47dd31c0.js @@ -0,0 +1 @@ +import{_ as a,o as n,c as s,a as e,b as t,d as o,r as i}from"./app.0e1565ce.js";const l={},h=e("p",null,"The Pulsar project is split up into a number of repositories, all hosted on GitHub.",-1),u=e("p",null,"The very nature of the way Pulsar works means that there are a large number of packages that come together to make up the editor and each of these have their own repository.",-1),c=e("p",null,"We also have a number of other repositories to host the website, package backend & frontend and our organization wide documentation & templates.",-1),d={href:"https://github.com/pulsar-edit",target:"_blank",rel:"noopener noreferrer"},p={href:"https://github.com/pulsar-edit/.github/blob/main/REPOS.md",target:"_blank",rel:"noopener noreferrer"},_=e("h2",{id:"editor",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#editor","aria-hidden":"true"},"#"),t(" Editor")],-1),b={href:"https://github.com/pulsar-edit/pulsar",target:"_blank",rel:"noopener noreferrer"},f={href:"https://github.com/pulsar-edit/ppm",target:"_blank",rel:"noopener noreferrer"},g=e("h2",{id:"websites-and-services",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#websites-and-services","aria-hidden":"true"},"#"),t(" Websites and services")],-1),m={href:"https://github.com/pulsar-edit/pulsar-edit.github.io",target:"_blank",rel:"noopener noreferrer"},k={href:"https://github.com/pulsar-edit/package-frontend",target:"_blank",rel:"noopener noreferrer"},v={href:"https://github.com/pulsar-edit/package-backend",target:"_blank",rel:"noopener noreferrer"},P={href:"https://github.com/pulsar-edit/.github",target:"_blank",rel:"noopener noreferrer"},w={href:"https://github.com/pulsar-edit/pulsar-assets",target:"_blank",rel:"noopener noreferrer"};function x(y,E){const r=i("ExternalLinkIcon");return n(),s("div",null,[h,u,c,e("p",null,[t("All of the Pulsar repositories can be found under the organization at "),e("a",d,[t("https://github.com/pulsar-edit"),o(r)]),t(" but below is a list of some of the more regularly used ones you may wish to visit in order to contribute to the project. For a more comprehensive list you can visit the org-level "),e("a",p,[t("REPOS file"),o(r)]),t(".")]),_,e("ul",null,[e("li",null,[e("a",b,[t("Pulsar"),o(r)])]),e("li",null,[e("a",f,[t("PPM"),o(r)])])]),g,e("ul",null,[e("li",null,[e("a",m,[t("Homepage & Documentation"),o(r)])]),e("li",null,[e("a",k,[t("Package Frontend"),o(r)])]),e("li",null,[e("a",v,[t("Package Backend"),o(r)])]),e("li",null,[e("a",P,[t("GitHub Organization Files"),o(r)])]),e("li",null,[e("a",w,[t("Assets"),o(r)])])])])}const z=a(l,[["render",x],["__file","repos.html.vue"]]);export{z as default}; diff --git a/assets/repos.html.ad6ff885.js b/assets/repos.html.ad6ff885.js new file mode 100644 index 0000000000..2968d14636 --- /dev/null +++ b/assets/repos.html.ad6ff885.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-41185df1","path":"/repos.html","title":"Pulsar Repositories","lang":"en-US","frontmatter":{"title":"Pulsar Repositories","path":"/repos/","sitemap":{"priority":0.4}},"excerpt":"","headers":[{"level":2,"title":"Editor","slug":"editor","link":"#editor","children":[]},{"level":2,"title":"Websites and services","slug":"websites-and-services","link":"#websites-and-services","children":[]}],"git":{"updatedTime":1669438016000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":2},{"name":"confused-Techie","email":"dev@lhbasics.com","commits":1}]},"readingTime":{"minutes":0.66,"words":199},"filePathRelative":"repos.md"}');export{e as data}; diff --git a/assets/scoped-settings-scopes-and-scope-descriptors.html.3914ff78.js b/assets/scoped-settings-scopes-and-scope-descriptors.html.3914ff78.js new file mode 100644 index 0000000000..0c241f6c2b --- /dev/null +++ b/assets/scoped-settings-scopes-and-scope-descriptors.html.3914ff78.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-56da0c3a","path":"/docs/launch-manual/sections/behind-pulsar/sections/scoped-settings-scopes-and-scope-descriptors.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Scoped Settings, Scopes and Scope Descriptors","slug":"scoped-settings-scopes-and-scope-descriptors","link":"#scoped-settings-scopes-and-scope-descriptors","children":[{"level":3,"title":"Scope Names in Syntax Tokens","slug":"scope-names-in-syntax-tokens","link":"#scope-names-in-syntax-tokens","children":[]},{"level":3,"title":"Scope Selectors","slug":"scope-selectors","link":"#scope-selectors","children":[]},{"level":3,"title":"Scope Descriptors","slug":"scope-descriptors","link":"#scope-descriptors","children":[]}]}],"git":{"updatedTime":1668711072000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":2.15,"words":645},"filePathRelative":"docs/launch-manual/sections/behind-pulsar/sections/scoped-settings-scopes-and-scope-descriptors.md"}');export{e as data}; diff --git a/assets/scoped-settings-scopes-and-scope-descriptors.html.564ad54d.js b/assets/scoped-settings-scopes-and-scope-descriptors.html.564ad54d.js new file mode 100644 index 0000000000..8a66af9208 --- /dev/null +++ b/assets/scoped-settings-scopes-and-scope-descriptors.html.564ad54d.js @@ -0,0 +1,27 @@ +import{_ as c}from"./markup.30f112ce.js";import{_ as p,o as i,c as r,a as s,d as a,b as n,e as t,f as o,r as l}from"./app.0e1565ce.js";const u={},d=o(`

    Scoped Settings, Scopes and Scope Descriptors

    Pulsar supports language-specific settings. You can soft wrap only Markdown files, or set the tab length to 4 in Python files.

    Language-specific settings are a subset of something more general we call "scoped settings". Scoped settings allow targeting down to a specific syntax token type. For example, you could conceivably set a setting to target only Ruby comments, only code inside Markdown files, or even only JavaScript function names.

    Scope Names in Syntax Tokens

    Each token in the editor has a collection of scope names. For example, the aforementioned JavaScript function name might have the scope names function and name. An open paren might have the scope names punctuation, parameters, begin.

    Scope names work just like CSS classes. In fact, in the editor, scope names are attached to a token's DOM node as CSS classes.

    Take this piece of JavaScript:

    function functionName() {
    +	console.log("Log it out");
    +}
    +

    In the dev tools, the first line's markup looks like this.

    Markup

    All the class names on the spans are scope names. Any scope name can be used to target a setting's value.

    Scope Selectors

    Scope selectors allow you to target specific tokens just like a CSS selector targets specific nodes in the DOM. Some examples:

    '.source.js' # selects all javascript tokens
    +'.source.js .function.name' # selects all javascript function names
    +'.function.name' # selects all function names in any language
    +
    `,14),k={href:"https://atom.io/docs/api/latest/Config#instance-set",target:"_blank",rel:"noopener noreferrer"},m=s("code",null,"Config::set",-1),g=s("code",null,"scopeSelector",-1),h=s("code",null,"scopeSelector",-1),v=o(`
    atom.config.set("my-package.my-setting", "special value", {
    +	scopeSelector: ".source.js .function.name",
    +});
    +

    Scope Descriptors

    `,2),f={href:"https://atom.io/docs/api/latest/ScopeDescriptor",target:"_blank",rel:"noopener noreferrer"},b=s("code",null,"Array",-1),_=s("code",null,"String",-1),y=s("em",null,"all",-1),S=o(`

    In our JavaScript example above, a scope descriptor for the function name token would be:

    ["source.js", "meta.function.js", "entity.name.function.js"];
    +
    `,2),j={href:"https://atom.io/docs/api/latest/Config#instance-get",target:"_blank",rel:"noopener noreferrer"},x=s("code",null,"Config::get",-1),q=s("code",null,"scopeDescriptor",-1),D=o(`
    const scopeDescriptor = [
    +	"source.js",
    +	"meta.function.js",
    +	"entity.name.function.js",
    +];
    +const value = atom.config.get("my-package.my-setting", {
    +	scope: scopeDescriptor,
    +});
    +

    But, you do not need to generate scope descriptors by hand. There are a couple methods available to get the scope descriptor from the editor:

    `,2),w={href:"https://atom.io/docs/api/latest/TextEditor#instance-getRootScopeDescriptor",target:"_blank",rel:"noopener noreferrer"},T=s("code",null,"Editor::getRootScopeDescriptor",-1),P=s("code",null,'[".source.js"]',-1),A={href:"https://atom.io/docs/api/latest/TextEditor#instance-scopeDescriptorForBufferPosition",target:"_blank",rel:"noopener noreferrer"},O=s("code",null,"Editor::scopeDescriptorForBufferPosition",-1),C={href:"https://atom.io/docs/api/latest/Cursor#instance-getScopeDescriptor",target:"_blank",rel:"noopener noreferrer"},I=s("code",null,"Cursor::getScopeDescriptor",-1),E=s("code",null,'["source.js", "meta.function.js", "entity.name.function.js"]',-1),J=o(`

    Let's revisit our example using these methods:

    const editor = atom.workspace.getActiveTextEditor();
    +const cursor = editor.getLastCursor();
    +const valueAtCursor = atom.config.get("my-package.my-setting", {
    +	scope: cursor.getScopeDescriptor(),
    +});
    +const valueForLanguage = atom.config.get("my-package.my-setting", {
    +	scope: editor.getRootScopeDescriptor(),
    +});
    +
    `,2);function L(N,B){const e=l("ExternalLinkIcon");return i(),r("div",null,[d,s("p",null,[s("a",k,[m,a(e)]),n(" accepts a "),t("TODO: There is currently no Pulsar API doc so this is being left for the time being"),g,n(". If you'd like to set a setting for JavaScript function names, you can give it the JavaScript function name "),h,n(":")]),v,s("p",null,[n("A scope descriptor is an "),s("a",f,[n("Object"),a(e)]),t("TODO: There is currently no Pulsar API doc so this is being left for the time being"),n(" that wraps an "),b,n(" of "),_,n("s. The Array describes a path from the root of the syntax tree to a token including "),y,n(" scope names for the entire path.")]),S,s("p",null,[s("a",j,[x,a(e)]),n(" accepts a "),t("TODO: There is currently no Pulsar API doc so this is being left for the time being"),q,n(". You can get the value for your setting scoped to JavaScript function names via:")]),D,s("ul",null,[s("li",null,[s("a",w,[T,a(e)]),t("TODO: There is currently no Pulsar API doc so this is being left for the time being"),n(" to get the language's descriptor. For example: "),P]),s("li",null,[s("a",A,[O,a(e)]),t("TODO: There is currently no Pulsar API doc so this is being left for the time being"),n(" to get the descriptor at a specific position in the buffer.")]),s("li",null,[s("a",C,[I,a(e)]),t("TODO: There is currently no Pulsar API doc so this is being left for the time being"),n(" to get a cursor's descriptor based on position. eg. if the cursor were in the name of the method in our example it would return "),E])]),J])}const V=p(u,[["render",L],["__file","scoped-settings-scopes-and-scope-descriptors.html.vue"]]);export{V as default}; diff --git a/assets/scoped-settings-scopes-and-scope-descriptors.html.9871f523.js b/assets/scoped-settings-scopes-and-scope-descriptors.html.9871f523.js new file mode 100644 index 0000000000..c7eed69e4e --- /dev/null +++ b/assets/scoped-settings-scopes-and-scope-descriptors.html.9871f523.js @@ -0,0 +1,27 @@ +import{_ as o}from"./markup.30f112ce.js";import{_ as c,o as p,c as i,a as n,d as e,b as s,f as t,r}from"./app.0e1565ce.js";const l={},u=t(`

    Scoped Settings, Scopes and Scope Descriptors

    Atom supports language-specific settings. You can soft wrap only Markdown files, or set the tab length to 4 in Python files.

    Language-specific settings are a subset of something more general we call "scoped settings". Scoped settings allow targeting down to a specific syntax token type. For example, you could conceivably set a setting to target only Ruby comments, only code inside Markdown files, or even only JavaScript function names.

    Scope Names in Syntax Tokens

    Each token in the editor has a collection of scope names. For example, the aforementioned JavaScript function name might have the scope names function and name. An open paren might have the scope names punctuation, parameters, begin.

    Scope names work just like CSS classes. In fact, in the editor, scope names are attached to a token's DOM node as CSS classes.

    Take this piece of JavaScript:

    function functionName() {
    +	console.log("Log it out");
    +}
    +

    In the dev tools, the first line's markup looks like this.

    Markup

    All the class names on the spans are scope names. Any scope name can be used to target a setting's value.

    Scope Selectors

    Scope selectors allow you to target specific tokens just like a CSS selector targets specific nodes in the DOM. Some examples:

    '.source.js' # selects all javascript tokens
    +'.source.js .function.name' # selects all javascript function names
    +'.function.name' # selects all function names in any language
    +
    `,14),d={href:"https://atom.io/docs/api/latest/Config#instance-set",target:"_blank",rel:"noopener noreferrer"},k=n("code",null,"Config::set",-1),m=n("code",null,"scopeSelector",-1),g=n("code",null,"scopeSelector",-1),h=t(`
    atom.config.set("my-package.my-setting", "special value", {
    +	scopeSelector: ".source.js .function.name",
    +});
    +

    Scope Descriptors

    `,2),v={href:"https://atom.io/docs/api/latest/ScopeDescriptor",target:"_blank",rel:"noopener noreferrer"},f=n("code",null,"Array",-1),_=n("code",null,"String",-1),b=n("em",null,"all",-1),y=t(`

    In our JavaScript example above, a scope descriptor for the function name token would be:

    ["source.js", "meta.function.js", "entity.name.function.js"];
    +
    `,2),S={href:"https://atom.io/docs/api/latest/Config#instance-get",target:"_blank",rel:"noopener noreferrer"},j=n("code",null,"Config::get",-1),x=n("code",null,"scopeDescriptor",-1),q=t(`
    const scopeDescriptor = [
    +	"source.js",
    +	"meta.function.js",
    +	"entity.name.function.js",
    +];
    +const value = atom.config.get("my-package.my-setting", {
    +	scope: scopeDescriptor,
    +});
    +

    But, you do not need to generate scope descriptors by hand. There are a couple methods available to get the scope descriptor from the editor:

    `,2),w={href:"https://atom.io/docs/api/latest/TextEditor#instance-getRootScopeDescriptor",target:"_blank",rel:"noopener noreferrer"},D=n("code",null,"Editor::getRootScopeDescriptor",-1),C=n("code",null,'[".source.js"]',-1),A={href:"https://atom.io/docs/api/latest/TextEditor#instance-scopeDescriptorForBufferPosition",target:"_blank",rel:"noopener noreferrer"},E=n("code",null,"Editor::scopeDescriptorForBufferPosition",-1),T={href:"https://atom.io/docs/api/latest/Cursor#instance-getScopeDescriptor",target:"_blank",rel:"noopener noreferrer"},J=n("code",null,"Cursor::getScopeDescriptor",-1),L=n("code",null,'["source.js", "meta.function.js", "entity.name.function.js"]',-1),B=t(`

    Let's revisit our example using these methods:

    const editor = atom.workspace.getActiveTextEditor();
    +const cursor = editor.getLastCursor();
    +const valueAtCursor = atom.config.get("my-package.my-setting", {
    +	scope: cursor.getScopeDescriptor(),
    +});
    +const valueForLanguage = atom.config.get("my-package.my-setting", {
    +	scope: editor.getRootScopeDescriptor(),
    +});
    +
    `,2);function F(I,N){const a=r("ExternalLinkIcon");return p(),i("div",null,[u,n("p",null,[n("a",d,[k,e(a)]),s(" accepts a "),m,s(". If you'd like to set a setting for JavaScript function names, you can give it the JavaScript function name "),g,s(":")]),h,n("p",null,[s("A scope descriptor is an "),n("a",v,[s("Object"),e(a)]),s(" that wraps an "),f,s(" of "),_,s("s. The Array describes a path from the root of the syntax tree to a token including "),b,s(" scope names for the entire path.")]),y,n("p",null,[n("a",S,[j,e(a)]),s(" accepts a "),x,s(". You can get the value for your setting scoped to JavaScript function names via:")]),q,n("ul",null,[n("li",null,[n("a",w,[D,e(a)]),s(" to get the language's descriptor. For example: "),C]),n("li",null,[n("a",A,[E,e(a)]),s(" to get the descriptor at a specific position in the buffer.")]),n("li",null,[n("a",T,[J,e(a)]),s(" to get a cursor's descriptor based on position. eg. if the cursor were in the name of the method in our example it would return "),L])]),B])}const V=c(l,[["render",F],["__file","scoped-settings-scopes-and-scope-descriptors.html.vue"]]);export{V as default}; diff --git a/assets/scoped-settings-scopes-and-scope-descriptors.html.c8332e42.js b/assets/scoped-settings-scopes-and-scope-descriptors.html.c8332e42.js new file mode 100644 index 0000000000..13a0d4b8f4 --- /dev/null +++ b/assets/scoped-settings-scopes-and-scope-descriptors.html.c8332e42.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-4bb0fcfd","path":"/docs/atom-archive/behind-atom/sections/scoped-settings-scopes-and-scope-descriptors.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"Scoped Settings, Scopes and Scope Descriptors","slug":"scoped-settings-scopes-and-scope-descriptors","link":"#scoped-settings-scopes-and-scope-descriptors","children":[]}],"git":{"updatedTime":1668309800000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":3},{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":1.81,"words":543},"filePathRelative":"docs/atom-archive/behind-atom/sections/scoped-settings-scopes-and-scope-descriptors.md"}');export{e as data}; diff --git a/assets/scopename.20d826fe.png b/assets/scopename.20d826fe.png new file mode 100644 index 0000000000..2fe1c11316 Binary files /dev/null and b/assets/scopename.20d826fe.png differ diff --git a/assets/search.0782d0d1.svg b/assets/search.0782d0d1.svg new file mode 100644 index 0000000000..03d83913e8 --- /dev/null +++ b/assets/search.0782d0d1.svg @@ -0,0 +1 @@ + diff --git a/assets/serialization-in-atom.html.06063206.js b/assets/serialization-in-atom.html.06063206.js new file mode 100644 index 0000000000..0957ca3d07 --- /dev/null +++ b/assets/serialization-in-atom.html.06063206.js @@ -0,0 +1,78 @@ +import{_ as n,o as s,c as a,f as e}from"./app.0e1565ce.js";const t={},i=e(`

    Serialization in Atom

    When a window is refreshed or restored from a previous session, the view and its associated objects are deserialized from a JSON representation that was stored during the window's previous shutdown. For your own views and objects to be compatible with refreshing, you'll need to make them play nicely with the serializing and deserializing.

    Package Serialization Hook

    Your package's main module can optionally include a serialize method, which will be called before your package is deactivated. You should return a JSON-serializable object, which will be handed back to you as an object argument to activate next time it is called. In the following example, the package keeps an instance of MyObject in the same state across refreshes.

    module.exports = {
    +	activate(state) {
    +		this.myObject = state
    +			? atom.deserializers.deserialize(state)
    +			: new MyObject("Hello");
    +	},
    +
    +	serialize() {
    +		return this.myObject.serialize();
    +	},
    +};
    +

    Serialization Methods

    class MyObject {
    +	constructor(data) {
    +		this.data = data;
    +	}
    +
    +	serialize() {
    +		return {
    +			deserializer: "MyObject",
    +			data: this.data,
    +		};
    +	}
    +}
    +
    serialize()

    Objects that you want to serialize should implement .serialize(). This method should return a serializable object, and it must contain a key named deserializer whose value is the name of a registered deserializer that can convert the rest of the data to an object. It's usually just the name of the class itself.

    Registering Deserializers

    The other side of the coin is deserializers, whose job is to convert a state object returned from a previous call to serialize back into a genuine object.

    deserializers in package.json

    The preferred way to register deserializers is via your package's package.json file:

    {
    +  "name": "wordcount",
    +  ...
    +  "deserializers": {
    +    "MyObject": "deserializeMyObject"
    +  }
    +}
    +

    Here, the key ("MyObject") is the name of the deserializer\u2014the same string used by the deserializer field in the object returned by your serialize() method. The value ("deserializeMyObject") is the name of a function in your main module that'll be passed the serialized data and will return a genuine object. For example, your main module might look like this:

    module.exports = {
    +	deserializeMyObject({ data }) {
    +		return new MyObject(data);
    +	},
    +};
    +

    Now you can call the global deserialize method with state returned from serialize, and your class's deserialize method will be selected automatically.

    atom.deserializers.add(klass)

    An alternative is to use the atom.deserializers.add method with your class in order to make it available to the deserialization system. Usually this is used in conjunction with a class-level deserialize method:

    class MyObject {
    +	static initClass() {
    +		atom.deserializers.add(this);
    +	}
    +
    +	static deserialize({ data }) {
    +		return new MyObject(data);
    +	}
    +
    +	constructor(data) {
    +		this.data = data;
    +	}
    +
    +	serialize() {
    +		return {
    +			deserializer: "MyObject",
    +			data: this.data,
    +		};
    +	}
    +}
    +
    +MyObject.initClass();
    +

    While this used to be the standard method of registering a deserializer, the package.json method is now preferred since it allows Atom to defer loading and executing your code until it's actually needed.

    Versioning

    class MyObject {
    +	static initClass() {
    +		atom.deserializers.add(this);
    +
    +		this.version = 2;
    +	}
    +
    +	static deserialize(state) {
    +		// ...
    +	}
    +
    +	serialize() {
    +		return {
    +			version: this.constructor.version,
    +			// ...
    +		};
    +	}
    +}
    +
    +MyObject.initClass();
    +

    Your serializable class can optionally have a class-level @version property and include a version key in its serialized state. When deserializing, Atom will only attempt to call deserialize if the two versions match, and otherwise return undefined. We plan on implementing a migration system in the future, but this at least protects you from improperly deserializing old state.

    `,24),o=[i];function p(c,l){return s(),a("div",null,o)}const r=n(t,[["render",p],["__file","serialization-in-atom.html.vue"]]);export{r as default}; diff --git a/assets/serialization-in-atom.html.9a8a1ff4.js b/assets/serialization-in-atom.html.9a8a1ff4.js new file mode 100644 index 0000000000..9a8d91a5d5 --- /dev/null +++ b/assets/serialization-in-atom.html.9a8a1ff4.js @@ -0,0 +1 @@ +const i=JSON.parse('{"key":"v-5c815036","path":"/docs/atom-archive/behind-atom/sections/serialization-in-atom.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"Serialization in Atom","slug":"serialization-in-atom","link":"#serialization-in-atom","children":[]}],"git":{"updatedTime":1668309800000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":3},{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":1.78,"words":534},"filePathRelative":"docs/atom-archive/behind-atom/sections/serialization-in-atom.md"}');export{i as data}; diff --git a/assets/serialization-in-pulsar.html.58da7fb2.js b/assets/serialization-in-pulsar.html.58da7fb2.js new file mode 100644 index 0000000000..807dc61db8 --- /dev/null +++ b/assets/serialization-in-pulsar.html.58da7fb2.js @@ -0,0 +1 @@ +const i=JSON.parse('{"key":"v-33739e9c","path":"/docs/launch-manual/sections/behind-pulsar/sections/serialization-in-pulsar.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Serialization in Pulsar","slug":"serialization-in-pulsar","link":"#serialization-in-pulsar","children":[{"level":3,"title":"Package Serialization Hook","slug":"package-serialization-hook","link":"#package-serialization-hook","children":[]},{"level":3,"title":"Serialization Methods","slug":"serialization-methods","link":"#serialization-methods","children":[]},{"level":3,"title":"Versioning","slug":"versioning","link":"#versioning","children":[]}]}],"git":{"updatedTime":1668712701000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":1.81,"words":544},"filePathRelative":"docs/launch-manual/sections/behind-pulsar/sections/serialization-in-pulsar.md"}');export{i as data}; diff --git a/assets/serialization-in-pulsar.html.6abe4c1d.js b/assets/serialization-in-pulsar.html.6abe4c1d.js new file mode 100644 index 0000000000..dfee3dc881 --- /dev/null +++ b/assets/serialization-in-pulsar.html.6abe4c1d.js @@ -0,0 +1,78 @@ +import{_ as n,o as s,c as a,f as e}from"./app.0e1565ce.js";const t={},i=e(`

    Serialization in Pulsar

    When a window is refreshed or restored from a previous session, the view and its associated objects are deserialized from a JSON representation that was stored during the window's previous shutdown. For your own views and objects to be compatible with refreshing, you'll need to make them play nicely with the serializing and deserializing.

    Package Serialization Hook

    Your package's main module can optionally include a serialize method, which will be called before your package is deactivated. You should return a JSON-serializable object, which will be handed back to you as an object argument to activate next time it is called. In the following example, the package keeps an instance of MyObject in the same state across refreshes.

    module.exports = {
    +	activate(state) {
    +		this.myObject = state
    +			? atom.deserializers.deserialize(state)
    +			: new MyObject("Hello");
    +	},
    +
    +	serialize() {
    +		return this.myObject.serialize();
    +	},
    +};
    +

    Serialization Methods

    class MyObject {
    +	constructor(data) {
    +		this.data = data;
    +	}
    +
    +	serialize() {
    +		return {
    +			deserializer: "MyObject",
    +			data: this.data,
    +		};
    +	}
    +}
    +

    serialize()

    Objects that you want to serialize should implement .serialize(). This method should return a serializable object, and it must contain a key named deserializer whose value is the name of a registered deserializer that can convert the rest of the data to an object. It's usually just the name of the class itself.

    Registering Deserializers

    The other side of the coin is deserializers, whose job is to convert a state object returned from a previous call to serialize back into a genuine object.

    deserializers in package.json

    The preferred way to register deserializers is via your package's package.json file:

    {
    +  "name": "wordcount",
    +  ...
    +  "deserializers": {
    +    "MyObject": "deserializeMyObject"
    +  }
    +}
    +

    Here, the key ("MyObject") is the name of the deserializer\u2014the same string used by the deserializer field in the object returned by your serialize() method. The value ("deserializeMyObject") is the name of a function in your main module that'll be passed the serialized data and will return a genuine object. For example, your main module might look like this:

    module.exports = {
    +	deserializeMyObject({ data }) {
    +		return new MyObject(data);
    +	},
    +};
    +

    Now you can call the global deserialize method with state returned from serialize, and your class's deserialize method will be selected automatically.

    atom.deserializers.add(klass)

    An alternative is to use the atom.deserializers.add method with your class in order to make it available to the deserialization system. Usually this is used in conjunction with a class-level deserialize method:

    class MyObject {
    +	static initClass() {
    +		atom.deserializers.add(this);
    +	}
    +
    +	static deserialize({ data }) {
    +		return new MyObject(data);
    +	}
    +
    +	constructor(data) {
    +		this.data = data;
    +	}
    +
    +	serialize() {
    +		return {
    +			deserializer: "MyObject",
    +			data: this.data,
    +		};
    +	}
    +}
    +
    +MyObject.initClass();
    +

    While this used to be the standard method of registering a deserializer, the package.json method is now preferred since it allows Pulsar to defer loading and executing your code until it's actually needed.

    Versioning

    class MyObject {
    +	static initClass() {
    +		atom.deserializers.add(this);
    +
    +		this.version = 2;
    +	}
    +
    +	static deserialize(state) {
    +		// ...
    +	}
    +
    +	serialize() {
    +		return {
    +			version: this.constructor.version,
    +			// ...
    +		};
    +	}
    +}
    +
    +MyObject.initClass();
    +

    Your serializable class can optionally have a class-level @version property and include a version key in its serialized state. When deserializing, Pulsar will only attempt to call deserialize if the two versions match, and otherwise return undefined.

    `,24),o=[i];function p(c,l){return s(),a("div",null,o)}const r=n(t,[["render",p],["__file","serialization-in-pulsar.html.vue"]]);export{r as default}; diff --git a/assets/service-diagram-1.38837fcc.png b/assets/service-diagram-1.38837fcc.png new file mode 100644 index 0000000000..86b2dcd503 Binary files /dev/null and b/assets/service-diagram-1.38837fcc.png differ diff --git a/assets/service-diagram-2.0af85dad.png b/assets/service-diagram-2.0af85dad.png new file mode 100644 index 0000000000..77803ecfe6 Binary files /dev/null and b/assets/service-diagram-2.0af85dad.png differ diff --git a/assets/service-diagram-3.c330b93e.png b/assets/service-diagram-3.c330b93e.png new file mode 100644 index 0000000000..add374b75a Binary files /dev/null and b/assets/service-diagram-3.c330b93e.png differ diff --git a/assets/settings-wrap.da47925c.png b/assets/settings-wrap.da47925c.png new file mode 100644 index 0000000000..288d17f1ba Binary files /dev/null and b/assets/settings-wrap.da47925c.png differ diff --git a/assets/settings.f822be64.png b/assets/settings.f822be64.png new file mode 100644 index 0000000000..e4b4dfca91 Binary files /dev/null and b/assets/settings.f822be64.png differ diff --git a/assets/shields-io.b3bc3a32.png b/assets/shields-io.b3bc3a32.png new file mode 100644 index 0000000000..ed10c538e3 Binary files /dev/null and b/assets/shields-io.b3bc3a32.png differ diff --git a/assets/signing.c58cd4da.png b/assets/signing.c58cd4da.png new file mode 100644 index 0000000000..a89e4cb968 Binary files /dev/null and b/assets/signing.c58cd4da.png differ diff --git a/assets/snippet-scope.1f381b52.js b/assets/snippet-scope.1f381b52.js new file mode 100644 index 0000000000..f22c6e48db --- /dev/null +++ b/assets/snippet-scope.1f381b52.js @@ -0,0 +1 @@ +const s="/assets/snippets.1ed82d05.png",p="/assets/snippet-scope.7c6e9359.png";export{s as _,p as a}; diff --git a/assets/snippet-scope.7c6e9359.png b/assets/snippet-scope.7c6e9359.png new file mode 100644 index 0000000000..8419c3c757 Binary files /dev/null and b/assets/snippet-scope.7c6e9359.png differ diff --git a/assets/snippets.1ed82d05.png b/assets/snippets.1ed82d05.png new file mode 100644 index 0000000000..46455428f9 Binary files /dev/null and b/assets/snippets.1ed82d05.png differ diff --git a/assets/snippets.html.19dc3661.js b/assets/snippets.html.19dc3661.js new file mode 100644 index 0000000000..0a8367b8cf --- /dev/null +++ b/assets/snippets.html.19dc3661.js @@ -0,0 +1,42 @@ +import{_ as l,a as r}from"./snippet-scope.1f381b52.js";import{_ as c,o as d,c as u,a as s,b as e,d as n,w as o,f as i,r as p}from"./app.0e1565ce.js";const h={},m=i(`

    Snippets

    Snippets are an incredibly powerful way to quickly generate commonly needed code syntax from a shortcut.

    The idea is that you can type something like habtm and then press the Tab key and it will expand into has_and_belongs_to_many.

    Many Core and Community packages come bundled with their own snippets that are specific to it. For example, the language-html package that provides support for HTML syntax highlighting and grammar comes with dozens of snippets to create many of the various HTML tags you might want to use. If you create a new HTML file in Atom, you can type html and then press Tab and it will expand to:

    <!DOCTYPE html>
    +<html lang="en" dir="ltr">
    +	<head>
    +		<meta charset="utf-8" />
    +		<title></title>
    +	</head>
    +	<body></body>
    +</html>
    +

    It will also position the cursor in the lang attribute value so you can edit it if necessary. Many snippets have multiple focus points that you can move through with the Tab key as well - for instance, in the case of this HTML snippet, after the cursor is placed in the lang attribute value, you can continue pressing Tab and the cursor will move to the dir attribute value, then to the middle of the title tag, then finally to the middle of the body tag.

    To see all the available snippets for the file type that you currently have open, choose "Snippets: Available" in the Command Palette.

    View all available snippets

    You can also use fuzzy search to filter this list down by typing in the selection box. Selecting one of them will execute the snippet where your cursor is (or multiple cursors are).

    Creating Your Own Snippets

    So that's pretty cool, but what if there is something the language package didn't include or something that is custom to the code you write? Luckily it's incredibly easy to add your own snippets.

    There is a text file in your ~/.atom%USERPROFILE%\\.atom directory called snippets.cson that contains all your custom snippets that are loaded when you launch Atom. You can also easily open up that file by selecting the Atom > SnippetsEdit > SnippetsFile > Snippets menu.

    Snippet Format

    So let's look at how to write a snippet. The basic snippet format looks like this:

    '.source.js':
    +  'console.log':
    +    'prefix': 'log'
    +    'body': 'console.log(\${1:"crash"});$2'
    +

    The leftmost keys are the selectors where these snippets should be active. The easiest way to determine what this should be is to go to the language package of the language you want to add a snippet for and look for the "Scope" string.

    For example, if we wanted to add a snippet that would work for Java files, we would look up the language-java package in our Settings view and we can see the Scope is source.java. Then the top level snippet key would be that prepended by a period (like a CSS class selector would do).

    Finding the selector scope for a snippet

    The next level of keys are the snippet names. These are used for describing the snippet in a more readable way in the snippet menu. You can name them whatever you want.

    Under each snippet name is a prefix that should trigger the snippet and a body to insert when the snippet is triggered.

    Each $ followed by a number is a tab stop. Tab stops are cycled through by pressing Tab once a snippet has been triggered.

    Tab stops with the same number will create multiple cursors.

    The above example adds a log snippet to JavaScript files that would expand to:

    console.log("crash");
    +

    The string "crash" would be initially selected and pressing tab again would place the cursor after the ;

    `,25),g={class:"custom-container warning"},k=s("p",{class:"custom-container-title"},"Warning",-1),b=s("h5",{id:"multi-line-snippet-body",tabindex:"-1"},[s("a",{class:"header-anchor",href:"#multi-line-snippet-body","aria-hidden":"true"},"#"),e(" Multi-line Snippet Body")],-1),f={href:"http://coffeescript.org/#strings",target:"_blank",rel:"noopener noreferrer"},v=s("code",null,'"""',-1),y=i(`
    '.source.js':
    +  'if, else if, else':
    +    'prefix': 'ieie'
    +    'body': """
    +      if (\${1:true}) {
    +        $2
    +      } else if (\${3:false}) {
    +        $4
    +      } else {
    +        $5
    +      }
    +    """
    +

    As you might expect, there is a snippet to create snippets. If you open up a snippets file and type snip and then press Tab, you will get the following text inserted:

    '.source.js':
    +  'Snippet Name':
    +    'prefix': 'hello'
    +    'body': 'Hello World!'
    +

    \u{1F4A5} just fill that bad boy out and you have yourself a snippet. As soon as you save the file, Atom should reload the snippets and you will immediately be able to try it out.

    Multiple Snippets per Source

    You can see below the format for including multiple snippets for the same scope in your snippets.cson file. Just include the snippet name, prefix, and body keys for additional snippets inside the scope key:

    '.source.gfm':
    +  'Hello World':
    +    'prefix': 'hewo'
    +    'body': 'Hello World!'
    +
    +  'Github Hello':
    +    'prefix': 'gihe'
    +    'body': 'Octocat says Hi!'
    +
    +  'Octocat Image Link':
    +    'prefix': 'octopic'
    +    'body': '![GitHub Octocat](https://assets-cdn.github.com/images/modules/logos_page/Octocat.png)'
    +
    `,7),w=s("h4",{id:"more-info",tabindex:"-1"},[s("a",{class:"header-anchor",href:"#more-info","aria-hidden":"true"},"#"),e(" More Info")],-1),_={href:"https://github.com/atom/snippets",target:"_blank",rel:"noopener noreferrer"},x={href:"https://github.com/atom/language-html/blob/master/snippets/language-html.cson",target:"_blank",rel:"noopener noreferrer"},S={href:"https://github.com/atom/language-javascript/blob/master/snippets/language-javascript.cson",target:"_blank",rel:"noopener noreferrer"};function q(T,C){const t=p("RouterLink"),a=p("ExternalLinkIcon");return d(),u("div",null,[m,s("div",g,[k,s("p",null,[e("Snippet keys, unlike CSS selectors, can only be repeated once per level. If there are duplicate keys at the same level, then only the last one will be read. See "),n(t,{to:"/using-atom/sections/basic-customization/#configuring-with-cson"},{default:o(()=>[e("Configuring with CSON")]),_:1}),e(" for more information.")])]),b,s("p",null,[e("You can also use "),s("a",f,[e("CoffeeScript multi-line syntax"),n(a)]),e(" using "),v,e(" for larger templates:")]),y,s("p",null,[e("Again, see "),n(t,{to:"/using-atom/sections/basic-customization/#configuring-with-cson"},{default:o(()=>[e("Configuring with CSON")]),_:1}),e(" for more information on CSON key structure and non-repeatability.")]),w,s("p",null,[e("The snippets functionality is implemented in the "),s("a",_,[e("snippets"),n(a)]),e(" package.")]),s("p",null,[e("For more examples, see the snippets in the "),s("a",x,[e("language-html"),n(a)]),e(" and "),s("a",S,[e("language-javascript"),n(a)]),e(" packages.")])])}const H=c(h,[["render",q],["__file","snippets.html.vue"]]);export{H as default}; diff --git a/assets/snippets.html.2349e19b.js b/assets/snippets.html.2349e19b.js new file mode 100644 index 0000000000..92c926ca28 --- /dev/null +++ b/assets/snippets.html.2349e19b.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-3aec9d69","path":"/docs/atom-archive/using-atom/sections/snippets.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"Snippets","slug":"snippets","link":"#snippets","children":[]}],"git":{"updatedTime":1664634443000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1},{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":3.33,"words":998},"filePathRelative":"docs/atom-archive/using-atom/sections/snippets.md"}');export{e as data}; diff --git a/assets/snippets.html.f09874a6.js b/assets/snippets.html.f09874a6.js new file mode 100644 index 0000000000..c2a65fc7dc --- /dev/null +++ b/assets/snippets.html.f09874a6.js @@ -0,0 +1,42 @@ +import{_ as o,a as p}from"./snippet-scope.1f381b52.js";import{_ as i,o as r,c as l,a as s,b as e,d as a,f as t,r as c}from"./app.0e1565ce.js";const d={},u=t(`

    Snippets

    Snippets are an incredibly powerful way to quickly generate commonly needed code syntax from a shortcut.

    The idea is that you can type something like habtm and then press the Tab key and it will expand into has_and_belongs_to_many.

    Many Core and Community packages come bundled with their own snippets that are specific to it. For example, the language-html package that provides support for HTML syntax highlighting and grammar comes with dozens of snippets to create many of the various HTML tags you might want to use. If you create a new HTML file in Pulsar, you can type html and then press Tab and it will expand to:

    <!DOCTYPE html>
    +<html lang="en" dir="ltr">
    +	<head>
    +		<meta charset="utf-8" />
    +		<title></title>
    +	</head>
    +	<body></body>
    +</html>
    +

    It will also position the cursor in the lang attribute value so you can edit it if necessary. Many snippets have multiple focus points that you can move through with the Tab key as well - for instance, in the case of this HTML snippet, after the cursor is placed in the lang attribute value, you can continue pressing Tab and the cursor will move to the dir attribute value, then to the middle of the title tag, then finally to the middle of the body tag.

    To see all the available snippets for the file type that you currently have open, choose "Snippets: Available" in the Command Palette.

    View all available snippets

    You can also use fuzzy search to filter this list down by typing in the selection box. Selecting one of them will execute the snippet where your cursor is (or multiple cursors are).

    Creating Your Own Snippets

    So that's pretty cool, but what if there is something the language package didn't include or something that is custom to the code you write? Luckily it's incredibly easy to add your own snippets.

    There is a text file in your LNX/MAC: ~/.pulsar - WIN: %USERPROFILE%\\.pulsar directory called snippets.cson that contains all your custom snippets that are loaded when you launch Pulsar. You can also easily open up that file by selecting the LNX: Edit > Snippets - MAC: Pulsar > Snippets - WIN: File > Snippets menu.

    Snippet Format

    So let's look at how to write a snippet. The basic snippet format looks like this:

    '.source.js':
    +  'console.log':
    +    'prefix': 'log'
    +    'body': 'console.log(\${1:"crash"});$2'
    +

    The leftmost keys are the selectors where these snippets should be active. The easiest way to determine what this should be is to go to the language package of the language you want to add a snippet for and look for the "Scope" string.

    For example, if we wanted to add a snippet that would work for Java files, we would look up the language-java package in our Settings view and we can see the Scope is source.java. Then the top level snippet key would be that prepended by a period (like a CSS class selector would do).

    Finding the selector scope for a snippet

    The next level of keys are the snippet names. These are used for describing the snippet in a more readable way in the snippet menu. You can name them whatever you want.

    Under each snippet name is a prefix that should trigger the snippet and a body to insert when the snippet is triggered.

    Each $ followed by a number is a tab stop. Tab stops are cycled through by pressing Tab once a snippet has been triggered.

    Tab stops with the same number will create multiple cursors.

    The above example adds a log snippet to JavaScript files that would expand to:

    console.log("crash");
    +

    The string "crash" would be initially selected and pressing tab again would place the cursor after the ;

    Warning

    Snippet keys, unlike CSS selectors, can only be repeated once per level. If there are duplicate keys at the same level, then only the last one will be read. See Configuring with CSON for more information.

    Multi-line Snippet Body

    `,27),h={href:"http://coffeescript.org/#strings",target:"_blank",rel:"noopener noreferrer"},g=s("code",null,'"""',-1),m=t(`
    '.source.js':
    +  'if, else if, else':
    +    'prefix': 'ieie'
    +    'body': """
    +      if (\${1:true}) {
    +        $2
    +      } else if (\${3:false}) {
    +        $4
    +      } else {
    +        $5
    +      }
    +    """
    +

    As you might expect, there is a snippet to create snippets. If you open up a snippets file and type snip and then press Tab, you will get the following text inserted:

    '.source.js':
    +  'Snippet Name':
    +    'prefix': 'hello'
    +    'body': 'Hello World!'
    +

    \u{1F4A5} Just fill that bad boy out and you have yourself a snippet. As soon as you save the file, Pulsar should reload the snippets and you will immediately be able to try it out.

    Multiple Snippets per Source

    You can see below the format for including multiple snippets for the same scope in your snippets.cson file. Just include the snippet name, prefix, and body keys for additional snippets inside the scope key:

    '.source.gfm':
    +  'Hello World':
    +    'prefix': 'hewo'
    +    'body': 'Hello World!'
    +
    +  'Github Hello':
    +    'prefix': 'gihe'
    +    'body': 'Octocat says Hi!'
    +
    +  'Octocat Image Link':
    +    'prefix': 'octopic'
    +    'body': '![GitHub Octocat](https://assets-cdn.github.com/images/modules/logos_page/Octocat.png)'
    +

    Again, see Configuring with CSON for more information on CSON key structure and non-repeatability.

    More Info

    `,9),k={href:"https://github.com/pulsar-edit/snippets",target:"_blank",rel:"noopener noreferrer"},b={href:"https://github.com/pulsar-edit/language-html/blob/master/snippets/language-html.cson",target:"_blank",rel:"noopener noreferrer"},v={href:"https://github.com/pulsar-edit/language-javascript/blob/master/snippets/language-javascript.cson",target:"_blank",rel:"noopener noreferrer"};function y(f,w){const n=c("ExternalLinkIcon");return r(),l("div",null,[u,s("p",null,[e("You can also use "),s("a",h,[e("CoffeeScript multi-line syntax"),a(n)]),e(" using "),g,e(" for larger templates:")]),m,s("p",null,[e("The snippets functionality is implemented in the "),s("a",k,[e("snippets"),a(n)]),e(" package.")]),s("p",null,[e("For more examples, see the snippets in the "),s("a",b,[e("language-html"),a(n)]),e(" and "),s("a",v,[e("language-javascript"),a(n)]),e(" packages.")])])}const S=i(d,[["render",y],["__file","snippets.html.vue"]]);export{S as default}; diff --git a/assets/snippets.html.fd11bb00.js b/assets/snippets.html.fd11bb00.js new file mode 100644 index 0000000000..f8844c2ad7 --- /dev/null +++ b/assets/snippets.html.fd11bb00.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-20452234","path":"/docs/launch-manual/sections/using-pulsar/sections/snippets.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Snippets","slug":"snippets","link":"#snippets","children":[{"level":3,"title":"Creating Your Own Snippets","slug":"creating-your-own-snippets","link":"#creating-your-own-snippets","children":[]},{"level":3,"title":"More Info","slug":"more-info","link":"#more-info","children":[]}]}],"git":{"updatedTime":1669229414000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":4}]},"readingTime":{"minutes":3.17,"words":951},"filePathRelative":"docs/launch-manual/sections/using-pulsar/sections/snippets.md"}');export{e as data}; diff --git a/assets/spec-deps.9e42d44f.png b/assets/spec-deps.9e42d44f.png new file mode 100644 index 0000000000..d4cef39889 Binary files /dev/null and b/assets/spec-deps.9e42d44f.png differ diff --git a/assets/spec-deps.b3f6a1b6.js b/assets/spec-deps.b3f6a1b6.js new file mode 100644 index 0000000000..821cbbfae3 --- /dev/null +++ b/assets/spec-deps.b3f6a1b6.js @@ -0,0 +1 @@ +const s="/assets/spec-deps.9e42d44f.png";export{s as _}; diff --git a/assets/spec-suite.8f5e854d.js b/assets/spec-suite.8f5e854d.js new file mode 100644 index 0000000000..400445b89d --- /dev/null +++ b/assets/spec-suite.8f5e854d.js @@ -0,0 +1 @@ +const s="/assets/package.055df86c.png",t="/assets/menu.b336059b.png",c="/assets/context-menu.3bf0cb3c.png",o="/assets/toggle.8625d841.png",a="/assets/wordcount.cf347e3c.png",e="/assets/spec-suite.cccabf22.png";export{s as _,t as a,c as b,o as c,a as d,e}; diff --git a/assets/spec-suite.cccabf22.png b/assets/spec-suite.cccabf22.png new file mode 100644 index 0000000000..b887a307be Binary files /dev/null and b/assets/spec-suite.cccabf22.png differ diff --git a/assets/spellcheck.603fa2d2.png b/assets/spellcheck.603fa2d2.png new file mode 100644 index 0000000000..6970ec5ab9 Binary files /dev/null and b/assets/spellcheck.603fa2d2.png differ diff --git a/assets/spotlight.003b8888.png b/assets/spotlight.003b8888.png new file mode 100644 index 0000000000..0e95c0447f Binary files /dev/null and b/assets/spotlight.003b8888.png differ diff --git a/assets/spotlight.aba19e76.js b/assets/spotlight.aba19e76.js new file mode 100644 index 0000000000..ad92ce4516 --- /dev/null +++ b/assets/spotlight.aba19e76.js @@ -0,0 +1 @@ +const s="/assets/spotlight.003b8888.png";export{s as _}; diff --git a/assets/sql-filter-abomination.88955aa2.png b/assets/sql-filter-abomination.88955aa2.png new file mode 100644 index 0000000000..e6275bfc37 Binary files /dev/null and b/assets/sql-filter-abomination.88955aa2.png differ diff --git a/assets/star-history-2023610.47fda7b2.png b/assets/star-history-2023610.47fda7b2.png new file mode 100644 index 0000000000..c2e6033cea Binary files /dev/null and b/assets/star-history-2023610.47fda7b2.png differ diff --git a/assets/style.a535866b.css b/assets/style.a535866b.css new file mode 100644 index 0000000000..3ec7ccfa39 --- /dev/null +++ b/assets/style.a535866b.css @@ -0,0 +1 @@ +@import"https://fonts.googleapis.com/css2?family=Jura:wght@500&display=swap";:root{--theme-color: #3eaf7c}:root{--text-color: #2c3e50}html[data-theme=dark]{--text-color: #9e9e9e}:root{--bg-color: #fff}html[data-theme=dark]{--bg-color: #1d2025}:root{--bg-color-light: #fff}html[data-theme=dark]{--bg-color-light: #252934}:root{--bg-color-active: #f8f8f8}html[data-theme=dark]{--bg-color-active: #252934}:root{--bg-color-back: #f8f8f8}html[data-theme=dark]{--bg-color-back: #1d2025}:root{--bg-color-float: #fff}html[data-theme=dark]{--bg-color-float: #252934}:root{--bg-color-blur: rgba(255, 255, 255, .9)}html[data-theme=dark]{--bg-color-blur: rgba(29, 32, 37, .9)}:root{--bg-color-float-blur: rgba(255, 255, 255, .9)}html[data-theme=dark]{--bg-color-float-blur: rgba(37, 41, 52, .9)}:root{--border-color: #eaecef}html[data-theme=dark]{--border-color: #302d28}:root{--box-shadow: #f0f1f2}html[data-theme=dark]{--box-shadow: #282a32}:root{--card-shadow: rgba(0, 0, 0, .15)}html[data-theme=dark]{--card-shadow: rgba(0, 0, 0, .3)}:root{--black: #000}html[data-theme=dark]{--black: #fff}:root{--dark-grey: #666}html[data-theme=dark]{--dark-grey: #999}:root{--light-grey: #999}html[data-theme=dark]{--light-grey: #666}:root{--white: #fff}html[data-theme=dark]{--white: #000}:root{--grey3: #333}html[data-theme=dark]{--grey3: #bbb}:root{--grey12: #bbb}html[data-theme=dark]{--grey12: #333}:root{--grey14: #eee}html[data-theme=dark]{--grey14: #111}:root{--navbar-height: 3.75rem}:root{--navbar-horizontal-padding: 1.5rem}:root{--navbar-vertical-padding: .7rem}:root{--navbar-mobile-height: 3.25rem}:root{--navbar-mobile-horizontal-padding: 1rem}:root{--navbar-mobile-vertical-padding: .5rem}:root{--sidebar-width: 18rem}:root{--sidebar-mobile-width: 16rem}:root{--content-width: 740px}:root{--home-page-width: 1160px}:root{--font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", STHeiti, "Microsoft YaHei", SimSun, sans-serif}:root{--font-family-fancy: Jura, sans-serif}:root{--font-family-code: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace}:root{--line-numbers-width: 2.5rem}:root{--color-transition: .3s ease}:root{--transform-transition: .3s ease}:root{--text-color-light: #3a5169}html[data-theme=dark]{--text-color-light: #a8a8a8}:root{--text-color-lighter: #476582}html[data-theme=dark]{--text-color-lighter: #b1b1b1}:root{--text-color-bright: #6a8bad}html[data-theme=dark]{--text-color-bright: #c5c5c5}:root{--theme-color-dark: #389e70}:root{--theme-color-light: #4abf8a}:root{--theme-color-mask: rgba(62, 175, 124, .15)}:root{--badge-tip-color: #42b983;--badge-warning-color: #f4cd00;--badge-danger-color: #f55;--badge-info-color: #0295ff;--badge-note-color: #666}.badge{display:inline-block;vertical-align:top;height:18px;padding:0 6px;border-radius:3px;background-color:var(--c-brand, #3eaf7c);color:var(--white);font-size:14px;line-height:18px;transition:background-color var(--color-transition, .3s ease),color var(--color-transition, .3s ease)}.table-of-contents .badge{vertical-align:middle}.badge.tip{background-color:var(--badge-tip-color)}.badge.warning{background-color:var(--badge-warning-color)}.badge.danger{background-color:var(--badge-danger-color)}.badge.info{background-color:var(--badge-info-color)}.badge.note{background-color:var(--badge-note-color)}.badge+.badge{margin-left:5px}.back-to-top{border-width:0;background-color:transparent;cursor:pointer;position:fixed!important;right:1rem;bottom:4rem;z-index:100;width:3rem;height:3rem;padding:.5rem;border-radius:1rem;background-color:var(--c-bg, #fff);color:var(--c-brand, #3eaf7c);box-shadow:2px 2px 10px 0 var(--card-shadow)}@media (max-width: 719px){.back-to-top{width:2.25rem;height:2.25rem;padding:.25rem;border-radius:.5rem}}.back-to-top:hover{color:var(--c-brand-light, #4abf8a)}.back-to-top svg{overflow:hidden;width:100%;border-radius:50%;fill:currentcolor}.fade-enter-active,.fade-leave-active{transition:opacity var(--color-transition, .3s ease)}.fade-enter,.fade-leave-to{opacity:0}:root{--external-link-icon-color: #aaa}.external-link-icon{position:relative;display:inline-block;color:var(--external-link-icon-color);vertical-align:middle;top:-1px}.external-link-icon-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0;-webkit-user-select:none;-moz-user-select:none;user-select:none}:root{--nprogress-color: #29d;--nprogress-z-index: 1031}#nprogress{pointer-events:none}#nprogress .bar{background:var(--nprogress-color);position:fixed;z-index:var(--nprogress-z-index);top:0;left:0;width:100%;height:2px}@keyframes message-move-in{0%{opacity:0;transform:translateY(-100%)}to{opacity:1;transform:translateY(0)}}@keyframes message-move-out{0%{opacity:1;transform:translateY(0)}to{opacity:0;transform:translateY(-100%)}}#message-container{position:fixed;top:calc(var(--navbar-height) + 1rem);right:0;left:0;z-index:75;text-align:center}#message-container .message{display:inline-block;padding:8px 10px;border-radius:3px;background:var(--c-bg, #fff);color:var(--c-text, #2c3e50);box-shadow:0 0 10px 0 var(--box-shadow, #f0f1f2);font-size:14px;transition:height .2s ease-in-out,margin .2s ease-in-out}#message-container .message.move-in{animation:message-move-in .3s ease-in-out}#message-container .message.move-out{animation:message-move-out .3s ease-in-out;animation-fill-mode:forwards}#message-container .message svg{position:relative;bottom:-.125em;margin-right:5px}:root{--balloon-border-radius: 2px;--balloon-color: rgba(16, 16, 16, .95);--balloon-text-color: #fff;--balloon-font-size: 12px;--balloon-move: 4px}button[aria-label][data-balloon-pos]{overflow:visible}[aria-label][data-balloon-pos]{position:relative;cursor:pointer}[aria-label][data-balloon-pos]:after{opacity:0;pointer-events:none;transition:all .18s ease-out .18s;text-indent:0;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Open Sans,Helvetica Neue,sans-serif;font-weight:400;font-style:normal;text-shadow:none;font-size:var(--balloon-font-size);background:var(--balloon-color);border-radius:2px;color:var(--balloon-text-color);border-radius:var(--balloon-border-radius);content:attr(aria-label);padding:.5em 1em;position:absolute;white-space:nowrap;z-index:10}[aria-label][data-balloon-pos]:before{width:0;height:0;border:5px solid transparent;border-top-color:var(--balloon-color);opacity:0;pointer-events:none;transition:all .18s ease-out .18s;content:"";position:absolute;z-index:10}[aria-label][data-balloon-pos]:hover:before,[aria-label][data-balloon-pos]:hover:after,[aria-label][data-balloon-pos][data-balloon-visible]:before,[aria-label][data-balloon-pos][data-balloon-visible]:after,[aria-label][data-balloon-pos]:not([data-balloon-nofocus]):focus:before,[aria-label][data-balloon-pos]:not([data-balloon-nofocus]):focus:after{opacity:1;pointer-events:none}[aria-label][data-balloon-pos].font-awesome:after{font-family:FontAwesome,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Open Sans,Helvetica Neue,sans-serif}[aria-label][data-balloon-pos][data-balloon-break]:after{white-space:pre}[aria-label][data-balloon-pos][data-balloon-break][data-balloon-length]:after{white-space:pre-line;word-break:break-word}[aria-label][data-balloon-pos][data-balloon-blunt]:before,[aria-label][data-balloon-pos][data-balloon-blunt]:after{transition:none}[aria-label][data-balloon-pos][data-balloon-pos=up]:hover:after,[aria-label][data-balloon-pos][data-balloon-pos=up][data-balloon-visible]:after,[aria-label][data-balloon-pos][data-balloon-pos=down]:hover:after,[aria-label][data-balloon-pos][data-balloon-pos=down][data-balloon-visible]:after{transform:translate(-50%)}[aria-label][data-balloon-pos][data-balloon-pos=up]:hover:before,[aria-label][data-balloon-pos][data-balloon-pos=up][data-balloon-visible]:before,[aria-label][data-balloon-pos][data-balloon-pos=down]:hover:before,[aria-label][data-balloon-pos][data-balloon-pos=down][data-balloon-visible]:before{transform:translate(-50%)}[aria-label][data-balloon-pos][data-balloon-pos*=-left]:after{left:0}[aria-label][data-balloon-pos][data-balloon-pos*=-left]:before{left:5px}[aria-label][data-balloon-pos][data-balloon-pos*=-right]:after{right:0}[aria-label][data-balloon-pos][data-balloon-pos*=-right]:before{right:5px}[aria-label][data-balloon-pos][data-balloon-po*=-left]:hover:after,[aria-label][data-balloon-pos][data-balloon-po*=-left][data-balloon-visible]:after,[aria-label][data-balloon-pos][data-balloon-pos*=-right]:hover:after,[aria-label][data-balloon-pos][data-balloon-pos*=-right][data-balloon-visible]:after{transform:translate(0)}[aria-label][data-balloon-pos][data-balloon-po*=-left]:hover:before,[aria-label][data-balloon-pos][data-balloon-po*=-left][data-balloon-visible]:before,[aria-label][data-balloon-pos][data-balloon-pos*=-right]:hover:before,[aria-label][data-balloon-pos][data-balloon-pos*=-right][data-balloon-visible]:before{transform:translate(0)}[aria-label][data-balloon-pos][data-balloon-pos^=up]:before,[aria-label][data-balloon-pos][data-balloon-pos^=up]:after{bottom:100%;transform-origin:top;transform:translateY(var(--balloon-move))}[aria-label][data-balloon-pos][data-balloon-pos^=up]:after{margin-bottom:10px}[aria-label][data-balloon-pos][data-balloon-pos=up]:before,[aria-label][data-balloon-pos][data-balloon-pos=up]:after{left:50%;transform:translate(-50%,var(--balloon-move))}[aria-label][data-balloon-pos][data-balloon-pos^=down]:before,[aria-label][data-balloon-pos][data-balloon-pos^=down]:after{top:100%;transform:translateY(calc(var(--balloon-move) * -1))}[aria-label][data-balloon-pos][data-balloon-pos^=down]:after{margin-top:10px}[aria-label][data-balloon-pos][data-balloon-pos^=down]:before{width:0;height:0;border:5px solid transparent;border-bottom-color:var(--balloon-color)}[aria-label][data-balloon-pos][data-balloon-pos=down]:after,[aria-label][data-balloon-pos][data-balloon-pos=down]:before{left:50%;transform:translate(-50%,calc(var(--balloon-move) * -1))}[aria-label][data-balloon-pos][data-balloon-pos=left]:hover:after,[aria-label][data-balloon-pos][data-balloon-pos=left][data-balloon-visible]:after,[aria-label][data-balloon-pos][data-balloon-pos=right]:hover:after,[aria-label][data-balloon-pos][data-balloon-pos=right][data-balloon-visible]:after{transform:translateY(-50%)}[aria-label][data-balloon-pos][data-balloon-pos=left]:hover:before,[aria-label][data-balloon-pos][data-balloon-pos=left][data-balloon-visible]:before,[aria-label][data-balloon-pos][data-balloon-pos=right]:hover:before,[aria-label][data-balloon-pos][data-balloon-pos=right][data-balloon-visible]:before{transform:translateY(-50%)}[aria-label][data-balloon-pos][data-balloon-pos=left]:after,[aria-label][data-balloon-pos][data-balloon-pos=left]:before{right:100%;top:50%;transform:translate(var(--balloon-move),-50%)}[aria-label][data-balloon-pos][data-balloon-pos=left]:after{margin-right:10px}[aria-label][data-balloon-pos][data-balloon-pos=left]:before{width:0;height:0;border:5px solid transparent;border-left-color:var(--balloon-color)}[aria-label][data-balloon-pos][data-balloon-pos=right]:after,[aria-label][data-balloon-pos][data-balloon-pos=right]:before{left:100%;top:50%;transform:translate(calc(var(--balloon-move) * -1),-50%)}[aria-label][data-balloon-pos][data-balloon-pos=right]:after{margin-left:10px}[aria-label][data-balloon-pos][data-balloon-pos=right]:before{width:0;height:0;border:5px solid transparent;border-right-color:var(--balloon-color)}[aria-label][data-balloon-pos][data-balloon-length]:after{white-space:normal}[aria-label][data-balloon-pos][data-balloon-length=small]:after{width:80px}[aria-label][data-balloon-pos][data-balloon-length=medium]:after{width:150px}[aria-label][data-balloon-pos][data-balloon-length=large]:after{width:260px}[aria-label][data-balloon-pos][data-balloon-length=xlarge]:after{width:380px}@media screen and (max-width: 768px){[aria-label][data-balloon-pos][data-balloon-length=xlarge]:after{width:90vw}}[aria-label][data-balloon-pos][data-balloon-length=fit]:after{width:100%}:root{--copy-icon: url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' height='20' width='20' stroke='rgba(128,128,128,1)' stroke-width='2'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M9 5H7a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-2M9 5a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2M9 5a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2'/%3E%3C/svg%3E");--copied-icon: url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' height='20' width='20' stroke='rgba(128,128,128,1)' stroke-width='2'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M9 5H7a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-2M9 5a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2M9 5a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2m-6 9 2 2 4-4'/%3E%3C/svg%3E")}div[class*=language-]>button.copy{border-width:0;background-color:transparent;position:absolute;outline:none;cursor:pointer}div[class*=language-]>button.copy .copy-icon{background-color:currentcolor;-webkit-mask-image:var(--copy-icon);mask-image:var(--copy-icon);-webkit-mask-position:50%;mask-position:50%;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:1em;mask-size:1em}div[class*=language-]>button.copy:not(.pure){right:-14px;bottom:-14px;z-index:5;width:2rem;height:2rem;padding:7px 8px;border-radius:50%;background:#339af0;color:#fff}@media (max-width: 419px){div[class*=language-]>button.copy:not(.pure){right:0;bottom:0;width:28px;height:28px;border-radius:50% 10% 0}}div[class*=language-]>button.copy:not(.pure):hover{background:#228be6}div[class*=language-]>button.copy:not(.pure) .copy-icon{width:100%;height:100%;color:#fff;font-size:1.25rem}@media (max-width: 419px){div[class*=language-]>button.copy:not(.pure) .copy-icon{position:relative;top:2px;left:2px}}div[class*=language-]>button.copy.pure{border-width:0;background-color:transparent;cursor:pointer;position:absolute;top:0;right:3em;z-index:5;width:2.5rem;height:2.5rem;padding:0;border-radius:.5rem;opacity:0;transition:opacity .4s}div[class*=language-]>button.copy.pure:hover{background-color:var(--code-hl-bg-color, rgba(0, 0, 0, .66))}div[class*=language-]>button.copy.pure.copied:after{content:attr(data-copied);position:absolute;top:0;right:100%;display:block;height:1.25rem;padding:.625rem;border-radius:.5rem;background-color:var(--code-hl-bg-color, rgba(0, 0, 0, .66));font-weight:500;line-height:1.25rem}div[class*=language-]>button.copy.pure .copy-icon{width:1.25rem;height:1.25rem;padding:.625rem;color:var(--code-ln-color, #9e9e9e);font-size:1.25rem}div[class*=language-]>button.copy.copied .copy-icon{-webkit-mask-image:var(--copied-icon);mask-image:var(--copied-icon)}div[class*=language-]:hover>button.copy.pure,div[class*=language-]>button.copy.pure:focus{opacity:1}:root{--info-title-color: #193c47;--info-bg-color: #eef9fd;--info-border-color: #4cb3d4;--note-title-color: #474748;--note-bg-color: #fdfdfe;--note-border-color: #d4d5d8;--tip-title-color: #003100;--tip-bg-color: #e6f6e6;--tip-border-color: #009400;--warning-title-color: #4d3800;--warning-bg-color: #fff8e6;--warning-border-color: #e6a700;--danger-title-color: #4b1113;--danger-bg-color: #ffebec;--danger-border-color: #e13238;--detail-bg-color: #eee;--detail-text-color: inherit}html[data-theme=dark]{--info-title-color: #eef9fd;--info-bg-color: #193c47;--note-title-color: #fdfdfe;--note-bg-color: #474748;--tip-title-color: #e6f6e6;--tip-bg-color: #003100;--warning-title-color: #fff8e6;--warning-bg-color: #4d3800;--danger-title-color: #ffebec;--danger-bg-color: #4b1113;--detail-bg-color: #333;--detail-text-color: #a8a8a8}.custom-container{position:relative;transition:background-color var(--color-transition),border-color var(--color-transition),color var(--color-transition)}.custom-container .custom-container-title{position:relative;font-weight:600;line-height:1.25}.custom-container.info,.custom-container.note,.custom-container.tip,.custom-container.warning,.custom-container.danger{margin:1rem 0;padding:.25rem 1rem;border-left-width:.3rem;border-left-style:solid;border-radius:.5rem;color:inherit}.custom-container.info .custom-container-title,.custom-container.note .custom-container-title,.custom-container.tip .custom-container-title,.custom-container.warning .custom-container-title,.custom-container.danger .custom-container-title{padding-left:1.5rem}.custom-container.info .custom-container-title:before,.custom-container.note .custom-container-title:before,.custom-container.tip .custom-container-title:before,.custom-container.warning .custom-container-title:before,.custom-container.danger .custom-container-title:before{content:" ";position:absolute;left:0;width:20px;height:20px;background-position:left;background-repeat:no-repeat}.custom-container.info p,.custom-container.note p,.custom-container.tip p,.custom-container.warning p,.custom-container.danger p{line-height:1.5}.custom-container.info a,.custom-container.note a,.custom-container.tip a,.custom-container.warning a,.custom-container.danger a{color:var(--c-brand, #3eaf7c)}.custom-container.info{border-color:var(--info-border-color);background:var(--info-bg-color)}.custom-container.info .custom-container-title{color:var(--info-title-color)}.custom-container.info .custom-container-title:before{background-image:url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 16'%3E%3Cpath fill='%23193c47' fill-rule='evenodd' d='M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z'/%3E%3C/svg%3E")}html[data-theme=dark] .custom-container.info .custom-container-title:before{background-image:url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 16'%3E%3Cpath fill='%23eef9fd' fill-rule='evenodd' d='M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z'/%3E%3C/svg%3E")}.custom-container.note{border-color:var(--note-border-color);background:var(--note-bg-color)}.custom-container.note .custom-container-title{color:var(--note-title-color)}.custom-container.note .custom-container-title:before{background-image:url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 16'%3E%3Cpath fill='%23474748' fill-rule='evenodd' d='M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z'/%3E%3C/svg%3E")}html[data-theme=dark] .custom-container.note .custom-container-title:before{background-image:url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 16'%3E%3Cpath fill='%23fdfdfe' fill-rule='evenodd' d='M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z'/%3E%3C/svg%3E")}.custom-container.tip{border-color:var(--tip-border-color);background:var(--tip-bg-color)}.custom-container.tip .custom-container-title{color:var(--tip-title-color)}.custom-container.tip .custom-container-title:before{background-image:url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 16'%3E%3Cpath fill='%23003100' fill-rule='evenodd' d='M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z'/%3E%3C/svg%3E")}html[data-theme=dark] .custom-container.tip .custom-container-title:before{background-image:url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 16'%3E%3Cpath fill='%23e6f6e6' fill-rule='evenodd' d='M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z'/%3E%3C/svg%3E")}.custom-container.warning{border-color:var(--warning-border-color);background:var(--warning-bg-color)}.custom-container.warning .custom-container-title{color:var(--warning-title-color)}.custom-container.warning .custom-container-title:before{background-image:url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill='%234d3800' fill-rule='evenodd' d='M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z'/%3E%3C/svg%3E")}html[data-theme=dark] .custom-container.warning .custom-container-title:before{background-image:url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill='%23fff8e6' fill-rule='evenodd' d='M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z'/%3E%3C/svg%3E")}.custom-container.danger{border-color:var(--danger-border-color);background:var(--danger-bg-color)}.custom-container.danger .custom-container-title{color:var(--danger-title-color)}.custom-container.danger .custom-container-title:before{background-image:url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1024 1024'%3E%3Cpath d='M147.911 833.422V560.356a364.089 364.089 0 1 1 728.178 0v273.066H921.6v91.022H102.4v-91.022h45.511zm91.022-273.066h91.023A182.044 182.044 0 0 1 512 378.31v-91.02a273.067 273.067 0 0 0-273.067 273.067zM466.49 14.222h91.022v136.534H466.49V14.222zm399.496 127.795 64.353 64.353-96.483 96.53-64.399-64.354 96.53-96.529zM93.662 206.37l64.353-64.353 96.529 96.484-64.308 64.444-96.574-96.575z' fill='%234b1113'/%3E%3C/svg%3E")}html[data-theme=dark] .custom-container.danger .custom-container-title:before{background-image:url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1024 1024'%3E%3Cpath d='M147.911 833.422V560.356a364.089 364.089 0 1 1 728.178 0v273.066H921.6v91.022H102.4v-91.022h45.511zm91.022-273.066h91.023A182.044 182.044 0 0 1 512 378.31v-91.02a273.067 273.067 0 0 0-273.067 273.067zM466.49 14.222h91.022v136.534H466.49V14.222zm399.496 127.795 64.353 64.353-96.483 96.53-64.399-64.354 96.53-96.529zM93.662 206.37l64.353-64.353 96.529 96.484-64.308 64.444-96.574-96.575z' fill='%23ffebec'/%3E%3C/svg%3E")}.custom-container.details{position:relative;display:block;margin:1.6em 0;padding:1.5rem;border-radius:.5rem;background:var(--detail-bg-color);color:var(--detail-text-color);transition:all var(--transform-transition)}.custom-container.details h4{margin-top:0}.custom-container.details figure:last-child,.custom-container.details p:last-child{margin-bottom:0;padding-bottom:0}.custom-container.details a{color:var(--c-brand, #3eaf7c)}.custom-container.details summary{position:relative;padding-left:2.5rem;outline:none;list-style:none;cursor:pointer}.custom-container.details summary::-webkit-details-marker,.custom-container.details summary::marker{color:transparent;font-size:0}.custom-container.details summary:before,.custom-container.details summary:after{content:" ";position:absolute;top:calc(50% - .75rem);left:0;width:1.5rem;height:1.5rem}.custom-container.details summary:before{border-radius:50%;background:#ccc;transition:all var(--transform-transition)}html[data-theme=dark] .custom-container.details summary:before{background:#555}.custom-container.details summary:after{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='rgba(0,0,0,0.5)' d='M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z'/%3E%3C/svg%3E");line-height:normal;transition:all var(--transform-transition);transform:rotate(90deg)}html[data-theme=dark] .custom-container.details summary:after{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='rgba(255,255,255,0.5)' d='M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z'/%3E%3C/svg%3E")}.custom-container.details[open] summary{margin-bottom:.5em}.custom-container.details[open] summary:after{transform:rotate(180deg)}.footnote-item{margin-top:calc(0rem - var(--navbar-height));padding-top:calc(var(--navbar-height) + .5rem)}.footnote-item>p{margin-bottom:0}.footnote-ref{position:relative}.footnote-anchor{position:absolute;top:calc(-.5rem - var(--navbar-height))}:root{--tab-bg-color: var(--c-bg);--tab-nav-text-color: var(--c-text);--tab-nav-bg-color: #e0e0e0;--tab-nav-hover-color: #eee}html[data-theme=dark]{--tab-nav-bg-color: #34343f;--tab-nav-hover-color: #2d2d38}.tab-list{margin:1.5rem 0;border:2px solid var(--c-border);border-radius:8px}@media (max-width: 419px){.theme-hope-content>.tab-list{margin-right:-1.5rem;margin-left:-1.5rem;border-radius:0}}.tab-list-nav{overflow-x:auto;margin:0;padding:0;border-radius:8px 8px 0 0;background-color:var(--tab-nav-bg-color);list-style:none;white-space:nowrap;transition:background-color var(--color-transition, .3s ease)}@media (max-width: 419px){.tab-list-nav{border-radius:0}}.tab-list-nav-item{border-width:0;position:relative;min-width:4rem;margin:0;padding:.5em 1em;border-radius:8px 8px 0 0;background-color:transparent;color:var(--tab-nav-text-color);font-weight:600;font-size:.85em;line-height:1.75;cursor:pointer;transition:background-color var(--color-transition, .3s ease),color var(--color-transition, .3s ease)}.tab-list-nav-item:hover{background-color:var(--tab-nav-hover-color)}.tab-list-nav-item:before,.tab-list-nav-item:after{content:" ";position:absolute;bottom:0;z-index:1;width:8px;height:8px}.tab-list-nav-item:before{right:100%}.tab-list-nav-item:after{left:100%}.tab-list-nav-item.active{background-color:var(--tab-bg-color)}.tab-list-nav-item.active:before{background:radial-gradient(16px at left top,transparent 50%,var(--tab-bg-color) 50%)}.tab-list-nav-item.active:after{background:radial-gradient(16px at right top,transparent 50%,var(--tab-bg-color) 50%)}.tab-list-nav-item:first-child:before{display:none}.tab-item{display:none;padding:1rem .75rem;background:var(--tab-bg-color)}.tab-item.active{display:block}/*! PhotoSwipe main CSS by Dmytro Semenov | photoswipe.com */.pswp{--pswp-bg: #000;--pswp-placeholder-bg: #222;--pswp-root-z-index: 100000;--pswp-preloader-color: rgba(79, 79, 79, .4);--pswp-preloader-color-secondary: rgba(255, 255, 255, .9);--pswp-icon-color: #fff;--pswp-icon-color-secondary: #4f4f4f;--pswp-icon-stroke-color: #4f4f4f;--pswp-icon-stroke-width: 2px;--pswp-error-text-color: var(--pswp-icon-color)}.pswp{position:fixed;top:0;left:0;width:100%;height:100%;z-index:var(--pswp-root-z-index);display:none;touch-action:none;outline:0;opacity:.003;contain:layout style size;-webkit-tap-highlight-color:rgba(0,0,0,0)}.pswp:focus{outline:0}.pswp *{box-sizing:border-box}.pswp img{max-width:none}.pswp--open{display:block}.pswp,.pswp__bg{transform:translateZ(0);will-change:opacity}.pswp__bg{opacity:.005;background:var(--pswp-bg)}.pswp,.pswp__scroll-wrap{overflow:hidden}.pswp__scroll-wrap,.pswp__bg,.pswp__container,.pswp__item,.pswp__content,.pswp__img,.pswp__zoom-wrap{position:absolute;top:0;left:0;width:100%;height:100%}.pswp__img,.pswp__zoom-wrap{width:auto;height:auto}.pswp--click-to-zoom.pswp--zoom-allowed .pswp__img{cursor:zoom-in}.pswp--click-to-zoom.pswp--zoomed-in .pswp__img{cursor:move;cursor:grab}.pswp--click-to-zoom.pswp--zoomed-in .pswp__img:active{cursor:grabbing}.pswp--no-mouse-drag.pswp--zoomed-in .pswp__img,.pswp--no-mouse-drag.pswp--zoomed-in .pswp__img:active,.pswp__img{cursor:zoom-out}.pswp__container,.pswp__img,.pswp__button,.pswp__counter{-webkit-user-select:none;-moz-user-select:none;user-select:none}.pswp__item{z-index:1;overflow:hidden}.pswp__hidden{display:none!important}.pswp__content{pointer-events:none}.pswp__content>*{pointer-events:auto}.pswp__error-msg-container{display:grid}.pswp__error-msg{margin:auto;font-size:1em;line-height:1;color:var(--pswp-error-text-color)}.pswp .pswp__hide-on-close{opacity:.005;will-change:opacity;transition:opacity var(--pswp-transition-duration) cubic-bezier(.4,0,.22,1);z-index:10;pointer-events:none}.pswp--ui-visible .pswp__hide-on-close{opacity:1;pointer-events:auto}.pswp__button{position:relative;display:block;width:50px;height:60px;padding:0;margin:0;overflow:hidden;cursor:pointer;background:none;border:0;box-shadow:none;opacity:.85;-webkit-appearance:none;-webkit-touch-callout:none}.pswp__button:hover,.pswp__button:active,.pswp__button:focus{transition:none;padding:0;background:none;border:0;box-shadow:none;opacity:1}.pswp__button:disabled{opacity:.3;cursor:auto}.pswp__icn{fill:var(--pswp-icon-color);color:var(--pswp-icon-color-secondary)}.pswp__icn{position:absolute;top:14px;left:9px;width:32px;height:32px;overflow:hidden;pointer-events:none}.pswp__icn-shadow{stroke:var(--pswp-icon-stroke-color);stroke-width:var(--pswp-icon-stroke-width);fill:none}.pswp__icn:focus{outline:0}div.pswp__img--placeholder,.pswp__img--with-bg{background:var(--pswp-placeholder-bg)}.pswp__top-bar{position:absolute;left:0;top:0;width:100%;height:60px;display:flex;flex-direction:row;justify-content:flex-end;z-index:10;pointer-events:none!important}.pswp__top-bar>*{pointer-events:auto;will-change:opacity}.pswp__button--close{margin-right:6px}.pswp__button--arrow{position:absolute;top:0;width:75px;height:100px;top:50%;margin-top:-50px}.pswp__button--arrow:disabled{display:none;cursor:default}.pswp__button--arrow .pswp__icn{top:50%;margin-top:-30px;width:60px;height:60px;background:none;border-radius:0}.pswp--one-slide .pswp__button--arrow{display:none}.pswp--touch .pswp__button--arrow{visibility:hidden}.pswp--has_mouse .pswp__button--arrow{visibility:visible}.pswp__button--arrow--prev{right:auto;left:0px}.pswp__button--arrow--next{right:0px}.pswp__button--arrow--next .pswp__icn{left:auto;right:14px;transform:scaleX(-1)}.pswp__button--zoom{display:none}.pswp--zoom-allowed .pswp__button--zoom{display:block}.pswp--zoomed-in .pswp__zoom-icn-bar-v{display:none}.pswp__preloader{position:relative;overflow:hidden;width:50px;height:60px;margin-right:auto}.pswp__preloader .pswp__icn{opacity:0;transition:opacity .2s linear;animation:pswp-clockwise .6s linear infinite}.pswp__preloader--active .pswp__icn{opacity:.85}@keyframes pswp-clockwise{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.pswp__counter{height:30px;margin:15px 0 0 20px;font-size:14px;line-height:30px;color:var(--pswp-icon-color);text-shadow:1px 1px 3px var(--pswp-icon-color-secondary);opacity:.85}.pswp--one-slide .pswp__counter{display:none}.footer-wrapper{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-evenly;padding-left:.75rem 2rem .75rem calc(var(--sidebar-width) + 2rem);border-top:1px solid var(--border-color);background:var(--bg-color);color:var(--dark-color);text-align:center;transition:border-top-color var(--color-transition),background var(--color-transition)}@media (max-width: 719px){.footer-wrapper{padding-left:2rem}}.no-sidebar .footer-wrapper,.sidebar-collapsed .footer-wrapper{padding-left:2rem}@media (max-width: 419px){.footer-wrapper>div{width:100%}}.footer-wrapper .footer{margin:.5rem 1rem;font-size:14px}.footer-wrapper .copyright{margin:6px 0;font-size:13px}.page:not(.not-found)+.footer-wrapper{margin-top:-2rem}.toggle-sidebar-wrapper{position:fixed;top:var(--navbar-height);bottom:0;left:var(--sidebar-width);z-index:100;display:flex;align-items:center;justify-content:center;font-size:2rem;transition:left var(--transform-transition)}@media (max-width: 719px){.toggle-sidebar-wrapper{display:none}}.toggle-sidebar-wrapper:hover{background-color:#7f7f7f0d;cursor:pointer}.toggle-sidebar-wrapper .arrow{display:inline-block;vertical-align:middle;width:1em;height:1em;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='rgba(0,0,0,0.5)' d='M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z'/%3E%3C/svg%3E");line-height:normal;transition:all .3s}html[data-theme=dark] .toggle-sidebar-wrapper .arrow{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='rgba(255,255,255,0.5)' d='M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z'/%3E%3C/svg%3E")}.toggle-sidebar-wrapper .arrow.down{transform:rotate(180deg)}.toggle-sidebar-wrapper .arrow.right{transform:rotate(90deg)}.toggle-sidebar-wrapper .arrow.left{transform:rotate(-90deg)}.theme-container .page{padding-top:var(--navbar-height);padding-left:calc(var(--sidebar-width) + 2rem)}@media (max-width: 719px){.theme-container .page{padding-left:0}}.theme-container .sidebar{top:var(--navbar-height)}.theme-container.no-navbar .page{padding-top:0}.theme-container.no-navbar .sidebar{top:0}@media (max-width: 719px){.theme-container.no-navbar .sidebar{top:0}}@media (max-width: 719px){.theme-container.hide-navbar .sidebar{top:0}}.theme-container.no-sidebar .page{padding-left:0}.theme-container.no-sidebar .toggle-sidebar-button,.theme-container.no-sidebar .toggle-sidebar-wrapper,.theme-container.no-sidebar .sidebar{display:none}.theme-container.sidebar-open .sidebar{box-shadow:2px 0 8px var(--card-shadow);transform:translate(0)}.theme-container.sidebar-collapsed .page{padding-left:2rem}.theme-container.sidebar-collapsed .sidebar{box-shadow:none;transform:translate(-100%)}.theme-container.sidebar-collapsed .toggle-sidebar-wrapper{left:0}@media (min-width: 1440px){.theme-container.has-toc .page{padding-right:16rem}}.theme-hope-content.custom{margin:0;padding:0}.theme-hope-content.custom img{max-width:100%}.theme-hope-content:not(.custom){max-width:var(--content-width);margin:0 auto;padding:2rem 2.5rem;padding-top:0}@media (max-width: 959px){.theme-hope-content:not(.custom){padding:1.5rem}}@media (max-width: 419px){.theme-hope-content:not(.custom){padding:1rem 1.5rem}}.theme-hope-content:not(.custom) a:hover{text-decoration:underline}.theme-hope-content:not(.custom) img{max-width:100%}.action-button{display:inline-block;overflow:hidden;margin:.6rem .8rem;padding:.75rem 1.5rem;border:2px solid var(--theme-color);border-radius:.5rem;color:var(--theme-color);font-size:1.2rem;transition:background-color var(--color-transition),color var(--color-transition)}@media (max-width: 719px){.action-button{padding:.8rem 1.2rem;font-size:1.1rem}}@media (max-width: 419px){.action-button{font-size:1rem}}.action-button:hover,.action-button.primary{background-color:var(--theme-color);color:var(--white)}.action-button.primary:hover{border-color:var(--theme-color-light);background-color:var(--theme-color-light)}.home.project{display:block;max-width:var(--home-page-width);min-height:calc(100vh - var(--navbar-height));margin:0 auto;padding:var(--navbar-height) 2rem 0}@media (max-width: 419px){.home.project{padding-right:1.5rem;padding-left:1.5rem}}.home.project .hero{text-align:center}@media (min-width: 959px){.home.project .hero{display:flex;align-items:center;justify-content:space-evenly;text-align:left}}.home.project .hero img{display:block;max-width:100%;max-height:280px;margin:0}@media (max-width: 959px){.home.project .hero img{margin:3rem auto 1.5rem}}@media (max-width: 719px){.home.project .hero img{max-height:240px;margin:2rem auto 1.2rem}}@media (max-width: 419px){.home.project .hero img{max-height:210px;margin:1.5rem auto 1rem}}.home.project .hero img.light{display:block}html[data-theme=dark] .home.project .hero img.light,.home.project .hero img.dark{display:none}html[data-theme=dark] .home.project .hero img.dark{display:block}.home.project .hero h1,.home.project .hero .description,.home.project .hero .action{margin:1.8rem auto}@media (max-width: 719px){.home.project .hero h1,.home.project .hero .description,.home.project .hero .action{margin:1.5rem auto}}@media (max-width: 419px){.home.project .hero h1,.home.project .hero .description,.home.project .hero .action{margin:1.2rem auto}}.home.project .hero h1{font-size:3.6rem;font-family:var(--font-family-fancy)}@media (max-width: 719px){.home.project .hero h1{font-size:2.5rem}}@media (max-width: 419px){.home.project .hero h1{font-size:2rem}}.home.project .hero .description{max-width:35rem;color:var(--text-color-bright);font-size:1.6rem;font-family:var(--font-family-fancy);line-height:1.3}@media (max-width: 719px){.home.project .hero .description{font-size:1.4rem}}@media (max-width: 419px){.home.project .hero .description{font-size:1.2rem}}.home.project .features{display:flex;flex-wrap:wrap;align-content:stretch;align-items:stretch;justify-content:center;overflow:hidden;margin:0 -2rem;padding:1.2rem 0;border-top:1px solid var(--border-color);transition:border-color var(--color-transition)}@media (max-width: 419px){.home.project .features{margin:0 -1.5rem}}.home.project .feature{position:relative;flex-basis:calc(33% - 4rem);overflow:hidden;margin:.5rem;padding:1rem 1.5rem;border-radius:.5rem;text-align:center;transition:transform .3s,box-shadow .3s}@media (min-width: 1440px){.home.project .feature{flex-basis:calc(25% - 4rem)}}@media (max-width: 959px){.home.project .feature{flex-basis:calc(50% - 4rem)}}@media (max-width: 719px){.home.project .feature{font-size:.95rem}}@media (max-width: 419px){.home.project .feature{flex-basis:100%;margin:.5rem 0;border-radius:0;font-size:.9rem}}.home.project .feature.link{cursor:pointer}.home.project .feature:hover{box-shadow:0 2px 12px 0 var(--card-shadow);transform:scale(1.05)}.home.project .feature .icon{display:inline-block;margin-bottom:.5rem;color:var(--theme-color);font-size:2rem}.home.project .feature h2{margin:.25rem 0;border-bottom:none;color:var(--text-color-light);font-weight:700;font-size:1.25rem}@media (max-width: 419px){.home.project .feature h2{font-size:1.2rem}}.home.project .feature p{margin:0;color:var(--text-color-lighter);line-height:1.4}.home.project .theme-hope-content{padding-bottom:1.5rem}.home.project #sponsors{text-align:center;margin-top:3rem;margin-bottom:5rem}.home.project #sponsors .links{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-evenly;gap:1.5rem}.theme-hope-content:not(.custom)>*:first-child{margin-top:0}.breadcrumb{max-width:var(--content-width);margin-right:auto;margin-left:auto;padding-right:2.5rem;padding-left:2.5rem;position:relative;z-index:2;padding-top:.5rem;font-size:15px}@media (max-width: 959px){.breadcrumb{padding-right:1.5rem;padding-left:1.5rem}}@media (max-width: 959px){.breadcrumb{font-size:14px}}@media (max-width: 419px){.breadcrumb{font-size:12.8px}}.breadcrumb .icon{margin-right:.25em}.breadcrumb a{display:inline-block;padding:0 .5em;line-height:2}.breadcrumb a:before{position:relative;bottom:.125rem;margin-right:.25em}.breadcrumb a:hover{color:var(--theme-color)}.breadcrumb ol{margin:0;padding-left:0;list-style:none}.breadcrumb li{display:inline-block}.breadcrumb li:first-child a{padding-left:0}.breadcrumb li:last-child a{padding-right:0}.breadcrumb li.is-active a{color:var(--light-grey);cursor:default;pointer-events:none}.breadcrumb li+li:before{content:"/";color:var(--light-grey)}.page-nav{max-width:var(--content-width);margin-right:auto;margin-left:auto;padding:.5rem 2.5rem;overflow:auto;min-height:2rem;margin-top:0;border-top:1px solid var(--border-color);transition:border-top var(--color-transition)}@media (max-width: 959px){.page-nav{padding-right:1.5rem;padding-left:1.5rem}}.page-nav .nav-link{display:inline-block;padding:.25rem}.page-nav .nav-link .hint{color:var(--light-grey);font-size:.875rem;line-height:2}.page-nav .nav-link .arrow{display:inline-block;vertical-align:middle;width:1em;height:1em;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='rgba(0,0,0,0.5)' d='M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z'/%3E%3C/svg%3E");line-height:normal;transition:all .3s;font-size:.75rem}html[data-theme=dark] .page-nav .nav-link .arrow{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='rgba(255,255,255,0.5)' d='M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z'/%3E%3C/svg%3E")}.page-nav .nav-link .arrow.down{transform:rotate(180deg)}.page-nav .nav-link .arrow.right{transform:rotate(90deg)}.page-nav .nav-link .arrow.left{transform:rotate(-90deg)}.page-nav .prev{text-align:left}.page-nav .prev .icon{margin-right:.25em}.page-nav .next{float:right;text-align:right}.page-nav .next .icon{margin-left:.25em}.categories-wrapper{margin:0;padding-left:0;list-style:none}.categories-wrapper li{display:inline-block}.categories-wrapper .category{display:inline-block;margin:0 .25em;padding:0 .25em;border-radius:.25em;font-weight:700;font-size:.75rem;line-height:2;transition:background-color var(--color-transition),color var(--color-transition)}.categories-wrapper .category>span:after{content:", "}.categories-wrapper .category>span:last-child:after{content:""}.categories-wrapper .category.clickable>span:hover{color:var(--theme-color);cursor:pointer}.categories-wrapper .category0{background:#fde5e7;color:#ec2f3e}html[data-theme=dark] .categories-wrapper .category0{background:#340509;color:#ba111f}.categories-wrapper .category0:hover{background:#f9bec3}html[data-theme=dark] .categories-wrapper .category0:hover{background:#53080e}.categories-wrapper .category1{background:#ffeee8;color:#fb7649}html[data-theme=dark] .categories-wrapper .category1{background:#441201;color:#f54205}.categories-wrapper .category1:hover{background:#fed4c6}html[data-theme=dark] .categories-wrapper .category1:hover{background:#6d1d02}.categories-wrapper .category2{background:#fef5e7;color:#f5b041}html[data-theme=dark] .categories-wrapper .category2{background:#3e2703;color:#e08e0b}.categories-wrapper .category2:hover{background:#fce6c4}html[data-theme=dark] .categories-wrapper .category2:hover{background:#633f05}.categories-wrapper .category3{background:#eafaf1;color:#55d98d}html[data-theme=dark] .categories-wrapper .category3{background:#0c331c;color:#29b866}.categories-wrapper .category3:hover{background:#caf3db}html[data-theme=dark] .categories-wrapper .category3:hover{background:#12522d}.categories-wrapper .category4{background:#e6f9ee;color:#36d278}html[data-theme=dark] .categories-wrapper .category4{background:#092917;color:#219552}.categories-wrapper .category4:hover{background:#c0f1d5}html[data-theme=dark] .categories-wrapper .category4:hover{background:#0f4224}.categories-wrapper .category5{background:#e1fcfc;color:#16e1e1}html[data-theme=dark] .categories-wrapper .category5{background:#042929;color:#0e9595}.categories-wrapper .category5:hover{background:#b4f8f8}html[data-theme=dark] .categories-wrapper .category5:hover{background:#064242}.categories-wrapper .category6{background:#e4f0fe;color:#2589f6}html[data-theme=dark] .categories-wrapper .category6{background:#021b36;color:#0862c3}.categories-wrapper .category6:hover{background:#bbdafc}html[data-theme=dark] .categories-wrapper .category6:hover{background:#042c57}.categories-wrapper .category7{background:#f7f1fd;color:#bb8ced}html[data-theme=dark] .categories-wrapper .category7{background:#2a0b4b;color:#9851e4}.categories-wrapper .category7:hover{background:#eadbfa}html[data-theme=dark] .categories-wrapper .category7:hover{background:#431277}.categories-wrapper .category8{background:#fdeaf5;color:#ef59ab}html[data-theme=dark] .categories-wrapper .category8{background:#400626;color:#e81689}.categories-wrapper .category8:hover{background:#facbe5}html[data-theme=dark] .categories-wrapper .category8:hover{background:#670a3d}.tags-wrapper{margin:0;padding-left:0;list-style:none}.tags-wrapper li{display:inline-block}.tags-wrapper .tag{position:relative;display:inline-block;vertical-align:middle;overflow:hidden;min-width:1.5rem;margin:0 .125rem;padding:.125rem .25rem .125rem .625rem;font-weight:700;font-size:.625rem;line-height:1.5;text-align:center;transition:background-color var(--color-transition),color var(--color-transition)}.tags-wrapper .tag.clickable:hover{cursor:pointer}.tags-wrapper .tag0{background:#fde5e7;background:linear-gradient(135deg,transparent .75em,#fde5e7 0) top,linear-gradient(45deg,transparent .75em,#fde5e7 0) bottom;background-size:100% 52%!important;background-repeat:no-repeat!important;color:#ec2f3e}html[data-theme=dark] .tags-wrapper .tag0{background:#340509;background:linear-gradient(135deg,transparent .75em,#340509 0) top,linear-gradient(45deg,transparent .75em,#340509 0) bottom;color:#ba111f}.tags-wrapper .tag0.clickable:hover{background:#f9bec3;background:linear-gradient(135deg,transparent .75em,#f9bec3 0) top,linear-gradient(45deg,transparent .75em,#f9bec3 0) bottom}html[data-theme=dark] .tags-wrapper .tag0.clickable:hover{background:#53080e;background:linear-gradient(135deg,transparent .75em,#53080e 0) top,linear-gradient(45deg,transparent .75em,#53080e 0) bottom}.tags-wrapper .tag1{background:#ffeee8;background:linear-gradient(135deg,transparent .75em,#ffeee8 0) top,linear-gradient(45deg,transparent .75em,#ffeee8 0) bottom;background-size:100% 52%!important;background-repeat:no-repeat!important;color:#fb7649}html[data-theme=dark] .tags-wrapper .tag1{background:#441201;background:linear-gradient(135deg,transparent .75em,#441201 0) top,linear-gradient(45deg,transparent .75em,#441201 0) bottom;color:#f54205}.tags-wrapper .tag1.clickable:hover{background:#fed4c6;background:linear-gradient(135deg,transparent .75em,#fed4c6 0) top,linear-gradient(45deg,transparent .75em,#fed4c6 0) bottom}html[data-theme=dark] .tags-wrapper .tag1.clickable:hover{background:#6d1d02;background:linear-gradient(135deg,transparent .75em,#6d1d02 0) top,linear-gradient(45deg,transparent .75em,#6d1d02 0) bottom}.tags-wrapper .tag2{background:#fef5e7;background:linear-gradient(135deg,transparent .75em,#fef5e7 0) top,linear-gradient(45deg,transparent .75em,#fef5e7 0) bottom;background-size:100% 52%!important;background-repeat:no-repeat!important;color:#f5b041}html[data-theme=dark] .tags-wrapper .tag2{background:#3e2703;background:linear-gradient(135deg,transparent .75em,#3e2703 0) top,linear-gradient(45deg,transparent .75em,#3e2703 0) bottom;color:#e08e0b}.tags-wrapper .tag2.clickable:hover{background:#fce6c4;background:linear-gradient(135deg,transparent .75em,#fce6c4 0) top,linear-gradient(45deg,transparent .75em,#fce6c4 0) bottom}html[data-theme=dark] .tags-wrapper .tag2.clickable:hover{background:#633f05;background:linear-gradient(135deg,transparent .75em,#633f05 0) top,linear-gradient(45deg,transparent .75em,#633f05 0) bottom}.tags-wrapper .tag3{background:#eafaf1;background:linear-gradient(135deg,transparent .75em,#eafaf1 0) top,linear-gradient(45deg,transparent .75em,#eafaf1 0) bottom;background-size:100% 52%!important;background-repeat:no-repeat!important;color:#55d98d}html[data-theme=dark] .tags-wrapper .tag3{background:#0c331c;background:linear-gradient(135deg,transparent .75em,#0c331c 0) top,linear-gradient(45deg,transparent .75em,#0c331c 0) bottom;color:#29b866}.tags-wrapper .tag3.clickable:hover{background:#caf3db;background:linear-gradient(135deg,transparent .75em,#caf3db 0) top,linear-gradient(45deg,transparent .75em,#caf3db 0) bottom}html[data-theme=dark] .tags-wrapper .tag3.clickable:hover{background:#12522d;background:linear-gradient(135deg,transparent .75em,#12522d 0) top,linear-gradient(45deg,transparent .75em,#12522d 0) bottom}.tags-wrapper .tag4{background:#e6f9ee;background:linear-gradient(135deg,transparent .75em,#e6f9ee 0) top,linear-gradient(45deg,transparent .75em,#e6f9ee 0) bottom;background-size:100% 52%!important;background-repeat:no-repeat!important;color:#36d278}html[data-theme=dark] .tags-wrapper .tag4{background:#092917;background:linear-gradient(135deg,transparent .75em,#092917 0) top,linear-gradient(45deg,transparent .75em,#092917 0) bottom;color:#219552}.tags-wrapper .tag4.clickable:hover{background:#c0f1d5;background:linear-gradient(135deg,transparent .75em,#c0f1d5 0) top,linear-gradient(45deg,transparent .75em,#c0f1d5 0) bottom}html[data-theme=dark] .tags-wrapper .tag4.clickable:hover{background:#0f4224;background:linear-gradient(135deg,transparent .75em,#0f4224 0) top,linear-gradient(45deg,transparent .75em,#0f4224 0) bottom}.tags-wrapper .tag5{background:#e1fcfc;background:linear-gradient(135deg,transparent .75em,#e1fcfc 0) top,linear-gradient(45deg,transparent .75em,#e1fcfc 0) bottom;background-size:100% 52%!important;background-repeat:no-repeat!important;color:#16e1e1}html[data-theme=dark] .tags-wrapper .tag5{background:#042929;background:linear-gradient(135deg,transparent .75em,#042929 0) top,linear-gradient(45deg,transparent .75em,#042929 0) bottom;color:#0e9595}.tags-wrapper .tag5.clickable:hover{background:#b4f8f8;background:linear-gradient(135deg,transparent .75em,#b4f8f8 0) top,linear-gradient(45deg,transparent .75em,#b4f8f8 0) bottom}html[data-theme=dark] .tags-wrapper .tag5.clickable:hover{background:#064242;background:linear-gradient(135deg,transparent .75em,#064242 0) top,linear-gradient(45deg,transparent .75em,#064242 0) bottom}.tags-wrapper .tag6{background:#e4f0fe;background:linear-gradient(135deg,transparent .75em,#e4f0fe 0) top,linear-gradient(45deg,transparent .75em,#e4f0fe 0) bottom;background-size:100% 52%!important;background-repeat:no-repeat!important;color:#2589f6}html[data-theme=dark] .tags-wrapper .tag6{background:#021b36;background:linear-gradient(135deg,transparent .75em,#021b36 0) top,linear-gradient(45deg,transparent .75em,#021b36 0) bottom;color:#0862c3}.tags-wrapper .tag6.clickable:hover{background:#bbdafc;background:linear-gradient(135deg,transparent .75em,#bbdafc 0) top,linear-gradient(45deg,transparent .75em,#bbdafc 0) bottom}html[data-theme=dark] .tags-wrapper .tag6.clickable:hover{background:#042c57;background:linear-gradient(135deg,transparent .75em,#042c57 0) top,linear-gradient(45deg,transparent .75em,#042c57 0) bottom}.tags-wrapper .tag7{background:#f7f1fd;background:linear-gradient(135deg,transparent .75em,#f7f1fd 0) top,linear-gradient(45deg,transparent .75em,#f7f1fd 0) bottom;background-size:100% 52%!important;background-repeat:no-repeat!important;color:#bb8ced}html[data-theme=dark] .tags-wrapper .tag7{background:#2a0b4b;background:linear-gradient(135deg,transparent .75em,#2a0b4b 0) top,linear-gradient(45deg,transparent .75em,#2a0b4b 0) bottom;color:#9851e4}.tags-wrapper .tag7.clickable:hover{background:#eadbfa;background:linear-gradient(135deg,transparent .75em,#eadbfa 0) top,linear-gradient(45deg,transparent .75em,#eadbfa 0) bottom}html[data-theme=dark] .tags-wrapper .tag7.clickable:hover{background:#431277;background:linear-gradient(135deg,transparent .75em,#431277 0) top,linear-gradient(45deg,transparent .75em,#431277 0) bottom}.tags-wrapper .tag8{background:#fdeaf5;background:linear-gradient(135deg,transparent .75em,#fdeaf5 0) top,linear-gradient(45deg,transparent .75em,#fdeaf5 0) bottom;background-size:100% 52%!important;background-repeat:no-repeat!important;color:#ef59ab}html[data-theme=dark] .tags-wrapper .tag8{background:#400626;background:linear-gradient(135deg,transparent .75em,#400626 0) top,linear-gradient(45deg,transparent .75em,#400626 0) bottom;color:#e81689}.tags-wrapper .tag8.clickable:hover{background:#facbe5;background:linear-gradient(135deg,transparent .75em,#facbe5 0) top,linear-gradient(45deg,transparent .75em,#facbe5 0) bottom}html[data-theme=dark] .tags-wrapper .tag8.clickable:hover{background:#670a3d;background:linear-gradient(135deg,transparent .75em,#670a3d 0) top,linear-gradient(45deg,transparent .75em,#670a3d 0) bottom}.page-info{display:flex;flex-wrap:wrap;align-content:stretch;align-items:center;justify-content:flex-start;color:var(--dark-grey);font-size:14px}.page-info>span{display:flex;flex-shrink:0;align-items:center;margin-right:.5em;line-height:2}@media (min-width: 1440px){.page-info>span{font-size:1.1em}}@media (max-width: 419px){.page-info>span{margin-right:.3em;font-size:.875em}}.page-info .origin{position:relative;display:inline-block;vertical-align:middle;overflow:hidden;padding:0 .5em;border:.5px solid var(--dark-grey);border-radius:.75em;background:var(--bg-color);font-size:.75em;line-height:1.5}.page-info .icon{position:relative;display:inline-block;vertical-align:middle;width:1em;height:1em;margin-right:.25em}.page-info a{color:inherit}.page-info a:hover,.page-info a:active{color:var(--theme-color)}.page-info .author-item{display:inline-block;margin:0 4px;font-weight:400}.theme-hope-content:not(.custom) h1:first-child,.theme-hope-content:not(.custom) h2:first-child,.theme-hope-content:not(.custom) h3:first-child,.theme-hope-content:not(.custom) h4:first-child,.theme-hope-content:not(.custom) h5:first-child,.theme-hope-content:not(.custom) h6:first-child{margin-top:calc(.5rem - var(--navbar-height))!important;padding-top:var(--navbar-height)!important}.theme-hope-content:not(.custom){padding-top:0!important}.theme-hope-content:not(.custom)>h1:first-child{display:none}.page-title{max-width:var(--content-width);margin-right:auto;margin-left:auto;padding-right:2.5rem;padding-left:2.5rem;position:relative;z-index:1;padding-top:.5rem;padding-bottom:0}@media (max-width: 959px){.page-title{padding-right:1.5rem;padding-left:1.5rem}}.page-title h1{margin-top:calc(.5rem - var(--navbar-height))!important;margin-bottom:1rem;padding-top:var(--navbar-height)!important;font-size:2.2rem}.page-title h1 .icon{margin-right:.25em;color:var(--c-brand, #3eaf7c);font-size:.9em}.page-meta{max-width:var(--content-width);margin-right:auto;margin-left:auto;padding:.75rem 2.5rem;overflow:auto}@media (max-width: 959px){.page-meta{padding-right:1.5rem;padding-left:1.5rem}}.page-meta .meta-item{margin-top:.5rem}.page-meta .meta-item .label{font-weight:500}.page-meta .meta-item .label:not(a){color:var(--text-color-lighter)}.page-meta .meta-item .info{color:var(--dark-grey);font-weight:400}.page-meta .edit-link{display:inline-block;margin-right:.25rem;font-size:14px}@media (max-width: 719px){.page-meta .edit-link{margin-bottom:8px}}.page-meta .edit-link .icon{position:relative;bottom:-.125em;width:1em;height:1em;margin-right:.25em}.page-meta .update-time{float:right;font-size:14px}@media (max-width: 719px){.page-meta .update-time{float:none;font-size:13px;text-align:left}}.page-meta .update-time:first-child{float:left}.page-meta .contributors{font-size:14px;text-align:right}@media (max-width: 719px){.page-meta .contributors{font-size:13px;text-align:left}}.toc-place-holder{position:sticky;top:calc(var(--navbar-height) + 2rem);z-index:99;max-width:var(--content-width);margin:0 auto}.toc-place-holder+.theme-hope-content:not(.custom){padding-top:0}#toc{position:absolute;left:calc(100% + 1rem);display:none;min-width:10rem;max-width:15rem}@media (min-width: 1440px){.has-toc #toc{display:block}}#toc .toc-header{margin:0 0 .75rem .5rem;font-weight:600;font-size:.875rem}#toc .toc-wrapper{position:relative;overflow-x:hidden;overflow-y:auto;max-height:75vh;margin:0 .5rem;padding-left:8px;text-overflow:ellipsis;white-space:nowrap;scroll-behavior:smooth}#toc .toc-wrapper::-webkit-scrollbar-track-piece{background:transparent}#toc .toc-wrapper::-webkit-scrollbar{width:3px}#toc .toc-wrapper::-webkit-scrollbar-thumb:vertical{background:#ddd}html[data-theme=dark] #toc .toc-wrapper::-webkit-scrollbar-thumb:vertical{background:#333}#toc .toc-wrapper:before{content:" ";position:absolute;top:0;bottom:0;left:0;z-index:-1;width:2px;background:var(--border-color)}#toc .toc-list{margin:0;padding:0}#toc .toc-link{position:relative;display:block;overflow:hidden;max-width:100%;color:var(--light-grey);line-height:inherit;text-overflow:ellipsis;white-space:nowrap}#toc .toc-link.level2{padding-left:0;font-size:14px}#toc .toc-link.level3{padding-left:8px;font-size:13px}#toc .toc-link.level4{padding-left:16px;font-size:12px}#toc .toc-link.level5{padding-left:24px;font-size:11px}#toc .toc-link.level6{padding-left:32px;font-size:10px}#toc .toc-item{position:relative;box-sizing:border-box;height:1.7rem;padding:0 .5rem;list-style:none;line-height:1.7rem}#toc .toc-item:before{content:" ";position:absolute;top:0;bottom:0;left:-8px;z-index:2;width:2px;background:transparent}#toc .toc-item:hover>.toc-link{color:var(--theme-color)}#toc .toc-item.active>.toc-link{color:var(--theme-color);font-weight:700}#toc .toc-item.active:before{background:var(--theme-color)}.page{display:block;padding-bottom:2rem;transition:padding var(--transform-transition)}@media (max-width: 719px){.page{min-height:100vh}}.dropdown-wrapper{cursor:pointer}.dropdown-wrapper:not(:hover) .arrow{transform:rotate(-180deg)}.dropdown-wrapper .dropdown-title{border-width:0;background-color:transparent;cursor:pointer;padding:0 .25rem;color:var(--dark-grey);font-weight:500;font-size:inherit;font-family:inherit;line-height:inherit;cursor:inherit}.dropdown-wrapper .dropdown-title:hover{border-color:transparent}.dropdown-wrapper .dropdown-title:after{border-left:5px solid var(--theme-color)}.dropdown-wrapper .dropdown-title .icon{margin-right:.25em;font-size:1em}.dropdown-wrapper .dropdown-title .arrow{display:inline-block;vertical-align:middle;width:1em;height:1em;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='rgba(0,0,0,0.5)' d='M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z'/%3E%3C/svg%3E");line-height:normal;transition:all .3s;font-size:1.2em}html[data-theme=dark] .dropdown-wrapper .dropdown-title .arrow{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='rgba(255,255,255,0.5)' d='M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z'/%3E%3C/svg%3E")}.dropdown-wrapper .dropdown-title .arrow.down{transform:rotate(180deg)}.dropdown-wrapper .dropdown-title .arrow.right{transform:rotate(90deg)}.dropdown-wrapper .dropdown-title .arrow.left{transform:rotate(-90deg)}.dropdown-wrapper ul{margin:0;padding:0;list-style-type:none}.dropdown-wrapper .nav-dropdown{position:absolute;top:100%;right:0;overflow-y:auto;box-sizing:border-box;max-height:calc(100vh - var(--navbar-height));margin:0;padding:.5rem .75rem;border:1px solid var(--grey14);border-radius:.25rem;background:var(--bg-color);box-shadow:2px 2px 10px var(--card-shadow);text-align:left;white-space:nowrap;opacity:0;visibility:hidden;transition:all .18s ease-out;transform:scale(.8)}.dropdown-wrapper:hover .nav-dropdown,.dropdown-wrapper.open .nav-dropdown{z-index:2;opacity:1;visibility:visible;transform:scale(1)}.dropdown-wrapper .nav-link{position:relative;display:block;margin-bottom:0;border-bottom:none;color:var(--dark-grey);font-weight:400;font-size:.875rem;line-height:1.7rem;transition:color var(--color-transition)}.dropdown-wrapper .nav-link:hover,.dropdown-wrapper .nav-link.active{color:var(--theme-color)}.dropdown-wrapper .dropdown-subtitle{margin:0;padding:0 .25rem;color:var(--light-grey);font-weight:600;font-size:.75rem;line-height:2;text-transform:uppercase;transition:color var(--color-transition)}.dropdown-wrapper .dropdown-subitem-wrapper{padding:0 0 .5rem;border-bottom:1px solid var(--grey14)}.dropdown-wrapper .dropdown-item{color:inherit;line-height:1.7rem}.dropdown-wrapper .dropdown-item:last-child .dropdown-subitem-wrapper{padding-bottom:0;border-bottom-width:0}.navbar .logo{vertical-align:top;height:var(--navbar-line-height);margin-right:.8rem}.navbar .logo.light{display:inline-block}.navbar .logo.dark,html[data-theme=dark] .navbar .logo.light{display:none}html[data-theme=dark] .navbar .logo.dark{display:inline-block}.navbar .site-name{position:relative;color:var(--text-color);font-size:1.25rem}@media (max-width: 719px){.navbar .site-name{overflow:hidden;width:calc(100vw - 9.4rem);text-overflow:ellipsis;white-space:nowrap}}.brand:hover .navbar .site-name{color:var(--theme-color)}.navbar .nav-links{display:flex;align-items:center;font-size:.875rem}.navbar .nav-item{position:relative;margin:0 .25rem;line-height:2rem}.navbar .nav-item:first-child{margin-left:0}.navbar .nav-item:last-child{margin-right:0}.navbar .nav-item>.nav-link{color:var(--dark-grey)}.navbar .nav-item>.nav-link:after{content:" ";position:absolute;right:50%;bottom:0;left:50%;height:2px;border-radius:1px;background:var(--theme-color-light);visibility:hidden;transition:left .2s ease-in-out,right .2s ease-in-out}.navbar .nav-item>.nav-link.active{color:var(--theme-color)}.navbar .nav-item>.nav-link:hover:after,.navbar .nav-item>.nav-link.active:after{right:0;left:0;visibility:visible}.nav-screen-dropdown-title{border-width:0;background-color:transparent;position:relative;display:flex;align-items:center;width:100%;padding:0;color:var(--dark-grey);font-size:inherit;font-family:inherit;text-align:left;cursor:pointer}.nav-screen-dropdown-title:hover,.nav-screen-dropdown-title.active{color:var(--text-color)}.nav-screen-dropdown-title .title{flex:1}.nav-screen-dropdown-title .arrow{display:inline-block;vertical-align:middle;width:1em;height:1em;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='rgba(0,0,0,0.5)' d='M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z'/%3E%3C/svg%3E");line-height:normal;transition:all .3s}html[data-theme=dark] .nav-screen-dropdown-title .arrow{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='rgba(255,255,255,0.5)' d='M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z'/%3E%3C/svg%3E")}.nav-screen-dropdown-title .arrow.down{transform:rotate(180deg)}.nav-screen-dropdown-title .arrow.right{transform:rotate(90deg)}.nav-screen-dropdown-title .arrow.left{transform:rotate(-90deg)}.nav-screen-dropdown{overflow:hidden;margin:.5rem 0 0;padding:0;list-style:none;transition:transform .1s ease-out;transform:scaleY(1);transform-origin:top}.nav-screen-dropdown.hide{height:0;margin:0;transform:scaleY(0)}.nav-screen-dropdown .nav-link{position:relative;display:block;padding-left:.5rem;font-weight:400;line-height:2}.nav-screen-dropdown .nav-link:hover,.nav-screen-dropdown .nav-link.active{color:var(--theme-color)}.nav-screen-dropdown .nav-link .icon{font-size:1em}.nav-screen-dropdown .dropdown-item{color:inherit;line-height:1.7rem}.nav-screen-dropdown .dropdown-subtitle{margin:0;padding-left:.25rem;color:var(--light-grey);font-weight:600;font-size:.75rem;line-height:2;text-transform:uppercase;transition:color var(--color-transition)}.nav-screen-dropdown .dropdown-subtitle .nav-link{padding:0}.nav-screen-dropdown .dropdown-subitem-wrapper{margin:0;padding:0;list-style:none}.nav-screen-dropdown .dropdown-subitem{padding-left:.5rem;font-size:.9em}.nav-screen-links{display:none;padding-bottom:.75rem}@media (max-width: 719px){.nav-screen-links{display:block}}.nav-screen-links .navbar-links-item{position:relative;display:block;padding:12px 4px 11px 0;border-bottom:1px solid var(--border-color);font-size:16px;line-height:1.5rem;transition:border-bottom-color var(--color-transition)}.nav-screen-links .nav-link{display:inline-block;width:100%;color:var(--dark-grey);font-weight:400}.nav-screen-links .nav-link:hover{color:var(--text-color)}.nav-screen-links .nav-link.active{color:var(--theme-color)}.appearance-title{display:block;margin:0;padding:0 .25rem;color:var(--light-grey);font-weight:600;font-size:.75rem;line-height:2;transition:color var(--color-transition)}#appearance-switch{border-width:0;background-color:transparent;vertical-align:middle;padding:6px;color:var(--dark-grey);cursor:pointer}#appearance-switch:hover{color:var(--theme-color)}#appearance-switch .icon{width:1.25rem;height:1.25rem}.themecolor-title{display:block;margin:0;padding:0 .25rem;color:var(--light-grey);font-weight:600;font-size:.75rem;line-height:2;transition:color var(--color-transition)}#themecolor-picker{display:flex;margin:0;padding:0;list-style-type:none;font-size:14px}#themecolor-picker li span{display:inline-block;vertical-align:middle;width:15px;height:15px;margin:0 2px;border-radius:2px}#themecolor-picker li span.theme-color{background:#3eaf7c}.full-screen-title{display:block;margin:0;padding:0 .25rem;color:var(--light-grey);font-weight:600;font-size:.75rem;line-height:2;transition:color var(--color-transition)}.full-screen,.cancel-full-screen{border-width:0;background-color:transparent;vertical-align:middle;padding:6px;color:var(--dark-grey);cursor:pointer}.full-screen:hover,.cancel-full-screen:hover{color:var(--theme-color)}.full-screen .icon,.cancel-full-screen .icon{width:1.25rem;height:1.25rem}.enter-fullscreen-icon:hover,.cancel-fullscreen-icon{color:var(--theme-color)}.cancel-fullscreen-icon:hover{color:var(--dark-grey)}#nav-screen{position:fixed;top:var(--navbar-height);right:0;bottom:0;left:0;z-index:150;display:none;overflow-y:auto;padding:0 2rem;background-color:var(--bg-color);transition:background-color .5s}@media (max-width: 719px){#nav-screen{display:block}}#nav-screen .container{max-width:320px;margin:0 auto;padding:2rem 0 4rem}#nav-screen.fade-enter-active,#nav-screen.fade-leave-active{transition:opacity .25s}#nav-screen.fade-enter-active .container,#nav-screen.fade-leave-active .container{transition:transform .25s ease}#nav-screen.fade-enter-from,#nav-screen.fade-leave-to{opacity:0}#nav-screen.fade-enter-from .container,#nav-screen.fade-leave-to .container{transform:translateY(-8px)}#nav-screen .outlook-wrapper{display:flex;justify-content:space-around}#nav-screen .icon{margin-right:.25em}.outlook-button{border-width:0;background-color:transparent;cursor:pointer;position:relative;padding:6px;color:var(--dark-grey)}.outlook-button .icon{vertical-align:middle;width:1.25rem;height:1.25rem}.outlook-dropdown{position:absolute;top:100%;right:0;overflow-y:auto;box-sizing:border-box;min-width:100px;margin:0;padding:.5rem .75rem;border:1px solid var(--grey14);border-radius:.25rem;background:var(--bg-color);box-shadow:2px 2px 10px var(--card-shadow);text-align:left;white-space:nowrap;opacity:0;visibility:hidden;transition:all .18s ease-out;transform:scale(.8)}.outlook-dropdown>*:not(:last-child){padding-bottom:.5rem;border-bottom:1px solid var(--grey14)}.outlook-button:hover .outlook-dropdown,.outlook-button.open .outlook-dropdown{z-index:2;opacity:1;visibility:visible;transform:scale(1)}.toggle-navbar-button{border-width:0;background-color:transparent;cursor:pointer;position:relative;display:none;align-items:center;justify-content:center;padding:6px}@media screen and (max-width: 719px){.toggle-navbar-button{display:flex}}.toggle-navbar-button .button-container{position:relative;overflow:hidden;width:16px;height:14px}.toggle-navbar-button .button-top,.toggle-navbar-button .button-middle,.toggle-navbar-button .button-bottom{position:absolute;width:16px;height:2px;background-color:var(--dark-grey);transition:top .25s,background-color .5s,transform .25s}.toggle-navbar-button .button-top{top:0;left:0;transform:translate(0)}.toggle-navbar-button .button-middle{top:6px;left:0;transform:translate(8px)}.toggle-navbar-button .button-bottom{top:12px;left:0;transform:translate(4px)}.toggle-navbar-button:hover .button-top{top:0;left:0;transform:translate(4px)}.toggle-navbar-button:hover .button-middle{top:6;left:0;transform:translate(0)}.toggle-navbar-button:hover .button-bottom{top:12px;left:0;transform:translate(8px)}.toggle-navbar-button.is-active .button-top{top:6px;transform:translate(0) rotate(225deg)}.toggle-navbar-button.is-active .button-middle{top:6px;transform:translate(16px)}.toggle-navbar-button.is-active .button-bottom{top:6px;transform:translate(0) rotate(135deg)}.toggle-navbar-button.is-active:hover .button-top,.toggle-navbar-button.is-active:hover .button-middle,.toggle-navbar-button.is-active:hover .button-bottom{background-color:var(--theme-color);transition:top .25s,background-color .25s,transform .25s}.toggle-sidebar-button{border-width:0;background-color:transparent;cursor:pointer;display:none;vertical-align:middle;box-sizing:content-box;width:1rem;height:1rem;padding:.5rem;font:unset;transition:transform .2s ease-in-out}@media screen and (max-width: 719px){.toggle-sidebar-button{display:block;padding-right:var(--navbar-mobile-horizontal-padding)}}.toggle-sidebar-button:before,.toggle-sidebar-button:after,.toggle-sidebar-button .icon{display:block;width:100%;height:2px;border-radius:.05em;background:var(--dark-grey);transition:transform .2s ease-in-out}.toggle-sidebar-button:before{content:" ";margin-top:.125em}.sidebar-open .toggle-sidebar-button:before{transform:translateY(.34rem) rotate(135deg)}.toggle-sidebar-button:after{content:" ";margin-bottom:.125em}.sidebar-open .toggle-sidebar-button:after{transform:translateY(-.34rem) rotate(-135deg)}.toggle-sidebar-button .icon{margin:.2em 0}.sidebar-open .toggle-sidebar-button .icon{transform:scale(0)}.navbar .repo-link{display:inline-block;margin:auto;padding:6px;color:var(--dark-grey);line-height:1}.navbar .repo-link:hover,.navbar .repo-link:active{color:var(--theme-color)}.navbar{--navbar-line-height: calc( var(--navbar-height) - var(--navbar-vertical-padding) * 2 );position:fixed;top:0;right:0;left:0;z-index:175;display:flex;align-items:center;justify-content:space-between;box-sizing:border-box;height:var(--navbar-height);padding:var(--navbar-vertical-padding) var(--navbar-horizontal-padding);background:var(--navbar-bg-color);box-shadow:0 2px 8px var(--card-shadow);line-height:var(--navbar-line-height);white-space:nowrap;transition:transform ease-in-out .3s,background-color var(--color-transition),box-shadow var(--color-transition);-webkit-backdrop-filter:saturate(150%) blur(12px);backdrop-filter:saturate(150%) blur(12px)}.hide-navbar .navbar.auto-hide{transform:translateY(-100%)}.navbar.hide-icon .icon{display:none!important}.navbar .nav-link{padding:0 .25rem;color:var(--dark-grey)}.navbar .nav-link.active{color:var(--theme-color)}.navbar .nav-link .icon{margin-right:.25em;font-size:1em}.navbar-left,.navbar-right,.navbar-center{display:flex;align-items:center}.navbar-left>*,.navbar-right>*,.navbar-center>*{position:relative;margin:0 .25rem!important}.navbar-left>*:first-child,.navbar-right>*:first-child,.navbar-center>*:first-child{margin-left:0!important}.navbar-left>*:last-child,.navbar-right>*:last-child,.navbar-center>*:last-child{margin-right:0!important}.DocSearch.DocSearch-Button{margin-left:0}@media (max-width: 750px){#docsearch-container{min-width:36px!important}}.sidebar-heading{display:flex;align-items:center;overflow:hidden;box-sizing:border-box;width:100%;margin:0;padding:.35rem 1rem .35rem 1.25rem;border-width:0;border-left:.25rem solid transparent;background-color:transparent;color:var(--text-color);font-size:1.1em;-webkit-user-select:none;-moz-user-select:none;user-select:none;transition:color .15s ease;transform:rotate(0)}.sidebar-heading.open{color:inherit}.sidebar-heading.clickable:hover{background-color:var(--bg-color-active)}.sidebar-heading.clickable.exact{border-left-color:var(--theme-color);color:var(--theme-color)}.sidebar-heading.clickable.exact a{color:inherit}.sidebar-heading .icon{margin-right:.25em}.sidebar-heading .title{flex:1}.sidebar-heading .arrow{display:inline-block;vertical-align:middle;width:1em;height:1em;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='rgba(0,0,0,0.5)' d='M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z'/%3E%3C/svg%3E");line-height:normal;transition:all .3s;font-size:1.5em}html[data-theme=dark] .sidebar-heading .arrow{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='rgba(255,255,255,0.5)' d='M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z'/%3E%3C/svg%3E")}.sidebar-heading .arrow.down{transform:rotate(180deg)}.sidebar-heading .arrow.right{transform:rotate(90deg)}.sidebar-heading .arrow.left{transform:rotate(-90deg)}button.sidebar-heading{outline:none;font-weight:inherit;line-height:inherit;text-align:left;cursor:pointer}.sidebar-link{display:inline-block;box-sizing:border-box;width:100%;padding:.35rem 1rem .35rem 1.25rem;border-left:.2rem solid transparent;color:var(--text-color);font-weight:400;font-size:1em;line-height:1.5}.sidebar-link .icon{margin-right:.25em}.sidebar-link:hover{background-color:var(--bg-color-active)}.sidebar-link.active{border-left-color:var(--theme-color);background-color:var(--theme-color-mask);color:var(--theme-color);font-weight:500}.sidebar-link.active .icon{color:var(--theme-color)}.sidebar-sub-headers .sidebar-link{padding-top:.25rem;padding-bottom:.25rem;border-left:none}.sidebar-sub-headers .sidebar-link.active{background-color:transparent;font-weight:500}.sidebar-group .sidebar-group{padding-left:.5em}.sidebar-group .sidebar-group .sidebar-heading{font-size:1em}.sidebar-group:not(.collapsable) .sidebar-heading:not(.clickable){color:inherit;cursor:auto}.sidebar-group .sidebar-link{padding-left:1.75rem}.sidebar-links{margin:0;padding:0}.sidebar-links ul{margin:0;padding:0;list-style-type:none}.sidebar-links ul.sidebar-sub-headers{padding-left:.75rem;font-size:.95em}@media (min-width: 1440px){.has-toc .sidebar-links ul.sidebar-sub-headers{display:none}}.sidebar>.sidebar-links{padding:1.5rem 0}@media (max-width: 719px){.sidebar>.sidebar-links{padding:1rem 0}}.sidebar>.sidebar-links>li>.sidebar-link{font-size:1.1em;line-height:1.7}.sidebar>.sidebar-links>li:not(:first-child){margin-top:.5rem}.sidebar{position:fixed;top:0;bottom:0;left:0;z-index:125;overflow-y:auto;box-sizing:border-box;width:var(--sidebar-width);margin:0;background:var(--sidebar-bg-color);box-shadow:2px 0 8px var(--card-shadow);font-size:16px;transition:background-color var(--color-transition),box-shadow var(--color-transition),transform var(--transform-transition);-webkit-backdrop-filter:saturate(150%) blur(12px);backdrop-filter:saturate(150%) blur(12px);scrollbar-color:var(--theme-color) var(--border-color);scrollbar-width:thin}@media (max-width: 959px){.sidebar{font-size:15px}}@media (max-width: 719px){.sidebar{box-shadow:2px 0 8px var(--card-shadow);transform:translate(-100%)}}.sidebar a{display:inline-block;color:var(--text-color);font-weight:400}.sidebar .icon{margin-right:.25em}.sidebar.hide-icon .icon{display:none!important}.sidebar .blogger-info.mobile{display:none}@media (max-width: 719px){.sidebar .blogger-info.mobile{display:block}}.sidebar .blogger-info.mobile+hr{display:none}@media (max-width: 719px){.sidebar .blogger-info.mobile+hr{display:block;margin-top:16px}}.sidebar-mask{position:fixed;top:0;left:0;z-index:9;width:100vw;height:100vh;background-color:#00000026}.sidebar-mask.fade-enter-active,.sidebar-mask.fade-leave-active{transition:opacity .25s}.sidebar-mask.fade-enter-from,.sidebar-mask.fade-leave-to{opacity:0}.fade-slide-y-enter-active{transition:all .3s ease}.fade-slide-y-leave-active{transition:all .3s cubic-bezier(1,.5,.8,1)}.fade-slide-y-enter-from,.fade-slide-y-leave-to{opacity:0;transform:translateY(10px)}.skip-link{top:.25rem;left:.25rem;z-index:999;padding:.65rem 1.5rem;border-radius:.5rem;background-color:var(--bg-color);color:var(--theme-color);box-shadow:var(--card-shadow);font-weight:700;font-size:.9em;text-decoration:none}html[data-theme=dark] .skip-link{color:var(--theme-color)}.skip-link .skip-link:focus{clip:auto;width:auto;height:auto;-webkit-clip-path:none;clip-path:none}.page.not-found{display:block;max-width:var(--home-page-width);min-height:80vh;margin:0 auto;padding:calc(var(--navbar-height) + 1rem) 2rem 0 2rem!important;text-align:center}.page.not-found .not-found-icon{max-width:600px;margin:0 1.5rem}html[data-theme=dark] .page.not-found .not-found-icon{filter:invert(70%)}.page.not-found .action-button{display:inline-block;box-sizing:border-box;margin:0 .25rem;padding:.5rem 1rem;border-width:0;border-bottom:1px solid var(--theme-color-dark);border-radius:.25rem;background:var(--theme-color);color:var(--white);outline:none;font-size:1rem;transition:background .1s ease}.page.not-found .action-button:hover{background:var(--theme-color-light);cursor:pointer}:root{--c-brand: var(--theme-color);--c-brand-light: var(--theme-color-light);--c-text: var(--text-color);--c-bg: var(--bg-color);--c-bg-light: var(--bg-color-light);--c-border: var(--border-color);--navbar-bg-color: var(--bg-color-float-blur);--sidebar-bg-color: var(--bg-color-blur)}html[data-theme=dark]{--c-brand: var(--theme-color);--c-brand-light: var(--theme-color-light);--c-text: var(--text-color);--c-bg: var(--bg-color);--c-bg-light: var(--bg-color-light);--c-border: var(--border-color);--navbar-bg-color: var(--bg-color-blur);--sidebar-bg-color: var(--bg-color-blur)}#app{--code-hl-bg-color: var(--code-highlight-line-color);--code-ln-color: var(--code-line-color);--code-ln-wrapper-width: var(--line-numbers-width);--code-tabs-nav-text-color: var(--text-color);--code-tabs-nav-bg-color: var(--code-border-color);--code-tabs-nav-hover-color: var(--code-highlight-line-color)}@media (max-width: 959px){#app{--navbar-height: var(--navbar-mobile-height);--navbar-vertical-padding: var(--navbar-mobile-vertical-padding);--navbar-horizontal-padding: var(--navbar-mobile-horizontal-padding);--sidebar-width: var(--sidebar-mobile-width)}}.DocSearch-Button,.DocSearch{--docsearch-primary-color: var(--c-theme-color);--docsearch-text-color: var(--text-color);--docsearch-highlight-color: var(--theme-color);--docsearch-muted-color: var(--light-gery);--docsearch-container-background: rgb(9 10 17 / 80%);--docsearch-modal-background: var(--bg-color-float);--docsearch-searchbox-background: var(--bg-color-active);--docsearch-searchbox-focus-background: var(--bg-color);--docsearch-searchbox-shadow: inset 0 0 0 2px var(--theme-color);--docsearch-hit-color: var(--text-color-light);--docsearch-hit-active-color: var(--bg-color);--docsearch-hit-background: var(--bg-color);--docsearch-hit-shadow: 0 1px 3px 0 var(--border-color);--docsearch-footer-background: var(--bg-color)}html[data-theme=dark] .DocSearch-Button,html[data-theme=dark] .DocSearch{--docsearch-logo-color: var(--c-text);--docsearch-modal-shadow: inset 1px 1px 0 0 #2c2e40, 0 3px 8px 0 #000309;--docsearch-key-shadow: inset 0 -2px 0 0 #282d55, inset 0 0 1px 1px #51577d, 0 2px 2px 0 rgb(3 4 9 / 30%);--docsearch-key-gradient: linear-gradient(-225deg, #444950, #1c1e21);--docsearch-footer-shadow: inset 0 1px 0 0 rgb(73 76 106 / 50%), 0 -4px 8px 0 rgb(0 0 0 / 20%)}#nprogress{--nprogress-color: var(--theme-color)}.search-box{--search-bg-color: var(--bg-color);--search-accent-color: var(--theme-color);--search-text-color: var(--text-color);--search-border-color: var(--border-color);--search-item-text-color: var(--text-color-lighter);--search-item-focus-bg-color: var(--bg-color-light)}.external-link-icon{--external-link-icon-color: var(--light-grey)}html,body{margin:0;padding:0;background:var(#fff)}body{min-height:100vh;color:#2c3e50;font-size:16px;font-display:optional;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;-webkit-tap-highlight-color:transparent}a{color:#3eaf7c;font-weight:500;text-decoration:none;overflow-wrap:break-word}kbd{padding:0 .15em;border:solid .15rem #ddd;border-bottom:solid .25rem #ddd;border-radius:.15rem;background:#eee}code{margin:0;padding:.25rem .5rem;border-radius:3px;background:rgba(127,127,127,.12);font-size:.85em;overflow-wrap:break-word}p a code{color:#3eaf7c;font-weight:400}blockquote{margin:1rem 0;padding:.25rem 0 .25rem 1rem;border-left:.2rem solid #ddd;color:#666;font-size:1rem}ul,ol{padding-left:1.2em}strong{font-weight:600}h1,h2,h3,h4,h5,h6{font-weight:500;line-height:1.25}h1:hover .header-anchor,h2:hover .header-anchor,h3:hover .header-anchor,h4:hover .header-anchor,h5:hover .header-anchor,h6:hover .header-anchor{opacity:1}h1{font-size:2rem}h2{padding-bottom:.3rem;border-bottom:1px solid #eaecef;font-size:1.65rem}h3{font-size:1.35rem}h4{font-size:1.15rem}h5{font-size:1.05rem}h6{font-size:1rem}a.header-anchor{float:left;margin-top:.125em;margin-left:-.87em;padding-right:.23em;font-size:.85em;opacity:0;transition:opacity .2s}a.header-anchor:hover{text-decoration:none}a.header-anchor:focus-visible{opacity:1}p,ul,ol{line-height:1.7}blockquote>p{margin:0}hr{border:0;border-top:1px solid #eaecef}table{display:block;overflow-x:auto;margin:1rem 0;border-collapse:collapse}tr{border-top:1px solid #dfe2e5}tr:nth-child(2n){background:#f6f8fa}th,td{padding:.6em 1em;border:1px solid #dfe2e5}@font-face{font-family:Crimson;src:url(data:font/truetype;charset=utf-8;base64,AAEAAAANAIAAAwBQRkZUTYr5mwEAAAyMAAAAHEdERUYAKQATAAAMbAAAAB5PUy8yVsJ0MgAAAVgAAABgY21hcBiKDzgAAAHcAAABWGdhc3D//wADAAAMZAAAAAhnbHlmr+DBdQAAA1AAAAdsaGVhZBZwt+8AAADcAAAANmhoZWEFawEuAAABFAAAACRobXR4BksA9gAAAbgAAAAibG9jYQlsC24AAAM0AAAAHG1heHAAEQBZAAABOAAAACBuYW1lLaFDVAAACrwAAAFrcG9zdAC1AHoAAAwoAAAAPAABAAAAAQAAqBd2H18PPPUACwQAAAAAANqqufwAAAAA2qq5/AAb/9wB4QMeAAAACAACAAAAAAAAAAEAAAMs/ywAXAH9AAAAAAHhAAEAAAAAAAAAAAAAAAAAAAAEAAEAAAANAFkAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAH1AZAABQAAApkCzAAAAI8CmQLMAAAB6wAzAQkAAAIABgMAAAAAAAAAAAABEAAAAAAAAAAAAAAAUGZFZADAADAAOQMs/ywAXAMsANQAAAABAAAAAAMYAAAAAAAgAAEBpwAfAAAAAAFVAAAB/QAfAH0ALQA+ABsAPgAyACgAPgAxAAAAAAADAAAAAwAAABwAAQAAAAAAUgADAAEAAAAcAAQANgAAAAQABAABAAAAOf//AAAAL///AAAAAQAEAAAAAAADAAQABQAGAAcACAAJAAoACwAMAAABBgAAAQAAAAAAAAABAgAAAAIAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAwQFBgcICQoLDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYAJgAmAGIAwAEeAZIBzgJAApYC2gNiA7YAAQAf/9wBhwMeABIAAAEGBwYHATAXFjM2NzY3ASYnJjcBgxwLCgH+zgMECxIKCgIBLgEDAwMDHhQFBgP85wMEAQgJBgMOAwMDEwAAAAIAH//9Ad0CkAAQACEAABMWFxYXNjc2NzQnJicGBwYHNyY3NjcWFxYXFAcGByYnJjcfATo6amo7OQE5OmxrOjkBXQIlJEE5IyIBIyJEOSQjAgFOkV5eBAReXoqJXl4EBF5eggJ0UlEDA09Qe3xVVgMDU1OEAAAAAAEAff/9AYACkQA+AAA3FAcGBwYHBiMGFQYXNjc2MzIXFhc2JzQnIicmJyY1JjURNjc2MSYnJicjBgcGBwYVFBUUFxYXNjc2NzIXFhXkAQEEBRgYDAMBBB4ZGhweGxofBAEDDBgZBQQBAQMEAQIDBAIFNTZCAgMDBA0XFw0LBQV3GBMVDAgEBAUKCgUCAQICAQIFCgoFBAQIDBUTGAGnLxkbBAYFAQIZGh4BAgECBQUEAwUHBwEICRYAAAAAAQAtAAAB0QKRADoAADcGFxYXITY3NjcmJyYjIgcGBwYHBisBNjc2NzY3NjUmJyYnBgcGBxQXFhc2NzY3FhcWFxYHBgcGBwYHLgEEAwMBYwURERADBwYFBAMDAg8VEx/LJkBAOhsQDwIxMkxSMjIHCAYGCSYmPTIfHwEBCgoeLkJBQg8EBQQCETAwKQICAgEBBCgUEylJSUYhJicsRDIzAgY1NRoEBQYBEyEhAwEjIjYlJCQtQlBQSAAAAAABAD7/+wG+ApEASgAANwYXFhcWFxYzNjc2NyYnJic2NzY3JicmIwYHBgcUFxYXNjc2NxYXFhcGBwYHBgcUFRQXNjc2NxYXFhcGBwYnIicmJyYnJiciBwYXPwEIBwUaHB0VZU5NBAMvLi8eIB4DAywsKzwrKxgEAwUIHR4wLRscAQMvLz8BAQYKEhEQNSYmAgImJSsWExQPCw0NFREMDQE7DgsLBQwFBgE8PWpMKSoGECQkMkAiIQIdHyUHBwcBCRscAwEbGSpCIyUOAgMCAwwIAwUEAQEoKD9XJSQBBQYODg8PAQ0NFQAAAgAb//oB4QKTACIAJQAANxQXFhchFRQXFjMyNzYjNTM2NzY1NCcmJyMRNCcmIwYHBgcBExEbAgMFASEJCRIdCAkBRgIBAQUEBTwFAwgHCQkG/vjmxgUGBgOwBQIBAwKzAgQDCBAMDQEBlAYGBgEICQf+cwEs/tQAAQA+//sBvgKTAEoAADcGFxYXFhcWMzY3NjcmJyYnIgcGBzY3NjczMjc2NzY3NjU0JyYnBgcGByMGBwYHFBcWMzY3NjMWFxYHBgcGJyInJicmJyYnIgcGFz8BCAcFGhwdFWVOTQQBMjJbFx8gFwoJCQlWKB0dFQ4JCAQDBQMdHSKXCREQEgMCBA4bGhNYJyUBAiYlKxYTFA8LDQ0VEQwNATsOCwsFDAUGATw9akU2NwMFBggrMC8uAgICExcZBgQCAgMBAwQBMVNUWAUFBAYFBAMxMTNZIyQBBQYODg8PAQ0NFQAAAgAy//oBzQKXACAAMwAANxQXFhc2NzY3NicmJyIHBgc2NzY3NCcmJwYHBgcGBwYXNyY3Njc2FxYXFgcGBwYHJicmNzM1NV5aOTsCAioqahoiIRsnWFhFAwIHQ0tMOTAZGQFbBAQaGxkXRB8fAQEfIDE9Hh4E511FRwQDPT1ZPEJBBQwLF4Y9PRMGCwwBEiwsPDZFRkkTHyAbCAcBAjAwREYsLQEFREVQAAAAAAEAKP/7AdUCiwApAAATFhcWMzI3Njc2NzYzIQYHBgcWFxYzMjcBNjc2NzQnJiMiBwYjIQYHBgcoAwYHAwYDAwELEBEdAQUJYWJXAQ8PDgcDAQ4LCQgBAQEEBhUVFv7JBgsNDAH6DQMCAQEFKRITFMjHjQcFBgMCPxYSEwoEAgMBAhkrKiAAAAADAD7/9wG/ApIAKABBAFgAADcGFxYXNjc2NyYnJicmJzQ3Njc2NyYnJiMGBwYHFhcWFxYVFAcGBwYHNyY3Njc2MzIzMhcyFxYXFhcGBwYHIicmNxMmNzY3FhcWFRQHBgcGByIjIicmJyY3PwE1M1ZQODgDAykpMQIBAyYlJQMCMC9HRjExAgIiIiMCAiMvLwNTBBQTKgEBAQECAQIBEjU1CAEdHjMrISICGAMYGSYvGxoTEx8CAQIBBAMfJCQBoU8tLQECMjFPOC4uGwIBAgEWJiU7SCYoAjEwQzopKhMBAgECEykpQAQsIiEbAQEBBywsQjUeHQEiI0QBZSMhIAECJiYvKh8gFAEBAhAfIEYAAAIAMf/6AcsClwAgADMAABMGFxYXMjc2NwYHBgcUFxYXNjc2NzY3NjUmJyYnBgcGBzcmNzY3FhcWFRQHBgcGJyYnJjc0AyopahoiIRsoV1hFAwIHQ0tMODEZGQE2NF5ZOjoBWgMfHzE9Hh4EGhoaF0QeHwUBy0dBQgUMCxeFPj0SBwsLAREsLD01RkVPV0dFBQQ8PU8UPCwtAQVFRUklIRsHCAECMDBPAAAADACWAAEAAAAAAAEABwAQAAEAAAAAAAIABwAoAAEAAAAAAAMABwBAAAEAAAAAAAQABwBYAAEAAAAAAAUAHgCeAAEAAAAAAAYABwDNAAMAAQQJAAEADgAAAAMAAQQJAAIADgAYAAMAAQQJAAMADgAwAAMAAQQJAAQADgBIAAMAAQQJAAUAPABgAAMAAQQJAAYADgC9AEMAcgBpAG0AcwBvAG4AAENyaW1zb24AAEMAcgBpAG0AcwBvAG4AAENyaW1zb24AAEMAcgBpAG0AcwBvAG4AAENyaW1zb24AAEMAcgBpAG0AcwBvAG4AAENyaW1zb24AAFYAZQByAHMAaQBvAG4AIAAxAC4AMAA7ACAARgBvAG4AdABFAGQAaQB0AG8AcgAgACgAdgAxAC4AMAApAABWZXJzaW9uIDEuMDsgRm9udEVkaXRvciAodjEuMCkAAEMAcgBpAG0AcwBvAG4AAENyaW1zb24AAAACAAAAAAAAADIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0AAAABAAIAEwAUABUAFgAXABgAGQAaABsAHAAAAAH//wACAAEAAAAMAAAAFgAAAAIAAQADAAwAAQAEAAAAAgAAAAAAAAABAAAAANWkJwgAAAAA2qq5/AAAAADaqrn8) format("truetype");font-weight:400;font-style:normal}html,body{background:var(--bg-color);transition:background-color var(--color-transition)}html[data-theme=dark]{color-scheme:dark}body{color:var(--text-color);font-family:var(--font-family)}a{color:var(--theme-color)}kbd{border-color:var(--grey14);background:var(--bg-color-light);font-family:var(--font-family-code)}code{font-family:var(--font-family-code);transition:background-color var(--color-transition),color var(--color-transition)}html[data-theme=dark] code{background:#333}p a code{color:var(--theme-color)}blockquote{border-color:#eee;color:#666;transition:border-color var(--color-transition),color var(--color-transition)}html[data-theme=dark] blockquote{border-color:#333}@media (max-width: 419px){h1{font-size:1.9rem}}h2{border-color:var(--border-color);transition:border-bottom-color var(--color-transition)}hr{border-color:var(--border-color);transition:border-top-color var(--color-transition)}tr{border-color:var(--grey14)}tr:nth-child(2n){background:var(--bg-color-light)}html[data-theme=dark] tr:nth-child(2n){background:#252322}th,td{border-color:var(--grey14)}pre[class*=language-]>code{background:none;color:var(--code-color);font-family:var(--font-family-code);text-align:left;white-space:pre;word-spacing:normal;word-wrap:normal;word-break:normal;-webkit-hyphens:none;hyphens:none;transition:color var(--color-transition)}pre[class*=language-]{overflow:auto}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.theme-hope-content pre,.theme-hope-content pre[class*=language-]{overflow:auto;margin:.85rem 0;padding:1rem;border-radius:6px;line-height:1.4}.theme-hope-content pre code,.theme-hope-content pre[class*=language-] code{padding:0;border-radius:0;background:transparent!important;overflow-wrap:unset;-webkit-font-smoothing:auto;-moz-osx-font-smoothing:auto}.theme-hope-content .line-number{font-family:var(--font-family-code)}div[class*=language-]{position:relative;border-radius:6px;background:var(--code-bg-color);transition:background-color var(--color-transition)}div[class*=language-]:before{position:absolute;top:.8em;right:1em;z-index:3;color:var(--code-line-color);font-size:.75rem;transition:color var(--color-transition)}div[class*=language-] pre,div[class*=language-] pre[class*=language-]{position:relative;z-index:1;background:transparent}div[class*=language-] .highlight-lines{position:absolute;top:0;left:0;width:100%;padding-top:1rem;line-height:1.4;-webkit-user-select:none;-moz-user-select:none;user-select:none}div[class*=language-] .highlight-line{background-color:var(--code-highlight-line-color);transition:background-color var(--color-transition)}div[class*=language-].line-numbers-mode:after{content:"";position:absolute;top:0;left:0;z-index:2;width:var(--line-numbers-width);height:100%;border-right:1px solid var(--code-highlight-line-color);border-radius:6px 0 0 6px;transition:border-color var(--color-transition)}@media (max-width: 419px){div[class*=language-].line-numbers-mode:after{border-radius:0}}div[class*=language-].line-numbers-mode .highlight-line{position:relative}div[class*=language-].line-numbers-mode .highlight-line:before{content:" ";position:absolute;top:0;left:0;z-index:3;display:block;width:var(--line-numbers-width);height:100%}div[class*=language-].line-numbers-mode pre{vertical-align:middle;margin-left:var(--line-numbers-width);padding-left:.5rem}div[class*=language-].line-numbers-mode .line-numbers{position:absolute;top:0;left:0;width:var(--line-numbers-width);padding:1rem 0;color:var(--code-line-color);line-height:1.4;counter-reset:line-number;text-align:center;transition:color var(--color-transition);transform:translateY(1px)}div[class*=language-].line-numbers-mode .line-number{position:relative;z-index:4;height:1.4em;-webkit-user-select:none;-moz-user-select:none;user-select:none}div[class*=language-].line-numbers-mode .line-number:before{content:counter(line-number);font-size:.85em;counter-increment:line-number}div[class*=language-]:not(.line-numbers-mode) .line-numbers{display:none}div[class*=language-].ext-c:before{content:"c"}div[class*=language-].ext-cpp:before{content:"cpp"}div[class*=language-].ext-cs:before{content:"cs"}div[class*=language-].ext-css:before{content:"css"}div[class*=language-].ext-dart:before{content:"dart"}div[class*=language-].ext-docker:before{content:"docker"}div[class*=language-].ext-fs:before{content:"fs"}div[class*=language-].ext-go:before{content:"go"}div[class*=language-].ext-html:before{content:"html"}div[class*=language-].ext-java:before{content:"java"}div[class*=language-].ext-js:before{content:"js"}div[class*=language-].ext-json:before{content:"json"}div[class*=language-].ext-kt:before{content:"kt"}div[class*=language-].ext-less:before{content:"less"}div[class*=language-].ext-makefile:before{content:"makefile"}div[class*=language-].ext-md:before{content:"md"}div[class*=language-].ext-php:before{content:"php"}div[class*=language-].ext-py:before{content:"py"}div[class*=language-].ext-rb:before{content:"rb"}div[class*=language-].ext-rs:before{content:"rs"}div[class*=language-].ext-sass:before{content:"sass"}div[class*=language-].ext-scss:before{content:"scss"}div[class*=language-].ext-sh:before{content:"sh"}div[class*=language-].ext-ts:before{content:"ts"}div[class*=language-].ext-vue:before{content:"vue"}div[class*=language-].ext-yml:before{content:"yml"}@media (max-width: 419px){.theme-hope-content>div[class*=language-]{margin:.85rem -1.5rem;border-radius:0}}html[data-theme=light] #app{--code-color: #383a42;--code-line-color: rgba(56, 58, 66, .67);--code-bg-color: #ecf4fa;--code-border-color: #c3def3;--code-highlight-line-color: #d8e9f6}html[data-theme=light] code[class*=language-],html[data-theme=light] pre[class*=language-]{-moz-tab-size:2;-o-tab-size:2;tab-size:2}html[data-theme=light] code[class*=language-]::-moz-selection,html[data-theme=light] code[class*=language-] ::-moz-selection,html[data-theme=light] pre[class*=language-]::-moz-selection,html[data-theme=light] pre[class*=language-] ::-moz-selection{background:#e5e5e6;color:inherit}html[data-theme=light] code[class*=language-]::selection,html[data-theme=light] code[class*=language-] ::selection,html[data-theme=light] pre[class*=language-]::selection,html[data-theme=light] pre[class*=language-] ::selection{background:#e5e5e6;color:inherit}html[data-theme=light] .token.comment,html[data-theme=light] .token.prolog,html[data-theme=light] .token.cdata{color:#a0a1a7}html[data-theme=light] .token.doctype,html[data-theme=light] .token.punctuation,html[data-theme=light] .token.entity{color:#383a42}html[data-theme=light] .token.attr-name,html[data-theme=light] .token.class-name,html[data-theme=light] .token.boolean,html[data-theme=light] .token.constant,html[data-theme=light] .token.number,html[data-theme=light] .token.atrule{color:#b76b01}html[data-theme=light] .token.keyword{color:#a626a4}html[data-theme=light] .token.property,html[data-theme=light] .token.tag,html[data-theme=light] .token.symbol,html[data-theme=light] .token.deleted,html[data-theme=light] .token.important{color:#e45649}html[data-theme=light] .token.selector,html[data-theme=light] .token.string,html[data-theme=light] .token.char,html[data-theme=light] .token.builtin,html[data-theme=light] .token.inserted,html[data-theme=light] .token.regex,html[data-theme=light] .token.attr-value,html[data-theme=light] .token.attr-value>.token.punctuation{color:#50a14f}html[data-theme=light] .token.variable,html[data-theme=light] .token.operator,html[data-theme=light] .token.function{color:#4078f2}html[data-theme=light] .token.url{color:#0184bc}html[data-theme=light] .token.attr-value>.token.punctuation.attr-equals,html[data-theme=light] .token.special-attr>.token.attr-value>.token.value.css{color:#383a42}html[data-theme=light] .language-css .token.selector{color:#e45649}html[data-theme=light] .language-css .token.property{color:#383a42}html[data-theme=light] .language-css .token.function,html[data-theme=light] .language-css .token.url>.token.function{color:#0184bc}html[data-theme=light] .language-css .token.url>.token.string.url{color:#50a14f}html[data-theme=light] .language-css .token.important,html[data-theme=light] .language-css .token.atrule .token.rule,html[data-theme=light] .language-javascript .token.operator{color:#a626a4}html[data-theme=light] .language-javascript .token.template-string>.token.interpolation>.token.interpolation-punctuation.punctuation{color:#ca1243}html[data-theme=light] .language-json .token.operator{color:#383a42}html[data-theme=light] .language-json .token.null.keyword{color:#b76b01}html[data-theme=light] .language-markdown .token.url,html[data-theme=light] .language-markdown .token.url>.token.operator,html[data-theme=light] .language-markdown .token.url-reference.url>.token.string{color:#383a42}html[data-theme=light] .language-markdown .token.url>.token.content{color:#4078f2}html[data-theme=light] .language-markdown .token.url>.token.url,html[data-theme=light] .language-markdown .token.url-reference.url{color:#0184bc}html[data-theme=light] .language-markdown .token.blockquote.punctuation,html[data-theme=light] .language-markdown .token.hr.punctuation{color:#a0a1a7;font-style:italic}html[data-theme=light] .language-markdown .token.code-snippet{color:#50a14f}html[data-theme=light] .language-markdown .token.bold .token.content{color:#b76b01}html[data-theme=light] .language-markdown .token.italic .token.content{color:#a626a4}html[data-theme=light] .language-markdown .token.strike .token.content,html[data-theme=light] .language-markdown .token.strike .token.punctuation,html[data-theme=light] .language-markdown .token.list.punctuation,html[data-theme=light] .language-markdown .token.title.important>.token.punctuation{color:#e45649}html[data-theme=light] .token.bold{font-weight:700}html[data-theme=light] .token.comment,html[data-theme=light] .token.italic{font-style:italic}html[data-theme=light] .token.entity{cursor:help}html[data-theme=light] .token.namespace{opacity:.8}html[data-theme=dark] #app{--code-color: #abb2bf;--code-line-color: rgba(171, 178, 191, .67);--code-bg-color: #282c34;--code-border-color: #1d2536;--code-highlight-line-color: #222935}html[data-theme=dark] code[class*=language-],html[data-theme=dark] pre[class*=language-]{text-shadow:0 1px rgba(0,0,0,.3);-moz-tab-size:2;-o-tab-size:2;tab-size:2}@media print{html[data-theme=dark] code[class*=language-],html[data-theme=dark] pre[class*=language-]{text-shadow:none}}html[data-theme=dark] code[class*=language-]::-moz-selection,html[data-theme=dark] code[class*=language-] ::-moz-selection,html[data-theme=dark] pre[class*=language-]::-moz-selection,html[data-theme=dark] pre[class*=language-] ::-moz-selection{background:#3e4451;color:inherit;text-shadow:none}html[data-theme=dark] code[class*=language-]::selection,html[data-theme=dark] code[class*=language-] ::selection,html[data-theme=dark] pre[class*=language-]::selection,html[data-theme=dark] pre[class*=language-] ::selection{background:#3e4451;color:inherit;text-shadow:none}html[data-theme=dark] .token.comment,html[data-theme=dark] .token.prolog,html[data-theme=dark] .token.cdata{color:#5c6370}html[data-theme=dark] .token.doctype,html[data-theme=dark] .token.punctuation,html[data-theme=dark] .token.entity{color:#abb2bf}html[data-theme=dark] .token.attr-name,html[data-theme=dark] .token.class-name,html[data-theme=dark] .token.boolean,html[data-theme=dark] .token.constant,html[data-theme=dark] .token.number,html[data-theme=dark] .token.atrule{color:#d19a66}html[data-theme=dark] .token.keyword{color:#c678dd}html[data-theme=dark] .token.property,html[data-theme=dark] .token.tag,html[data-theme=dark] .token.symbol,html[data-theme=dark] .token.deleted,html[data-theme=dark] .token.important{color:#e06c75}html[data-theme=dark] .token.selector,html[data-theme=dark] .token.string,html[data-theme=dark] .token.char,html[data-theme=dark] .token.builtin,html[data-theme=dark] .token.inserted,html[data-theme=dark] .token.regex,html[data-theme=dark] .token.attr-value,html[data-theme=dark] .token.attr-value>.token.punctuation{color:#98c379}html[data-theme=dark] .token.variable,html[data-theme=dark] .token.operator,html[data-theme=dark] .token.function{color:#61afef}html[data-theme=dark] .token.url{color:#56b6c2}html[data-theme=dark] .token.attr-value>.token.punctuation.attr-equals,html[data-theme=dark] .token.special-attr>.token.attr-value>.token.value.css{color:#abb2bf}html[data-theme=dark] .language-css .token.selector{color:#e06c75}html[data-theme=dark] .language-css .token.property{color:#abb2bf}html[data-theme=dark] .language-css .token.function,html[data-theme=dark] .language-css .token.url>.token.function{color:#56b6c2}html[data-theme=dark] .language-css .token.url>.token.string.url{color:#98c379}html[data-theme=dark] .language-css .token.important,html[data-theme=dark] .language-css .token.atrule .token.rule,html[data-theme=dark] .language-javascript .token.operator{color:#c678dd}html[data-theme=dark] .language-javascript .token.template-string>.token.interpolation>.token.interpolation-punctuation.punctuation{color:#be5046}html[data-theme=dark] .language-json .token.operator{color:#abb2bf}html[data-theme=dark] .language-json .token.null.keyword{color:#d19a66}html[data-theme=dark] .language-markdown .token.url,html[data-theme=dark] .language-markdown .token.url>.token.operator,html[data-theme=dark] .language-markdown .token.url-reference.url>.token.string{color:#abb2bf}html[data-theme=dark] .language-markdown .token.url>.token.content{color:#61afef}html[data-theme=dark] .language-markdown .token.url>.token.url,html[data-theme=dark] .language-markdown .token.url-reference.url{color:#56b6c2}html[data-theme=dark] .language-markdown .token.blockquote.punctuation,html[data-theme=dark] .language-markdown .token.hr.punctuation{color:#5c6370;font-style:italic}html[data-theme=dark] .language-markdown .token.code-snippet{color:#98c379}html[data-theme=dark] .language-markdown .token.bold .token.content{color:#d19a66}html[data-theme=dark] .language-markdown .token.italic .token.content{color:#c678dd}html[data-theme=dark] .language-markdown .token.strike .token.content,html[data-theme=dark] .language-markdown .token.strike .token.punctuation,html[data-theme=dark] .language-markdown .token.list.punctuation,html[data-theme=dark] .language-markdown .token.title.important>.token.punctuation{color:#e06c75}html[data-theme=dark] .token.bold{font-weight:700}html[data-theme=dark] .token.comment,html[data-theme=dark] .token.italic{font-style:italic}html[data-theme=dark] .token.entity{cursor:help}html[data-theme=dark] .token.namespace{opacity:.8}.sr-only{position:absolute;overflow:hidden;clip:rect(0,0,0,0);width:1px;height:1px;margin:-1px;padding:0;border-width:0;white-space:nowrap;-webkit-user-select:none;-moz-user-select:none;user-select:none}.theme-hope-content:not(.custom){max-width:var(--content-width);margin:0 auto;padding:2rem 2.5rem}@media (max-width: 959px){.theme-hope-content:not(.custom){padding:1.5rem}}@media (max-width: 419px){.theme-hope-content:not(.custom){padding:1rem 1.5rem}}.theme-hope-content:not(.custom)>h1,.theme-hope-content:not(.custom)>h2,.theme-hope-content:not(.custom)>h3,.theme-hope-content:not(.custom)>h4,.theme-hope-content:not(.custom)>h5,.theme-hope-content:not(.custom)>h6{margin-top:calc(.5rem - var(--navbar-height));margin-bottom:.5rem;padding-top:calc(1rem + var(--navbar-height));outline:none}.theme-container.no-navbar .theme-hope-content:not(.custom)>h1,.theme-container.no-navbar .theme-hope-content:not(.custom)>h2,.theme-container.no-navbar .theme-hope-content:not(.custom)>h3,.theme-container.no-navbar .theme-hope-content:not(.custom)>h4,.theme-container.no-navbar .theme-hope-content:not(.custom)>h5,.theme-container.no-navbar .theme-hope-content:not(.custom)>h6{margin-top:1.5rem;padding-top:0}.theme-hope-content:not(.custom)>p,.theme-hope-content:not(.custom)>ul p,.theme-hope-content:not(.custom)>ol p{text-align:justify;word-break:break-word;overflow-wrap:break-word;-webkit-hyphens:auto;hyphens:auto}@media (max-width: 419px){.theme-hope-content:not(.custom)>p,.theme-hope-content:not(.custom)>ul p,.theme-hope-content:not(.custom)>ol p{text-align:left}}.theme-hope-content:not(.custom) a:hover{text-decoration:underline}.theme-hope-content:not(.custom) img{max-width:100%}.theme-hope-content.custom{margin:0;padding:0}.theme-hope-content.custom img{max-width:100%}@media (min-width: 1280px){.chart-wrapper::-webkit-scrollbar,.flowchat-wrapper::-webkit-scrollbar,.mermaid-wrapper::-webkit-scrollbar{width:8px;height:8px}.chart-wrapper::-webkit-scrollbar-track-piece,.flowchat-wrapper::-webkit-scrollbar-track-piece,.mermaid-wrapper::-webkit-scrollbar-track-piece{border-radius:8px;background:rgba(0,0,0,.1)}}@media (prefers-reduced-motion: no-preference){:root{scroll-behavior:smooth}}::-webkit-scrollbar{width:6px;height:6px}::-webkit-scrollbar-track-piece{border-radius:6px;background:rgba(0,0,0,.1)}::-webkit-scrollbar-thumb{background-color:var(--theme-color)}::-webkit-scrollbar-thumb:active{background-color:var(--theme-color-light)}@media (max-width: 719px){.hide-in-mobile{display:none!important}}@media (max-width: 959px){.hide-in-pad{display:none!important}}.custom-container.details table{display:table;min-width:75%;max-width:90%}html[data-theme=light] .custom-container.details table{background-color:#fafafa}html[data-theme=light] .custom-container.details th{background-color:#00000003}html[data-theme=dark] .custom-container.details th{background-color:#111}#hero{position:relative;padding:1em;display:flex;align-items:center;justify-content:center;gap:10em;height:100vh}#hero>div{max-width:37em}#hero>div img{height:3em}#hero h1{font-size:4em;color:var(--c-brand);font-weight:700}#hero>img{position:relative;height:12em;animation:slide linear 10s infinite}@keyframes slide{0%{top:0px}25%{top:-50px}75%{top:50px}to{top:0px}}@media screen and (max-width: 900px){#hero>img{height:20vw}#hero h1{font-size:6vw}}@media screen and (max-width: 700px){#hero{flex-direction:column;gap:3em}#hero h1{font-size:10vw}}.blog-image-center{display:block;margin-left:auto;margin-right:auto}@media (max-width: 719px){.no-sidebar .footer-wrapper,.footer-wrapper{padding-left:0rem}}.footer{width:100%;max-width:1000px}.socialsBar{display:flex;gap:25px;font-size:25px;justify-content:center}@media (max-width: 350px){.socialsBar{flex-wrap:wrap}}.linksBox{display:flex;gap:25px;flex-direction:row;justify-content:center;align-items:center}@media (max-width: 350px){.linksBox{flex-direction:column}}.linksBoxLeft,.linksBoxMid,.linksBoxRight{text-align:left;margin:auto}.linksBoxHeader{font-weight:700;font-size:20px}.linksBoxContent,.linksBoxContent a{margin:0}.linksBoxContent ul{list-style:none;padding:0}.linksBoxContent li{margin-bottom:2px}.linksBoxContent a{display:inline-block;line-height:1.2}#logo-footer{fill:var(--text-color);fill-opacity:1;height:50px;margin:10px 0 20px;width:auto}.social-media-wrapper{display:flex;flex-wrap:wrap;justify-content:center;margin:8px auto}.social-media-wrapper .social-media{width:26px;height:26px;margin:4px;transition:transform .18s ease-out .18s;transform:scale(1)}.social-media-wrapper .social-media:hover{cursor:pointer;transform:scale(1.2)}.social-media-wrapper .social-media:after{--balloon-font-size: 8px;padding:.3em .6em}.social-media-wrapper .social-media .icon{width:100%;height:100%}.blogger-info{padding:.5rem;font-family:var(--font-family-fancy)}.page .blogger-info{background:var(--bg-color-float)}.blogger-info .blogger{padding:.5rem;text-align:center}.blogger-info .blogger-avatar{width:8rem;height:8rem;margin:0 auto}.blogger-info .blogger-avatar.round{border-radius:50%}.blogger-info .blogger-name{margin:1rem auto;font-size:22px}.blogger-info .blogger-description{margin:1rem auto;font-size:14px}.blogger-info .num-wrapper{display:flex;width:80%;margin:0 auto 1rem}.blogger-info .num-wrapper>div{width:25%;font-size:13px;text-align:center;cursor:pointer}.blogger-info .num-wrapper>div:hover{color:var(--theme-color)}.blogger-info .num-wrapper>div .num{position:relative;margin-bottom:.5rem;font-weight:600;font-size:20px}html[data-theme=dark] .empty-icon g.people{opacity:.8}html[data-theme=dark] .empty-icon g:not(.people){filter:invert(80%)}.article{position:relative;box-sizing:border-box;width:100%;margin:0 auto 1.25rem;border-radius:.4rem;background:var(--bg-color-float);box-shadow:0 1px 3px 1px var(--card-shadow);text-align:left}@media (max-width: 959px){.article{margin:0 auto 1rem}}@media (max-width: 419px){.article{border-radius:0}}.article:last-child{margin-bottom:0}.article:hover{box-shadow:0 2px 6px 2px var(--card-shadow)}.article>a{display:block;padding:.75rem 1.25rem;color:inherit}@media (max-width: 959px){.article>a{padding:.75rem 1rem}}.article .sticky-icon{position:absolute;top:0;right:0;width:1.5rem;height:1.5rem;color:var(--theme-color)}.article .title{position:relative;display:inline-block;font-size:1.25rem;font-family:var(--font-family-fancy);line-height:1.6}.article .title:after{content:"";position:absolute;bottom:0;left:0;width:100%;height:2px;background:var(--theme-color);visibility:hidden;transition:transform .3s ease-in-out;transform:scaleX(0)}.article .title:hover{cursor:pointer}.article .title:hover:after{visibility:visible;transform:scaleX(1)}.article .title a{color:inherit;font-weight:600}.article .title .lock-icon,.article .title .slides-icon{position:relative;bottom:-.125em;display:inline-block;vertical-align:baseline;width:1em;height:1em;margin-right:.25em;color:var(--theme-color)}.article .excerpt{overflow:hidden;line-height:1.6}@media (max-width: 959px){.article .excerpt{font-size:15px}}@media (max-width: 419px){.article .excerpt{font-size:14px}}.article .excerpt h1{display:none}.article .excerpt h1+p{margin-top:.5em}.article .excerpt p:first-child{margin-top:.5em}.article .excerpt p:last-child{margin-bottom:.5em}.article .excerpt pre{margin:.85rem 0;padding:1.25rem 1.5rem;line-height:1.4}.article .excerpt .line-numbers-mode pre{padding-left:calc(var(--line-numbers-width) + 1rem)}.article .excerpt .code-demo-wrapper,.article .excerpt .footnote-anchor{display:none}.article .excerpt section.footnotes{display:none}.article hr{-webkit-margin-after:.375em;margin-block-end:.375em;-webkit-margin-before:.375em;margin-block-start:.375em}.article .page-info>span{display:flex;flex-shrink:0;align-items:center;margin-right:.5em;line-height:1.8}.article .page-info>span:after{--balloon-font-size: 8px;padding:.3em .6em!important}.pagination-wrapper{margin:1.25rem 0 .75rem;font-weight:600;font-size:15px;line-height:2}.pagination-wrapper .pagination-list{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-evenly;-webkit-user-select:none;-moz-user-select:none;user-select:none}.pagination-wrapper .page-number{display:flex;align-items:stretch;overflow:hidden;height:30px;margin:0 .5rem;border:1px solid var(--border-color, #eaecef);border-radius:.25rem}.pagination-wrapper .page-number div{position:relative;padding:0 .5rem;background:var(--bg-color, #fff);color:var(--theme-color);cursor:pointer}.pagination-wrapper .page-number div:before{content:" ";position:absolute;top:0;bottom:0;left:0;width:1px;background:var(--border-color)}.pagination-wrapper .page-number div:first-child:before{background:transparent}.pagination-wrapper .page-number div:hover{color:var(--theme-color-light)}.pagination-wrapper .page-number div.active{background:var(--theme-color);color:var(--white)}.pagination-wrapper .page-number div.active:before{background:var(--theme-color)}.pagination-wrapper .page-number div.active+div:before{background:var(--theme-color)}.pagination-wrapper .page-number div.prev,.pagination-wrapper .page-number div.next{font-size:13px;line-height:30px}.pagination-wrapper .page-number div.active,.pagination-wrapper .page-number div.ellipsis{cursor:default}.pagination-wrapper .navigate-wrapper{display:flex;align-items:center;justify-content:center;margin:.5rem}.pagination-wrapper .navigate-wrapper input{width:3.5rem;margin:6px 5px;border:1px solid var(--border-color);border-radius:.25em;background:var(--bg-color);color:var(--text-color);outline:none;line-height:2;text-align:center}.pagination-wrapper .navigate-wrapper .navigate{overflow:hidden;padding:0 .75em;border:1px solid var(--border-color);border-radius:.25em;background:var(--bg-color);color:var(--theme-color);outline:none;font-weight:600;font-size:15px;line-height:2;cursor:pointer}.pagination-wrapper .navigate-wrapper .navigate:hover{color:var(--theme-color-light)}.article-wrapper{margin-top:calc(-.5rem - var(--navbar-height));padding-top:calc(var(--navbar-height) + .5rem);text-align:center}.article-wrapper .empty{max-width:560px;margin:0 auto;text-align:center}.blog-hero{position:relative;display:flex;flex-direction:column;justify-content:center;height:450px;margin-bottom:1rem;color:#eee;font-family:var(--font-family-fancy)}@media (max-width: 719px){.blog-hero{height:350px;margin:0 -1.5rem 1rem}}@media (max-width: 419px){.blog-hero{margin:0 0 1rem}}.blog-hero .mask{position:absolute;top:0;right:0;bottom:0;left:0}.blog-hero .mask:after{content:" ";position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;display:block;background:var(--light-grey);opacity:.2}.blog-hero>:not(.mask){position:relative;z-index:2}.blog-hero h1{margin:.5rem auto;font-size:2rem}@media (max-width: 959px){.blog-hero h1{font-size:1.875rem}}@media (max-width: 719px){.blog-hero h1{font-size:2rem}}@media (max-width: 419px){.blog-hero h1{font-size:1.875rem}}.blog-hero .hero-image+h1{margin:0 auto}.blog-hero .description{margin:1.2rem auto 0;font-size:20px}@media (max-width: 959px){.blog-hero .description{font-size:18px}}@media (max-width: 719px){.blog-hero .description{font-size:20px}}@media (max-width: 419px){.blog-hero .description{font-size:18px}}.blog-hero .slide-down-button{border-width:0;background-color:transparent;cursor:pointer;position:absolute;bottom:0;left:calc(50vw - 30px);display:none;width:60px;height:60px;padding:10px}.blog-hero .slide-down-button .icon{width:30px;margin:-15px 0;animation-name:bounce-down;animation-duration:1.5s;animation-timing-function:linear;animation-iteration-count:infinite;animation-direction:alternate}.blog-hero .slide-down-button .icon:first-child{color:#ffffff26}.blog-hero .slide-down-button .icon:nth-child(2){color:#ffffff80}.blog-hero.fullscreen{height:calc(100vh - var(--navbar-height))!important}.blog-hero.fullscreen .mask{background-position-y:top!important}.blog-hero.fullscreen .slide-down-button{display:block}@keyframes bounce-down{0%{transform:translateY(-5px)}to{transform:translateY(5px)}}.category-list-wrapper{position:relative;z-index:2;padding-left:0;list-style:none;font-size:14px}.category-list-wrapper a{color:inherit}.category-list-wrapper .category{display:inline-block;vertical-align:middle;overflow:hidden;margin:.3rem .6rem .8rem;padding:.4rem .8rem;border-radius:.25rem;color:var(--dark-grey);box-shadow:0 1px 4px 0 var(--card-shadow);cursor:pointer;transition:background-color .3s,color .3s}@media (max-width: 419px){.category-list-wrapper .category{font-size:.9rem}}.category-list-wrapper .category .category-num{display:inline-block;min-width:1rem;height:1.2rem;margin-left:.2em;padding:0 .1rem;border-radius:.6rem;color:var(--white);font-size:.7rem;line-height:1.2rem;text-align:center}.category-list-wrapper .category0{background:#fde5e7;color:#ba111f}html[data-theme=dark] .category-list-wrapper .category0{background:#340509;color:#ec2f3e}.category-list-wrapper .category0:hover{background:#f9bec3}html[data-theme=dark] .category-list-wrapper .category0:hover{background:#53080e}.category-list-wrapper .category0.active{background:#cf1322;color:#fff}html[data-theme=dark] .category-list-wrapper .category0.active{background:#a60f1b;color:var(--bg-color)}.category-list-wrapper .category0.active .category-num{background:var(--bg-color);color:#cf1322}.category-list-wrapper .category0 .category-num{background:#cf1322}.category-list-wrapper .category1{background:#ffeee8;color:#f54205}html[data-theme=dark] .category-list-wrapper .category1{background:#441201;color:#fb7649}.category-list-wrapper .category1:hover{background:#fed4c6}html[data-theme=dark] .category-list-wrapper .category1:hover{background:#6d1d02}.category-list-wrapper .category1.active{background:#fa541c;color:#fff}html[data-theme=dark] .category-list-wrapper .category1.active{background:#da3a05;color:var(--bg-color)}.category-list-wrapper .category1.active .category-num{background:var(--bg-color);color:#fa541c}.category-list-wrapper .category1 .category-num{background:#fa541c}.category-list-wrapper .category2{background:#fef5e7;color:#e08e0b}html[data-theme=dark] .category-list-wrapper .category2{background:#3e2703;color:#f5b041}.category-list-wrapper .category2:hover{background:#fce6c4}html[data-theme=dark] .category-list-wrapper .category2:hover{background:#633f05}.category-list-wrapper .category2.active{background:#f39c12;color:#fff}html[data-theme=dark] .category-list-wrapper .category2.active{background:#c77e0a;color:var(--bg-color)}.category-list-wrapper .category2.active .category-num{background:var(--bg-color);color:#f39c12}.category-list-wrapper .category2 .category-num{background:#f39c12}.category-list-wrapper .category3{background:#eafaf1;color:#29b866}html[data-theme=dark] .category-list-wrapper .category3{background:#0c331c;color:#55d98d}.category-list-wrapper .category3:hover{background:#caf3db}html[data-theme=dark] .category-list-wrapper .category3:hover{background:#12522d}.category-list-wrapper .category3.active{background:#2ecc71;color:#fff}html[data-theme=dark] .category-list-wrapper .category3.active{background:#25a35a;color:var(--bg-color)}.category-list-wrapper .category3.active .category-num{background:var(--bg-color);color:#2ecc71}.category-list-wrapper .category3 .category-num{background:#2ecc71}.category-list-wrapper .category4{background:#e6f9ee;color:#219552}html[data-theme=dark] .category-list-wrapper .category4{background:#092917;color:#36d278}.category-list-wrapper .category4:hover{background:#c0f1d5}html[data-theme=dark] .category-list-wrapper .category4:hover{background:#0f4224}.category-list-wrapper .category4.active{background:#25a55b;color:#fff}html[data-theme=dark] .category-list-wrapper .category4.active{background:#1e8449;color:var(--bg-color)}.category-list-wrapper .category4.active .category-num{background:var(--bg-color);color:#25a55b}.category-list-wrapper .category4 .category-num{background:#25a55b}.category-list-wrapper .category5{background:#e1fcfc;color:#0e9595}html[data-theme=dark] .category-list-wrapper .category5{background:#042929;color:#16e1e1}.category-list-wrapper .category5:hover{background:#b4f8f8}html[data-theme=dark] .category-list-wrapper .category5:hover{background:#064242}.category-list-wrapper .category5.active{background:#10a5a5;color:#fff}html[data-theme=dark] .category-list-wrapper .category5.active{background:#0d8484;color:var(--bg-color)}.category-list-wrapper .category5.active .category-num{background:var(--bg-color);color:#10a5a5}.category-list-wrapper .category5 .category-num{background:#10a5a5}.category-list-wrapper .category6{background:#e4f0fe;color:#0862c3}html[data-theme=dark] .category-list-wrapper .category6{background:#021b36;color:#2589f6}.category-list-wrapper .category6:hover{background:#bbdafc}html[data-theme=dark] .category-list-wrapper .category6:hover{background:#042c57}.category-list-wrapper .category6.active{background:#096dd9;color:#fff}html[data-theme=dark] .category-list-wrapper .category6.active{background:#0757ae;color:var(--bg-color)}.category-list-wrapper .category6.active .category-num{background:var(--bg-color);color:#096dd9}.category-list-wrapper .category6 .category-num{background:#096dd9}.category-list-wrapper .category7{background:#f7f1fd;color:#9851e4}html[data-theme=dark] .category-list-wrapper .category7{background:#2a0b4b;color:#bb8ced}.category-list-wrapper .category7:hover{background:#eadbfa}html[data-theme=dark] .category-list-wrapper .category7:hover{background:#431277}.category-list-wrapper .category7.active{background:#aa6fe9;color:#fff}html[data-theme=dark] .category-list-wrapper .category7.active{background:#8733e0;color:var(--bg-color)}.category-list-wrapper .category7.active .category-num{background:var(--bg-color);color:#aa6fe9}.category-list-wrapper .category7 .category-num{background:#aa6fe9}.category-list-wrapper .category8{background:#fdeaf5;color:#e81689}html[data-theme=dark] .category-list-wrapper .category8{background:#400626;color:#ef59ab}.category-list-wrapper .category8:hover{background:#facbe5}html[data-theme=dark] .category-list-wrapper .category8:hover{background:#670a3d}.category-list-wrapper .category8.active{background:#eb2f96;color:#fff}html[data-theme=dark] .category-list-wrapper .category8.active{background:#ce147a;color:var(--bg-color)}.category-list-wrapper .category8.active .category-num{background:var(--bg-color);color:#eb2f96}.category-list-wrapper .category8 .category-num{background:#eb2f96}.tag-list-wrapper{position:relative;z-index:2;display:flex;flex-wrap:wrap;justify-content:flex-start;padding-left:0;list-style:none}.tag-list-wrapper a{color:inherit}.tag-list-wrapper .tag{position:relative;display:inline-block;vertical-align:middle;overflow:hidden;min-width:24px;margin:4px 6px;padding:3px 8px;border-radius:8px;color:var(--white);box-shadow:0 1px 6px 0 var(--box-shadow);font-size:12px;text-align:center;cursor:pointer;transition:background-color .3s,transform .3s}.tag-list-wrapper .tag:hover{cursor:pointer}.tag-list-wrapper .tag.active{transform:scale(1.1)}.tag-list-wrapper .tag0{background:#e91526}.tag-list-wrapper .tag0:hover,.tag-list-wrapper .tag0.active,html[data-theme=dark] .tag-list-wrapper .tag0{background:#c51220}html[data-theme=dark] .tag-list-wrapper .tag0:hover,html[data-theme=dark] .tag-list-wrapper .tag0.active{background:#e91526}.tag-list-wrapper .tag1{background:#fb6533}.tag-list-wrapper .tag1:hover,.tag-list-wrapper .tag1.active,html[data-theme=dark] .tag-list-wrapper .tag1{background:#fa4a0e}html[data-theme=dark] .tag-list-wrapper .tag1:hover,html[data-theme=dark] .tag-list-wrapper .tag1.active{background:#fb6533}.tag-list-wrapper .tag2{background:#f4a62a}.tag-list-wrapper .tag2:hover,.tag-list-wrapper .tag2.active,html[data-theme=dark] .tag-list-wrapper .tag2{background:#ec950c}html[data-theme=dark] .tag-list-wrapper .tag2:hover,html[data-theme=dark] .tag-list-wrapper .tag2.active{background:#f4a62a}.tag-list-wrapper .tag3{background:#40d47f}.tag-list-wrapper .tag3:hover,.tag-list-wrapper .tag3.active,html[data-theme=dark] .tag-list-wrapper .tag3{background:#2cc26b}html[data-theme=dark] .tag-list-wrapper .tag3:hover,html[data-theme=dark] .tag-list-wrapper .tag3.active{background:#40d47f}.tag-list-wrapper .tag4{background:#2bbe69}.tag-list-wrapper .tag4:hover,.tag-list-wrapper .tag4.active,html[data-theme=dark] .tag-list-wrapper .tag4{background:#239d56}html[data-theme=dark] .tag-list-wrapper .tag4:hover,html[data-theme=dark] .tag-list-wrapper .tag4.active{background:#2bbe69}.tag-list-wrapper .tag5{background:#13c3c3}.tag-list-wrapper .tag5:hover,.tag-list-wrapper .tag5.active,html[data-theme=dark] .tag-list-wrapper .tag5{background:#0f9d9d}html[data-theme=dark] .tag-list-wrapper .tag5:hover,html[data-theme=dark] .tag-list-wrapper .tag5.active{background:#13c3c3}.tag-list-wrapper .tag6{background:#0a7bf4}.tag-list-wrapper .tag6:hover,.tag-list-wrapper .tag6.active,html[data-theme=dark] .tag-list-wrapper .tag6{background:#0968ce}html[data-theme=dark] .tag-list-wrapper .tag6:hover,html[data-theme=dark] .tag-list-wrapper .tag6.active{background:#0a7bf4}.tag-list-wrapper .tag7{background:#b37deb}.tag-list-wrapper .tag7:hover,.tag-list-wrapper .tag7.active,html[data-theme=dark] .tag-list-wrapper .tag7{background:#a160e7}html[data-theme=dark] .tag-list-wrapper .tag7:hover,html[data-theme=dark] .tag-list-wrapper .tag7.active{background:#b37deb}.tag-list-wrapper .tag8{background:#ed44a1}.tag-list-wrapper .tag8:hover,.tag-list-wrapper .tag8.active,html[data-theme=dark] .tag-list-wrapper .tag8{background:#ea2290}html[data-theme=dark] .tag-list-wrapper .tag8:hover,html[data-theme=dark] .tag-list-wrapper .tag8.active{background:#ed44a1}.timeline-list-wrapper{--dot-color: #fff;--dot-bar-color: #eaecef;--dot-border-color: #ddd;padding:8px 0}html[data-theme=dark] .timeline-list-wrapper{--dot-color: #444;--dot-bar-color: #333;--dot-border-color: #555}.timeline-list-wrapper .timeline-list-title{cursor:pointer}.timeline-list-wrapper .timeline-list-title .icon{position:relative;bottom:-.125rem;width:16px;height:16px;margin:0 6px}.timeline-list-wrapper .timeline-list-title .num{position:relative;margin:0 2px;font-size:22px}.timeline-list-wrapper .timeline-content{overflow-y:auto;max-height:80vh}.timeline-list-wrapper .timeline-content::-webkit-scrollbar-track-piece{background:transparent}.timeline-list-wrapper .timeline-list{position:relative;box-sizing:border-box;margin:0 8px;list-style:none}.timeline-list-wrapper .timeline-list:after{content:" ";position:absolute;top:14px;left:0;z-index:-1;width:4px;height:calc(100% - 14px);margin-left:-2px;background:var(--dot-bar-color)}.timeline-list-wrapper .timeline-year{position:relative;margin:20px 0 0;color:var(--text-color);font-weight:700;font-size:20px}.timeline-list-wrapper .timeline-year:before{content:" ";position:absolute;top:50%;left:-20px;z-index:2;width:8px;height:8px;margin-top:-4px;margin-left:-4px;border:1px solid var(--dot-border-color);border-radius:50%;background:var(--dot-color)}.timeline-list-wrapper .timeline-year-wrapper{padding-left:0!important}.timeline-list-wrapper .timeline-date{display:inline-block;vertical-align:bottom;width:36px;font-size:12px;line-height:32px}.timeline-list-wrapper .timeline-date:before{content:" ";position:absolute;top:24px;left:-19px;z-index:2;width:6px;height:6px;margin-left:-4px;border:1px solid var(--dot-border-color);border-radius:50%;background:var(--dot-color)}.timeline-list-wrapper .timeline-title{color:inherit;font-size:14px;line-height:32px;cursor:pointer}.timeline-list-wrapper .timeline-item{position:relative;display:flex;padding:12px 0 4px;border-bottom:1px dashed var(--border-color);list-style:none}.timeline-list-wrapper .timeline-item:hover .timeline-date{color:var(--theme-color)}.timeline-list-wrapper .timeline-item:hover .timeline-date:before{border-color:var(--dot-color);background:var(--theme-color)}.timeline-list-wrapper .timeline-item:hover .timeline-title{color:var(--theme-color)}.blog-info-list{margin:8px auto;padding:8px 16px}.page .blog-info-list{border-radius:6px;background:var(--bg-color-float);box-shadow:0 1px 3px 1px var(--card-shadow)}.page .blog-info-list:hover{box-shadow:0 2px 6px 2px var(--card-shadow)}.page .blog-info-list .timeline-list-wrapper .content{max-height:60vh}.blog-info-list .blog-type-wrapper{display:flex;justify-content:center;margin-bottom:8px}.blog-info-list .blog-type-button{border-width:0;background-color:transparent;cursor:pointer;width:44px;height:44px;margin:0 8px;padding:4px;color:var(--grey3)}.blog-info-list .blog-type-button:focus{outline:none}.blog-info-list .blog-type-button .icon-wapper{width:20px;height:20px;padding:8px;border-radius:50%;background:rgba(127,127,127,.15)}html[data-theme=dark] .blog-info-list .blog-type-button .icon-wapper{background:rgba(255,255,255,.15)}.blog-info-list .blog-type-button .icon-wapper:hover{cursor:pointer}.blog-info-list .blog-type-button .icon-wapper.active{background:var(--theme-color-light)}html[data-theme=dark] .blog-info-list .blog-type-button .icon-wapper.active{background:var(--theme-color-dark)}.blog-info-list .blog-type-button .icon{width:100%;height:100%}.blog-info-list .sticky-article-wrapper,.blog-info-list .category-wrapper,.blog-info-list .tag-wrapper{padding:8px 0}.blog-info-list .sticky-article-wrapper .title,.blog-info-list .category-wrapper .title,.blog-info-list .tag-wrapper .title{cursor:pointer}.blog-info-list .sticky-article-wrapper .title .icon,.blog-info-list .category-wrapper .title .icon,.blog-info-list .tag-wrapper .title .icon{position:relative;bottom:-.125rem;width:16px;height:16px;margin:0 6px}.blog-info-list .sticky-article-wrapper .title .num,.blog-info-list .category-wrapper .title .num,.blog-info-list .tag-wrapper .title .num{position:relative;margin:0 2px;font-size:22px;font-family:var(--font-family-fancy)}.blog-info-list .sticky-article-list{margin:8px auto}.blog-info-list .sticky-article{padding:12px 8px 4px;border-bottom:1px dashed var(--grey14)}.blog-info-list .sticky-article:hover{color:var(--theme-color);cursor:pointer}.blog-info-list .category-wrapper .category-list-wrapper,.blog-info-list .tag-wrapper .tag-list-wrapper{margin:8px auto}.sidebar .blog-info-wrapper .blogger-info{display:none}.page .blog-info-wrapper{position:sticky;top:calc(var(--navbar-height) + .75rem);flex:0 0 300px;box-sizing:border-box;height:auto;margin:0 0 .75rem 1rem;transition:all .3s}@media (max-width: 719px){.page .blog-info-wrapper{display:none}}.page .blog-info-wrapper .blogger-info{margin-bottom:16px;padding:8px 0;border-radius:8px;box-shadow:0 1px 3px 1px var(--card-shadow)}.page .blog-info-wrapper .blogger-info:hover{box-shadow:0 2px 6px 2px var(--card-shadow)}.project-panel{position:relative;z-index:2;display:flex;flex-wrap:wrap;align-content:stretch;align-items:stretch;justify-content:flex-start;margin-bottom:12px}.project-panel .project{position:relative;width:calc(33% - 40px);margin:6px 8px;padding:12px;border-radius:8px;background-color:var(--bg-color-float);transition:background-color var(--color-transition),transform var(--transform-transition)}@media (max-width: 959px){.project-panel .project{width:calc(50% - 40px)}}@media (min-width: 1440px){.project-panel .project{width:calc(25% - 40px)}}.project-panel .project:hover{cursor:pointer;transform:scale(.98)}.project-panel .project .name{position:relative;z-index:2;color:var(--grey3);font-weight:500;font-size:16px}.project-panel .project .desc{position:relative;z-index:2;margin:6px 0;color:var(--dark-grey);font-size:13px}.project-panel .project .image{position:relative;z-index:2;float:right;width:40px;height:40px}.project-panel .project .icon{position:relative;z-index:2;float:right;width:20px;height:20px}.project-panel .project0{background:#fde5e7}.project-panel .project0:hover{background:#f9bec3}html[data-theme=dark] .project-panel .project0{background:#340509}html[data-theme=dark] .project-panel .project0:hover{background:#53080e}.project-panel .project1{background:#ffeee8}.project-panel .project1:hover{background:#fed4c6}html[data-theme=dark] .project-panel .project1{background:#441201}html[data-theme=dark] .project-panel .project1:hover{background:#6d1d02}.project-panel .project2{background:#fef5e7}.project-panel .project2:hover{background:#fce6c4}html[data-theme=dark] .project-panel .project2{background:#3e2703}html[data-theme=dark] .project-panel .project2:hover{background:#633f05}.project-panel .project3{background:#eafaf1}.project-panel .project3:hover{background:#caf3db}html[data-theme=dark] .project-panel .project3{background:#0c331c}html[data-theme=dark] .project-panel .project3:hover{background:#12522d}.project-panel .project4{background:#e6f9ee}.project-panel .project4:hover{background:#c0f1d5}html[data-theme=dark] .project-panel .project4{background:#092917}html[data-theme=dark] .project-panel .project4:hover{background:#0f4224}.project-panel .project5{background:#e1fcfc}.project-panel .project5:hover{background:#b4f8f8}html[data-theme=dark] .project-panel .project5{background:#042929}html[data-theme=dark] .project-panel .project5:hover{background:#064242}.project-panel .project6{background:#e4f0fe}.project-panel .project6:hover{background:#bbdafc}html[data-theme=dark] .project-panel .project6{background:#021b36}html[data-theme=dark] .project-panel .project6:hover{background:#042c57}.project-panel .project7{background:#f7f1fd}.project-panel .project7:hover{background:#eadbfa}html[data-theme=dark] .project-panel .project7{background:#2a0b4b}html[data-theme=dark] .project-panel .project7:hover{background:#431277}.project-panel .project8{background:#fdeaf5}.project-panel .project8:hover{background:#facbe5}html[data-theme=dark] .project-panel .project8{background:#400626}html[data-theme=dark] .project-panel .project8:hover{background:#670a3d}.page.blog .blog-home{flex:1;overflow:hidden;max-width:780px}.page.blog .theme-hope-content:empty{padding:0}.article-type-wrapper{position:relative;z-index:2;display:flex;align-items:center;justify-content:center;padding-left:0;list-style:none;font-weight:600;font-size:18px}@media (max-width: 419px){.article-type-wrapper{font-size:16px}}.article-type{position:relative;vertical-align:middle;margin:.3em .8em;line-height:1.2;cursor:pointer}.article-type:after{content:" ";position:absolute;right:50%;bottom:-6px;left:50%;height:2px;border-radius:1px;background:var(--theme-color);visibility:hidden;transition:left .2s ease-in-out,right .2s ease-in-out}.article-type a{display:inline-block;color:inherit;transition:all .3s ease-in-out}.article-type.active{position:relative}.article-type.active a{color:var(--theme-color);transform:scale(1.1)}.article-type:hover:after,.article-type.active:after{right:calc(50% - 8px);left:calc(50% - 8px);visibility:visible}.timeline-wrapper{--dot-color: #fff;--dot-bar-color: #eaecef;--dot-border-color: #ddd;max-width:740px;margin:0 auto;padding:40px 0}@media (max-width: 719px){.timeline-wrapper{margin:0 1.2rem}}html[data-theme=dark] .timeline-wrapper{--dot-color: #444;--dot-bar-color: #333;--dot-border-color: #555}.timeline-wrapper #toc{right:0;left:unset;min-width:0}.timeline-wrapper .toc-wrapper{position:relative;z-index:10}.timeline-wrapper .timeline-content{position:relative;box-sizing:border-box;padding-left:76px;list-style:none}.timeline-wrapper .timeline-content:after{content:" ";position:absolute;top:14px;left:64px;z-index:-1;width:4px;height:calc(100% - 38px);margin-left:-2px;background:var(--dot-bar-color)}.timeline-wrapper .motto{position:relative;color:var(--text-color);font-size:18px}@media (min-width: 1280px){.timeline-wrapper .motto{font-size:20px}}.timeline-wrapper .motto:before{content:" ";position:absolute;top:50%;left:-12px;z-index:2;width:8px;height:8px;margin-top:-6px;margin-left:-6px;border:2px solid var(--dot-border-color);border-radius:50%;background:var(--dot-color)}.timeline-wrapper .timeline-year-title{margin-top:calc(3rem - var(--navbar-height));margin-bottom:.5rem;padding-top:var(--navbar-height);color:var(--text-color);font-weight:700;font-size:26px;font-family:var(--font-family-fancy)}.timeline-wrapper .timeline-year-title span{position:relative}.timeline-wrapper .timeline-year-title span:before{content:" ";position:absolute;top:50%;left:-12px;z-index:2;width:8px;height:8px;margin-top:-6px;margin-left:-6px;border:2px solid var(--dot-border-color);border-radius:50%;background:var(--dot-color)}.timeline-wrapper .timeline-year-wrapper{padding-left:0!important}.timeline-wrapper .timeline-date{position:absolute;right:calc(100% + 24px);width:40px;font-size:14px;line-height:30px;text-align:right}.timeline-wrapper .timeline-date:before{content:" ";position:absolute;top:50%;right:-16px;z-index:2;width:6px;height:6px;margin-top:-6px;margin-left:-6px;border:2px solid var(--dot-border-color);border-radius:50%;background:var(--dot-color)}.timeline-wrapper .timeline-title{position:relative;display:block;color:inherit;font-size:16px;line-height:30px}.timeline-wrapper .timeline-item{position:relative;z-index:3;display:flex;padding:30px 0 10px;border-bottom:1px dashed var(--border-color);list-style:none}.timeline-wrapper .timeline-item:hover{cursor:pointer}.timeline-wrapper .timeline-item:hover .timeline-date{font-size:16px;transition:font-size .3s ease-out}.timeline-wrapper .timeline-item:hover .timeline-date:before{border-color:var(--theme-color);background-color:var(--bg-color-active)}.timeline-wrapper .timeline-item:hover .timeline-title{color:var(--theme-color);font-size:18px;transition:font-size .3s ease-out}.blog-main{flex:1;max-width:780px}.blog-main .article-title{margin:10px 15px;font-size:1.8rem}.theme-container .page.blog{display:flex;flex-direction:column;justify-content:space-between;box-sizing:border-box;min-height:100vh;margin:0 auto;padding-top:var(--navbar-height);padding-bottom:2rem;background:var(--bg-color-back)}@media (min-width: 1440px){.theme-container.has-toc .page.blog{padding-right:0}}.blog-page-wrapper{display:flex;align-items:flex-start;justify-content:center;box-sizing:border-box;width:100%;margin:0 auto;padding:0 2rem}@media (max-width: 959px){.blog-page-wrapper{padding:0 1rem}}@media (max-width: 419px){.blog-page-wrapper{padding:0}}:root{--search-bg-color: #ffffff;--search-accent-color: #3eaf7c;--search-text-color: #2c3e50;--search-border-color: #eaecef;--search-item-text-color: #5d81a5;--search-item-focus-bg-color: #f3f4f5;--search-input-width: 8rem;--search-result-width: 20rem}.search-box{display:inline-block;position:relative;margin-left:1rem}.search-box input{cursor:text;width:var(--search-input-width);height:2rem;color:var(--search-text-color);display:inline-block;border:1px solid var(--search-border-color);border-radius:2rem;font-size:.9rem;line-height:2rem;padding:0 .5rem 0 2rem;outline:none;transition:all ease .3s;background:var(--search-bg-color) url(/assets/search.0782d0d1.svg) .6rem .5rem no-repeat;background-size:1rem}.search-box input:focus{cursor:auto;border-color:var(--search-accent-color)}.search-box .suggestions{background:var(--search-bg-color);width:var(--search-result-width);position:absolute;top:2rem;right:0;border:1px solid var(--search-border-color);border-radius:6px;padding:.4rem;list-style-type:none}.search-box .suggestion{line-height:1.4;padding:.4rem .6rem;border-radius:4px;cursor:pointer}.search-box .suggestion.focus{background-color:var(--search-item-focus-bg-color)}.search-box .suggestion.focus a{color:var(--search-accent-color)}.search-box .suggestion a{white-space:normal;color:var(--search-item-text-color)}.search-box .suggestion .page-title{font-weight:600}.search-box .suggestion .page-header{font-size:.9em;margin-left:.25em}@media (max-width: 720px){.search-box input{cursor:pointer;width:0;border-color:transparent;position:relative}.search-box input:focus{cursor:text;left:0;width:10rem}}@media (max-width: 420px){.search-box input:focus{width:8rem}.search-box .suggestions{width:calc(100vw - 4rem);right:-.5rem}}.image-preview{display:flex;justify-content:space-evenly;align-items:center;flex-wrap:wrap}.image-preview>img{box-sizing:border-box;width:33.3%!important;padding:9px;border-radius:16px}.image-preview>img:hover{cursor:zoom-in}@media (max-width: 719px){.image-preview>img{width:50%!important}}@media (max-width: 419px){.image-preview>img{width:100%!important}} diff --git a/assets/styleguide.61d5da11.png b/assets/styleguide.61d5da11.png new file mode 100644 index 0000000000..1c7672aedd Binary files /dev/null and b/assets/styleguide.61d5da11.png differ diff --git a/assets/summary.html.139b70e8.js b/assets/summary.html.139b70e8.js new file mode 100644 index 0000000000..6c76f66f7c --- /dev/null +++ b/assets/summary.html.139b70e8.js @@ -0,0 +1 @@ +import{_ as o,o as s,c as t,a as e,b as a}from"./app.0e1565ce.js";const n={},r=e("h2",{id:"summary",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#summary","aria-hidden":"true"},"#"),a(" Summary")],-1),i=e("p",null,"If you finished this chapter, you should be an Pulsar-hacking master. We've discussed how you should work with JavaScript and CoffeeScript, and how to put it to good use in creating packages. You should also be able to do this in your own created theme now.",-1),l=e("p",null,"Even when something goes wrong, you should be able to debug this easily. But also fewer things should go wrong, because you are capable of writing great specs for Pulsar.",-1),u=e("p",null,"In the next chapter, we\u2019ll go into more of a deep dive on individual internal APIs and systems of Pulsar, even looking at some Pulsar source to see how things are really getting done.",-1),d=[r,i,l,u];function c(h,m){return s(),t("div",null,d)}const g=o(n,[["render",c],["__file","summary.html.vue"]]);export{g as default}; diff --git a/assets/summary.html.17457156.js b/assets/summary.html.17457156.js new file mode 100644 index 0000000000..ea11c3e389 --- /dev/null +++ b/assets/summary.html.17457156.js @@ -0,0 +1 @@ +import{_ as s,o as a,c as t,a as e,b as o}from"./app.0e1565ce.js";const r={},n=e("h2",{id:"summary",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#summary","aria-hidden":"true"},"#"),o(" Summary")],-1),c=e("p",null,"You should now have a better understanding of some of the core Pulsar APIs and systems.",-1),d=[n,c];function m(_,u){return a(),t("div",null,d)}const l=s(r,[["render",m],["__file","summary.html.vue"]]);export{l as default}; diff --git a/assets/summary.html.1b92ae24.js b/assets/summary.html.1b92ae24.js new file mode 100644 index 0000000000..14af0a4960 --- /dev/null +++ b/assets/summary.html.1b92ae24.js @@ -0,0 +1 @@ +import{_ as e,o as a,c as o,a as t,b as s}from"./app.0e1565ce.js";const n={},r=t("h2",{id:"summary",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#summary","aria-hidden":"true"},"#"),s(" Summary")],-1),i=t("p",null,"You should now have a basic understanding of what Pulsar is and what you want to do with it. You should also have it installed on your system and be able to use it for the most basic text editing operations.",-1),d=t("p",null,"Now you're ready to start digging into the fun stuff.",-1),u=[r,i,d];function c(h,l){return a(),o("div",null,u)}const m=e(n,[["render",c],["__file","summary.html.vue"]]);export{m as default}; diff --git a/assets/summary.html.2480fd44.js b/assets/summary.html.2480fd44.js new file mode 100644 index 0000000000..87bc14b2e9 --- /dev/null +++ b/assets/summary.html.2480fd44.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-14a6a286","path":"/docs/launch-manual/sections/getting-started/sections/summary.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Summary","slug":"summary","link":"#summary","children":[]}],"git":{"updatedTime":1670961579000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":0.18,"words":53},"filePathRelative":"docs/launch-manual/sections/getting-started/sections/summary.md"}');export{e as data}; diff --git a/assets/summary.html.27ef905f.js b/assets/summary.html.27ef905f.js new file mode 100644 index 0000000000..a496fa68b6 --- /dev/null +++ b/assets/summary.html.27ef905f.js @@ -0,0 +1 @@ +import{_ as e,o as a,c as o,a as t,b as n}from"./app.0e1565ce.js";const s={},r=t("h3",{id:"summary",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#summary","aria-hidden":"true"},"#"),n(" Summary")],-1),i=t("p",null,"At this point you should be something of an Atom master user. You should be able to navigate and manipulate your text and files like a wizard. You should also be able to customize Atom backwards and forwards to make it look and act just how you want it to.",-1),c=t("p",null,"In the next chapter, we're going to kick it up a notch: we'll take a look at changing and adding new functionality to the core of Atom itself. We're going to start creating packages for Atom. If you can dream it, you can build it.",-1),d=[r,i,c];function l(u,m){return a(),o("div",null,d)}const _=e(s,[["render",l],["__file","summary.html.vue"]]);export{_ as default}; diff --git a/assets/summary.html.6c334e6e.js b/assets/summary.html.6c334e6e.js new file mode 100644 index 0000000000..61c7912fe4 --- /dev/null +++ b/assets/summary.html.6c334e6e.js @@ -0,0 +1 @@ +const a=JSON.parse('{"key":"v-4a1ab042","path":"/docs/launch-manual/sections/using-pulsar/sections/summary.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Summary","slug":"summary","link":"#summary","children":[]}],"git":{"updatedTime":1669229414000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":2}]},"readingTime":{"minutes":0.33,"words":99},"filePathRelative":"docs/launch-manual/sections/using-pulsar/sections/summary.md"}');export{a as data}; diff --git a/assets/summary.html.7e60db5d.js b/assets/summary.html.7e60db5d.js new file mode 100644 index 0000000000..882a16828a --- /dev/null +++ b/assets/summary.html.7e60db5d.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-82dca6e2","path":"/docs/launch-manual/sections/core-hacking/sections/summary.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Summary","slug":"summary","link":"#summary","children":[]}],"git":{"updatedTime":1670466847000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":0.38,"words":114},"filePathRelative":"docs/launch-manual/sections/core-hacking/sections/summary.md"}');export{e as data}; diff --git a/assets/summary.html.89a8b88a.js b/assets/summary.html.89a8b88a.js new file mode 100644 index 0000000000..badac0939b --- /dev/null +++ b/assets/summary.html.89a8b88a.js @@ -0,0 +1 @@ +import{_ as e,o as t,c as o,a,b as s}from"./app.0e1565ce.js";const n={},r=a("h2",{id:"summary",tabindex:"-1"},[a("a",{class:"header-anchor",href:"#summary","aria-hidden":"true"},"#"),s(" Summary")],-1),i=a("p",null,"At this point you should be something of a Pulsar master user. You should be able to navigate and manipulate your text and files like a wizard. You should also be able to customize Pulsar backwards and forwards to make it look and act just how you want it to.",-1),c=a("p",null,"In the next section, we're going to kick it up a notch: we'll take a look at changing and adding new functionality to the core of Pulsar itself. We're going to start creating packages for Pulsar. If you can dream it, you can build it.",-1),l=[r,i,c];function u(d,h){return t(),o("div",null,l)}const _=e(n,[["render",u],["__file","summary.html.vue"]]);export{_ as default}; diff --git a/assets/summary.html.8a21214b.js b/assets/summary.html.8a21214b.js new file mode 100644 index 0000000000..7cc0faa202 --- /dev/null +++ b/assets/summary.html.8a21214b.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-1b3c033e","path":"/docs/launch-manual/sections/behind-pulsar/sections/summary.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Summary","slug":"summary","link":"#summary","children":[]}],"git":{"updatedTime":1668711072000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":0.06,"words":17},"filePathRelative":"docs/launch-manual/sections/behind-pulsar/sections/summary.md"}');export{e as data}; diff --git a/assets/summary.html.a00ca4a9.js b/assets/summary.html.a00ca4a9.js new file mode 100644 index 0000000000..97749ef428 --- /dev/null +++ b/assets/summary.html.a00ca4a9.js @@ -0,0 +1 @@ +import{_ as e,o,c as a,a as t,b as s}from"./app.0e1565ce.js";const n={},r=t("h3",{id:"summary",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#summary","aria-hidden":"true"},"#"),s(" Summary")],-1),i=t("p",null,"You should now have a basic understanding of what Atom is and what you want to do with it. You should also have it installed on your system and be able to use it for the most basic text editing operations.",-1),d=t("p",null,"Now you're ready to start digging into the fun stuff.",-1),u=[r,i,d];function c(h,l){return o(),a("div",null,u)}const m=e(n,[["render",c],["__file","summary.html.vue"]]);export{m as default}; diff --git a/assets/summary.html.a6f48395.js b/assets/summary.html.a6f48395.js new file mode 100644 index 0000000000..0c7a361816 --- /dev/null +++ b/assets/summary.html.a6f48395.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-7d723830","path":"/docs/atom-archive/hacking-atom/sections/summary.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"Summary","slug":"summary","link":"#summary","children":[]}],"git":{"updatedTime":1664050274000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":2}]},"readingTime":{"minutes":0.37,"words":112},"filePathRelative":"docs/atom-archive/hacking-atom/sections/summary.md"}');export{e as data}; diff --git a/assets/summary.html.b8077cd9.js b/assets/summary.html.b8077cd9.js new file mode 100644 index 0000000000..1cbbd75ed1 --- /dev/null +++ b/assets/summary.html.b8077cd9.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-21ac7a6d","path":"/docs/atom-archive/using-atom/sections/summary.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"Summary","slug":"summary","link":"#summary","children":[]}],"git":{"updatedTime":1664634443000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1},{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":0.33,"words":99},"filePathRelative":"docs/atom-archive/using-atom/sections/summary.md"}');export{e as data}; diff --git a/assets/summary.html.b8867dc0.js b/assets/summary.html.b8867dc0.js new file mode 100644 index 0000000000..06d819a916 --- /dev/null +++ b/assets/summary.html.b8867dc0.js @@ -0,0 +1 @@ +import{_ as o,o as t,c as s,a as e,b as a}from"./app.0e1565ce.js";const n={},r=e("h3",{id:"summary",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#summary","aria-hidden":"true"},"#"),a(" Summary")],-1),i=e("p",null,"If you finished this chapter, you should be an Atom-hacking master. We've discussed how you should work with CoffeeScript, and how to put it to good use in creating packages. You should also be able to do this in your own created theme now.",-1),d=e("p",null,"Even when something goes wrong, you should be able to debug this easily. But also fewer things should go wrong, because you are capable of writing great specs for Atom.",-1),h=e("p",null,"In the next chapter, we\u2019ll go into more of a deep dive on individual internal APIs and systems of Atom, even looking at some Atom source to see how things are really getting done.",-1),c=[r,i,d,h];function l(u,m){return t(),s("div",null,c)}const g=o(n,[["render",l],["__file","summary.html.vue"]]);export{g as default}; diff --git a/assets/summary.html.c86acfc7.js b/assets/summary.html.c86acfc7.js new file mode 100644 index 0000000000..3ad0d1e65a --- /dev/null +++ b/assets/summary.html.c86acfc7.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-293dceca","path":"/docs/atom-archive/behind-atom/sections/summary.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"Summary","slug":"summary","link":"#summary","children":[]}],"git":{"updatedTime":1668309800000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":3},{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":0.06,"words":17},"filePathRelative":"docs/atom-archive/behind-atom/sections/summary.md"}');export{e as data}; diff --git a/assets/summary.html.e2da166f.js b/assets/summary.html.e2da166f.js new file mode 100644 index 0000000000..d3dd8f9763 --- /dev/null +++ b/assets/summary.html.e2da166f.js @@ -0,0 +1 @@ +import{_ as s,o as a,c as t,a as e,b as o}from"./app.0e1565ce.js";const r={},n=e("h3",{id:"summary",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#summary","aria-hidden":"true"},"#"),o(" Summary")],-1),c=e("p",null,"You should now have a better understanding of some of the core Atom APIs and systems.",-1),m=[n,c];function d(_,u){return a(),t("div",null,m)}const i=s(r,[["render",d],["__file","summary.html.vue"]]);export{i as default}; diff --git a/assets/summary.html.fe9be6bd.js b/assets/summary.html.fe9be6bd.js new file mode 100644 index 0000000000..85c79c0f00 --- /dev/null +++ b/assets/summary.html.fe9be6bd.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-3162898f","path":"/docs/atom-archive/getting-started/sections/summary.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"Summary","slug":"summary","link":"#summary","children":[]}],"git":{"updatedTime":1663961800000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":0.18,"words":53},"filePathRelative":"docs/atom-archive/getting-started/sections/summary.md"}');export{e as data}; diff --git a/assets/sunsetting.aedab22a.png b/assets/sunsetting.aedab22a.png new file mode 100644 index 0000000000..550338702b Binary files /dev/null and b/assets/sunsetting.aedab22a.png differ diff --git a/assets/survey1-infographic1.6eaa37f0.png b/assets/survey1-infographic1.6eaa37f0.png new file mode 100644 index 0000000000..54eec996e7 Binary files /dev/null and b/assets/survey1-infographic1.6eaa37f0.png differ diff --git a/assets/survey1-infographic2.9389690a.png b/assets/survey1-infographic2.9389690a.png new file mode 100644 index 0000000000..b9f45fb9fc Binary files /dev/null and b/assets/survey1-infographic2.9389690a.png differ diff --git a/assets/survey2-icons.3bc9eaf1.png b/assets/survey2-icons.3bc9eaf1.png new file mode 100644 index 0000000000..8ab88a274a Binary files /dev/null and b/assets/survey2-icons.3bc9eaf1.png differ diff --git a/assets/survey2-q1.5103b594.png b/assets/survey2-q1.5103b594.png new file mode 100644 index 0000000000..79967e72bb Binary files /dev/null and b/assets/survey2-q1.5103b594.png differ diff --git a/assets/survey2-q2.fbf62014.png b/assets/survey2-q2.fbf62014.png new file mode 100644 index 0000000000..b920235502 Binary files /dev/null and b/assets/survey2-q2.fbf62014.png differ diff --git a/assets/survey2-q3.2fdd208f.png b/assets/survey2-q3.2fdd208f.png new file mode 100644 index 0000000000..2d6e64d60f Binary files /dev/null and b/assets/survey2-q3.2fdd208f.png differ diff --git a/assets/symbol-provider-bookmarks-example.96a7a05d.png b/assets/symbol-provider-bookmarks-example.96a7a05d.png new file mode 100644 index 0000000000..b5461b2a3d Binary files /dev/null and b/assets/symbol-provider-bookmarks-example.96a7a05d.png differ diff --git a/assets/symbol-provider-notes.0753ac08.jpg b/assets/symbol-provider-notes.0753ac08.jpg new file mode 100644 index 0000000000..58162bc157 Binary files /dev/null and b/assets/symbol-provider-notes.0753ac08.jpg differ diff --git a/assets/symbol.a045bd68.png b/assets/symbol.a045bd68.png new file mode 100644 index 0000000000..5b40317f4e Binary files /dev/null and b/assets/symbol.a045bd68.png differ diff --git a/assets/symbol.b88bf5a9.js b/assets/symbol.b88bf5a9.js new file mode 100644 index 0000000000..1ce7011f4f --- /dev/null +++ b/assets/symbol.b88bf5a9.js @@ -0,0 +1 @@ +const s="/assets/goto.8ea021d6.png",o="/assets/symbol.a045bd68.png";export{s as _,o as a}; diff --git a/assets/symbolprovider-config-api.html.04edf651.js b/assets/symbolprovider-config-api.html.04edf651.js new file mode 100644 index 0000000000..de52bfa222 --- /dev/null +++ b/assets/symbolprovider-config-api.html.04edf651.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-0c09001f","path":"/docs/packages/core/autocomplete-plus/symbolprovider-config-api.html","title":"SymbolProvider Config API","lang":"en-us","frontmatter":{"lang":"en-us","title":"SymbolProvider Config API"},"excerpt":"","headers":[{"level":2,"title":"Typename Objects","slug":"typename-objects","link":"#typename-objects","children":[]},{"level":2,"title":"Finding Scope Selectors","slug":"finding-scope-selectors","link":"#finding-scope-selectors","children":[]}],"git":{"updatedTime":1663970614000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":2}]},"readingTime":{"minutes":1.58,"words":475},"filePathRelative":"docs/packages/core/autocomplete-plus/symbolprovider-config-api.md"}');export{e as data}; diff --git a/assets/symbolprovider-config-api.html.63c64aa2.js b/assets/symbolprovider-config-api.html.63c64aa2.js new file mode 100644 index 0000000000..5e8edd66f6 --- /dev/null +++ b/assets/symbolprovider-config-api.html.63c64aa2.js @@ -0,0 +1,50 @@ +import{_ as r,o as l,c as p,a as e,b as s,d as n,w as c,f as a,r as t}from"./app.0e1565ce.js";const d="/assets/symbol-provider-notes.0753ac08.jpg",u="/assets/scopename.20d826fe.png",m={},v=a('

    SymbolProvider Config API

    Note

    Please note that its possible this is outdated, as its original version was published by @'Phil' on Jan 24, 2017.

    The builtin SymbolProvider allows showing the types of symbols in the suggestion list.

    symbol-provider-notes

    The icon colors are intended to match the syntax color of the symbol type. e.g. functions are blue in this theme, so the function icon is also blue.

    ',5),b=e("code",null,"SymbolProvider",-1),h={href:"https://github.com/pulsar-edit/language-coffee-script/blob/d86c8963dcee0ab811da05a175b2218045d0c124/settings/language-coffee-script.cson#L5",target:"_blank",rel:"noopener noreferrer"},y=a(`
    # An example for the CoffeeScript language
    +'.source.coffee':
    +  autocomplete:
    +    symbols:
    +      class:
    +        selector: '.class.name, .inherited-class, .instance.type'
    +        typePriority: 4
    +      function:
    +        selector: '.function.name'
    +        typePriority: 3
    +      variable:
    +        selector: '.variable'
    +        typePriority: 2
    +      '': # the catch-all
    +        selector: '.source'
    +        typePriority: 1
    +      builtin:
    +        suggestions: [
    +          'one'
    +          'two'
    +        ]
    +

    A more generic example:

    
    +'.source.language':
    +  autocomplete:
    +    symbols:
    +      typename1:
    +        selector: '.some, .selectors'
    +        typePriority: 1
    +      typename2:
    +        suggestions: [...]
    +      typename3:
    +        ...
    +

    Any number of Typename objects are supported, and typename can be anything you choose. However, you are encouraged to use one of the predefined typenames. There are predefined styles for the following types:

    builtin
    +class
    +constant
    +function
    +import
    +keyword
    +method
    +module
    +mixin
    +package
    +property
    +require
    +snippet
    +tag
    +type
    +value
    +variable
    +

    Typename Objects

    Typename objects support the following properties:

    `,7),g=a("
  • selector: The selector that matches your symbol types. e.g. '.variable.name'. You can also have several selectors separated by commas, just like in CSS '.variable.name, .storage.variable'
  • typePriority: The priority this Typename object has over others. e.g. in our CoffeeScript example above, if a symbol is tagged with the function type in one part of the code, but class in another part of the code, it will be displayed to the user as a class because class has a higher typePriority
  • ",2),k=e("code",null,"suggestions",-1),f=e("code",null,"['Math', 'Array', ...]",-1),_=e("code",null,"suggestions",-1),x=e("h2",{id:"finding-scope-selectors",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#finding-scope-selectors","aria-hidden":"true"},"#"),s(" Finding Scope Selectors")],-1),w=e("p",null,[s("Coming up with the selectors for a given Typename object might be the hardest part of defining the "),e("code",null,"autocomplete.symbols"),s(" config. Fortunately there is a tool to help")],-1),P={href:"https://atom.io/docs/latest/getting-started-atom-basics#command-palette",target:"_blank",rel:"noopener noreferrer"},S=e("code",null,"log cursor scope",-1),T=e("p",null,[e("img",{src:u,alt:"scopenames"})],-1),j=e("p",null,[s("Each bullet in the box is a node. The last bullet is the symbol itself, and each preceding line is a parent of the symbol \u2014 just like CSS. With this information, you can see that the symbol can be matched with several selectors: "),e("code",null,"'.variable'"),s(", "),e("code",null,"'.variable.assignment'"),s(", "),e("code",null,"'.source.coffee .variable'"),s(", etc.")],-1);function C(A,E){const o=t("ExternalLinkIcon"),i=t("RouterLink");return l(),p("div",null,[v,e("p",null,[s("Each language can tune how the "),b,s(" tags symbols by modifying the "),e("a",h,[s("config of the language package"),n(o)]),s(".")]),y,e("ul",null,[g,e("li",null,[k,s(": This allows you to specify explicit completions for some scope. A good use is builtins: e.g. "),f,s(" in JavaScript "),e("ul",null,[e("li",null,[s("Items in the "),_,s(" list can also objects using any of the "),n(i,{to:"/docs/packages/core/autocomplete-plus/provider-api.html#suggestions"},{default:c(()=>[s("properties")]),_:1}),s(" from the provider API.")])])])]),x,w,e("p",null,[s("Open the "),e("a",P,[s("command palette"),n(o)]),s(", then search for "),S,s(". You will be presented with a blue box like the following:")]),T,j])}const L=r(m,[["render",C],["__file","symbolprovider-config-api.html.vue"]]);export{L as default}; diff --git a/assets/symbols-view-demo.17f7e949.webm b/assets/symbols-view-demo.17f7e949.webm new file mode 100644 index 0000000000..e54cc49402 Binary files /dev/null and b/assets/symbols-view-demo.17f7e949.webm differ diff --git a/assets/symbols-view-demo.946869fd.mp4 b/assets/symbols-view-demo.946869fd.mp4 new file mode 100644 index 0000000000..95f42c1b4f Binary files /dev/null and b/assets/symbols-view-demo.946869fd.mp4 differ diff --git a/assets/symbols-view-json.951c60f0.png b/assets/symbols-view-json.951c60f0.png new file mode 100644 index 0000000000..626c068c11 Binary files /dev/null and b/assets/symbols-view-json.951c60f0.png differ diff --git a/assets/symbols-view-tree-sitter-demo.16754842.webm b/assets/symbols-view-tree-sitter-demo.16754842.webm new file mode 100644 index 0000000000..de17244e19 Binary files /dev/null and b/assets/symbols-view-tree-sitter-demo.16754842.webm differ diff --git a/assets/symbols-view-tree-sitter-demo.21174d35.mp4 b/assets/symbols-view-tree-sitter-demo.21174d35.mp4 new file mode 100644 index 0000000000..319893c14d Binary files /dev/null and b/assets/symbols-view-tree-sitter-demo.21174d35.mp4 differ diff --git a/assets/symbols-view.91132e73.png b/assets/symbols-view.91132e73.png new file mode 100644 index 0000000000..6852c8cb7f Binary files /dev/null and b/assets/symbols-view.91132e73.png differ diff --git a/assets/test.a0002333.png b/assets/test.a0002333.png new file mode 100644 index 0000000000..5e49dd3ff2 Binary files /dev/null and b/assets/test.a0002333.png differ diff --git a/assets/the-init-file.html.106c20da.js b/assets/the-init-file.html.106c20da.js new file mode 100644 index 0000000000..bfccb6f00e --- /dev/null +++ b/assets/the-init-file.html.106c20da.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-69f77fd8","path":"/docs/launch-manual/sections/core-hacking/sections/the-init-file.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"The Init File","slug":"the-init-file","link":"#the-init-file","children":[]}],"git":{"updatedTime":1670466847000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":1.19,"words":356},"filePathRelative":"docs/launch-manual/sections/core-hacking/sections/the-init-file.md"}');export{e as data}; diff --git a/assets/the-init-file.html.3d79e2ce.js b/assets/the-init-file.html.3d79e2ce.js new file mode 100644 index 0000000000..afdccecb85 --- /dev/null +++ b/assets/the-init-file.html.3d79e2ce.js @@ -0,0 +1,9 @@ +import{_ as l,o as p,c as r,a as e,b as n,d as a,w as o,f as c,r as i}from"./app.0e1565ce.js";const d={},u=e("h3",{id:"the-init-file",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#the-init-file","aria-hidden":"true"},"#"),n(" The Init File")],-1),m=e("code",null,"init.coffee",-1),f=e("span",{class:"platform-mac platform-linux"},[e("code",null,"~/.atom")],-1),k=e("span",{class:"platform-windows"},[e("code",null,"%USERPROFILE%\\.atom")],-1),h={href:"https://atom.io/docs/api/latest",target:"_blank",rel:"noopener noreferrer"},g=e("a",{href:"../package-word-count"},"Package: Word Count",-1),_=c(`

    You can open the init.coffee file in an editor from the Atom > Init ScriptFile > Init ScriptEdit > Init Script menu. This file can also be named init.js and contain JavaScript code.

    For example, if you have the Audio Beep configuration setting enabled, you could add the following code to your init.coffee file to have Atom greet you with an audio beep every time it loads:

    atom.beep()
    +
    `,3),v=e("code",null,"init.coffee",-1),b={href:"https://atom.io/docs/api/latest/Selection",target:"_blank",rel:"noopener noreferrer"},w={href:"https://atom.io/docs/api/latest/Clipboard",target:"_blank",rel:"noopener noreferrer"},x=c(`
    atom.commands.add 'atom-text-editor', 'markdown:paste-as-link', ->
    +  return unless editor = atom.workspace.getActiveTextEditor()
    +
    +  selection = editor.getLastSelection()
    +  clipboardText = atom.clipboard.read()
    +
    +  selection.insertText("[#{selection.getText()}](#{clipboardText})")
    +
    `,1);function y(A,I){const t=i("ExternalLinkIcon"),s=i("RouterLink");return p(),r("div",null,[u,e("p",null,[n("When Atom finishes loading, it will evaluate "),m,n(" in your "),f,k,n(" directory, giving you a chance to run CoffeeScript code to make customizations. Code in this file has full access to "),e("a",h,[n("Atom's API"),a(t)]),n(". If customizations become extensive, consider creating a package, which we will cover in "),g,n(".")]),_,e("p",null,[n("Because "),v,n(" provides access to Atom's API, you can use it to implement useful commands without creating a new package or extending an existing one. Here's a command which uses the "),e("a",b,[n("Selection API"),a(t)]),n(" and "),e("a",w,[n("Clipboard API"),a(t)]),n(" to construct a Markdown link from the selected text and the clipboard contents as the URL:")]),x,e("p",null,[n("Now, reload Atom and use the "),a(s,{to:"/getting-started/sections/atom-basics/#command-palette"},{default:o(()=>[n("Command Palette")]),_:1}),n(` to execute the new command, "Markdown: Paste As Link", by name. And if you'd like to trigger the command via a keyboard shortcut, you can define a `),a(s,{to:"/using-atom/sections/basic-customization/#customizing-keybindings"},{default:o(()=>[n("keybinding for the command")]),_:1}),n(".")])])}const C=l(d,[["render",y],["__file","the-init-file.html.vue"]]);export{C as default}; diff --git a/assets/the-init-file.html.87635cd5.js b/assets/the-init-file.html.87635cd5.js new file mode 100644 index 0000000000..324c006635 --- /dev/null +++ b/assets/the-init-file.html.87635cd5.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-7061e28e","path":"/docs/atom-archive/hacking-atom/sections/the-init-file.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"The Init File","slug":"the-init-file","link":"#the-init-file","children":[]}],"git":{"updatedTime":1664050274000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":2}]},"readingTime":{"minutes":1.02,"words":305},"filePathRelative":"docs/atom-archive/hacking-atom/sections/the-init-file.md"}');export{e as data}; diff --git a/assets/the-init-file.html.a2ffb654.js b/assets/the-init-file.html.a2ffb654.js new file mode 100644 index 0000000000..8c9dc52bfd --- /dev/null +++ b/assets/the-init-file.html.a2ffb654.js @@ -0,0 +1,13 @@ +import{_ as p,o as l,c as u,a as s,b as n,d as t,w as r,e,f as i,r as o}from"./app.0e1565ce.js";const d={},k=s("h2",{id:"the-init-file",tabindex:"-1"},[s("a",{class:"header-anchor",href:"#the-init-file","aria-hidden":"true"},"#"),n(" The Init File")],-1),m={class:"custom-container info"},h=s("p",{class:"custom-container-title"},"Info",-1),f=s("code",null,"init.coffee",-1),v=s("code",null,"init.js",-1),g=s("strong",null,[s("em",null,"LNX/MAC")],-1),_=s("code",null,"~/.pulsar",-1),b=s("strong",null,[s("em",null,"WIN")],-1),w=s("code",null,"%USERPROFILE%\\.pulsar",-1),x={href:"https://atom.io/docs/api/latest",target:"_blank",rel:"noopener noreferrer"},y=s("a",{href:"#package-word-count"},"Package: Word Count",-1),I=i(`

    You can open the init.js file in an editor from the LNX: Atom > Init Script - MAC: File > Init Script - WIN: Edit > Init Script menu.

    For example, if you have the Audio Beep configuration setting enabled, you could add the following code to your init.js file to have Pulsar greet you with an audio beep every time it loads:

    atom.beep();
    +
    `,3),A=s("code",null,"init.js",-1),P={href:"https://atom.io/docs/api/latest/Selection",target:"_blank",rel:"noopener noreferrer"},C={href:"https://atom.io/docs/api/latest/Clipboard",target:"_blank",rel:"noopener noreferrer"},S=i(`
    atom.commands.add("atom-text-editor", "markdown:paste-as-link", () => {
    +	let clipboardText, editor, selection;
    +	if (!(editor = atom.workspace.getActiveTextEditor())) {
    +		return;
    +	}
    +	selection = editor.getLastSelection();
    +	clipboardText = atom.clipboard.read();
    +	return selection.insertText(
    +		"[" + selection.getText() + "](" + clipboardText + ")"
    +	);
    +});
    +

    Now, reload Pulsar and use the Command Palette to execute the new command, Markdown: Paste As Link, by name. And if you'd like to trigger the command via a keyboard shortcut, you can define a keybinding for the command.

    `,2);function T(j,q){const c=o("RouterLink"),a=o("ExternalLinkIcon");return l(),u("div",null,[k,s("div",m,[h,s("p",null,[n("The default init file for Pulsar has been changed from the previous CoffeeScript "),f,n(" file used by Atom to JavaScript. The CoffeeScript file will still work but should you wish to reference the specific version of this document for it then you should look at the "),t(c,{to:"/docs/atom-archive/hacking-atom/#the-init-file"},{default:r(()=>[n("Atom Archive")]),_:1}),n(".")])]),s("p",null,[n("When Pulsar finishes loading, it will evaluate "),v,n(" in your "),g,n(": "),_,n(" - "),b,n(": "),w,n(" directory, giving you a chance to run JavaScript code to make customizations. Code in this file has full access to "),s("a",x,[n("Pulsar's API"),t(a)]),n("."),e("TODO: Replace link when we have the API documented"),n(" If customizations become extensive, consider creating a package, which we will cover in "),y,n(".")]),I,e("TODO: All API links to be updated when it is documented"),s("p",null,[n("Because "),A,n(" provides access to Pulsar's API, you can use it to implement useful commands without creating a new package or extending an existing one. Here's a command which uses the "),s("a",P,[n("Selection API"),t(a)]),n(" and "),s("a",C,[n("Clipboard API"),t(a)]),n(" to construct a Markdown link from the selected text and the clipboard contents as the URL:")]),S])}const N=p(d,[["render",T],["__file","the-init-file.html.vue"]]);export{N as default}; diff --git a/assets/the-menu-bar-disappeared-how-do-i-get-it-back.html.29f26548.js b/assets/the-menu-bar-disappeared-how-do-i-get-it-back.html.29f26548.js new file mode 100644 index 0000000000..41bcb11e71 --- /dev/null +++ b/assets/the-menu-bar-disappeared-how-do-i-get-it-back.html.29f26548.js @@ -0,0 +1 @@ +import{_ as e,o as t,c as a,f as o}from"./app.0e1565ce.js";const n={},d=o('

    The menu bar disappeared, how do I get it back?

    If you're running Windows or Linux and you don't see the menu bar, it may have been accidentally toggled it off. You can bring it back from the Command Palette with Window: Toggle Menu Bar or by pressing Alt.

    You can disable hiding the menu bar with Alt by unchecking Settings > Core > Auto Hide Menu Bar.

    ',3),i=[d];function r(c,h){return t(),a("div",null,i)}const u=e(n,[["render",r],["__file","the-menu-bar-disappeared-how-do-i-get-it-back.html.vue"]]);export{u as default}; diff --git a/assets/the-menu-bar-disappeared-how-do-i-get-it-back.html.5285b7d6.js b/assets/the-menu-bar-disappeared-how-do-i-get-it-back.html.5285b7d6.js new file mode 100644 index 0000000000..e8f2826a44 --- /dev/null +++ b/assets/the-menu-bar-disappeared-how-do-i-get-it-back.html.5285b7d6.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-5bf72dd3","path":"/docs/atom-archive/faq/sections/the-menu-bar-disappeared-how-do-i-get-it-back.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"The menu bar disappeared, how do I get it back?","slug":"the-menu-bar-disappeared-how-do-i-get-it-back","link":"#the-menu-bar-disappeared-how-do-i-get-it-back","children":[]}],"git":{"updatedTime":1667690985000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1},{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":0.25,"words":74},"filePathRelative":"docs/atom-archive/faq/sections/the-menu-bar-disappeared-how-do-i-get-it-back.md"}');export{e as data}; diff --git a/assets/theme-boundary.d11d0274.png b/assets/theme-boundary.d11d0274.png new file mode 100644 index 0000000000..0193ccc548 Binary files /dev/null and b/assets/theme-boundary.d11d0274.png differ diff --git a/assets/theme-side-by-side.33cf8d5d.js b/assets/theme-side-by-side.33cf8d5d.js new file mode 100644 index 0000000000..772984a956 --- /dev/null +++ b/assets/theme-side-by-side.33cf8d5d.js @@ -0,0 +1 @@ +const s="/assets/theme-boundary.d11d0274.png",t="/assets/styleguide.61d5da11.png",e="/assets/theme-side-by-side.ca407a3d.png";export{s as _,t as a,e as b}; diff --git a/assets/theme-side-by-side.ca407a3d.png b/assets/theme-side-by-side.ca407a3d.png new file mode 100644 index 0000000000..7925d7051f Binary files /dev/null and b/assets/theme-side-by-side.ca407a3d.png differ diff --git a/assets/theme.a183f0e9.png b/assets/theme.a183f0e9.png new file mode 100644 index 0000000000..e4bc3de7c0 Binary files /dev/null and b/assets/theme.a183f0e9.png differ diff --git a/assets/themes.40122774.png b/assets/themes.40122774.png new file mode 100644 index 0000000000..aad6255ef2 Binary files /dev/null and b/assets/themes.40122774.png differ diff --git a/assets/timecop.1c72c1e3.png b/assets/timecop.1c72c1e3.png new file mode 100644 index 0000000000..ea4cd0ab2c Binary files /dev/null and b/assets/timecop.1c72c1e3.png differ diff --git a/assets/title-bar-no-tab.2e70eb94.png b/assets/title-bar-no-tab.2e70eb94.png new file mode 100644 index 0000000000..388f7010b1 Binary files /dev/null and b/assets/title-bar-no-tab.2e70eb94.png differ diff --git a/assets/title-bar-tab.c7644600.png b/assets/title-bar-tab.c7644600.png new file mode 100644 index 0000000000..ec80d964aa Binary files /dev/null and b/assets/title-bar-tab.c7644600.png differ diff --git a/assets/toggle.8625d841.png b/assets/toggle.8625d841.png new file mode 100644 index 0000000000..0032955ed5 Binary files /dev/null and b/assets/toggle.8625d841.png differ diff --git a/assets/tools-of-the-trade.html.06204920.js b/assets/tools-of-the-trade.html.06204920.js new file mode 100644 index 0000000000..bb246f5788 --- /dev/null +++ b/assets/tools-of-the-trade.html.06204920.js @@ -0,0 +1,14 @@ +import{_ as o,o as t,c as r,e as i,a as s,b as e,d as n,f as l,r as c}from"./app.0e1565ce.js";const p={},u=s("h2",{id:"tools-of-the-trade",tabindex:"-1"},[s("a",{class:"header-anchor",href:"#tools-of-the-trade","aria-hidden":"true"},"#"),e(" Tools of the Trade")],-1),d=s("p",null,"To begin, there are a few things we'll assume you know, at least to some degree. Since all of Pulsar is implemented using web technologies, we have to assume you know web technologies such as JavaScript and CSS. Specifically, we'll be using Less, which is a preprocessor for CSS.",-1),f={href:"https://github.com/pulsar-edit/.github/blob/main/project-birth/CONTRIBUTING-DURING-START.md#decaffeination",target:"_blank",rel:"noopener noreferrer"},k=l(`
    MyPackageView = require './my-package-view'
    +
    +module.exports =
    +  myPackageView: null
    +
    +  activate: (state) ->
    +    @myPackageView = new MyPackageView(state.myPackageViewState)
    +
    +  deactivate: ->
    +    @myPackageView.destroy()
    +
    +  serialize: ->
    +    myPackageViewState: @myPackageView.serialize()
    +
    `,1),m={href:"http://coffeescript.org",target:"_blank",rel:"noopener noreferrer"},h={href:"http://lesscss.org/",target:"_blank",rel:"noopener noreferrer"};function b(v,g){const a=c("ExternalLinkIcon");return t(),r("div",null,[u,d,i("This whole section needs to be reworked once decaff properly starts on the core"),s("p",null,[e('While much of Pulsar has been converted to JavaScript, a lot of older code is still implemented in CoffeeScript but the process of "decaffeination" is ongoing, to continue this conversion feel free to read '),s("a",f,[e("more"),n(a)]),e(". Additionally, Pulsar's default configuration language is CSON, which is based on CoffeeScript. If you don't know CoffeeScript, but you are familiar with JavaScript, you shouldn't have too much trouble. Here is an example of some simple CoffeeScript code:")]),k,s("p",null,[e("We'll go over examples like this in a bit, but this is what the language looks like. Just about everything you can do with CoffeeScript in Pulsar is also doable in JavaScript. You can brush up on CoffeeScript at "),s("a",m,[e("coffeescript.org"),n(a)]),e(".")]),s("p",null,[e("Less is an even simpler transition from CSS. It adds a number of useful things like variables and functions to CSS. You can learn about Less at "),s("a",h,[e("lesscss.org"),n(a)]),e(". Our usage of Less won't get too complex in this book however, so as long as you know basic CSS you should be fine.")])])}const y=o(p,[["render",b],["__file","tools-of-the-trade.html.vue"]]);export{y as default}; diff --git a/assets/tools-of-the-trade.html.9e252ffc.js b/assets/tools-of-the-trade.html.9e252ffc.js new file mode 100644 index 0000000000..c351dda9bd --- /dev/null +++ b/assets/tools-of-the-trade.html.9e252ffc.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-4bffba0e","path":"/docs/launch-manual/sections/core-hacking/sections/tools-of-the-trade.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Tools of the Trade","slug":"tools-of-the-trade","link":"#tools-of-the-trade","children":[]}],"git":{"updatedTime":1670806090000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":4}]},"readingTime":{"minutes":0.93,"words":280},"filePathRelative":"docs/launch-manual/sections/core-hacking/sections/tools-of-the-trade.md"}');export{e as data}; diff --git a/assets/tools-of-the-trade.html.9e3c72a0.js b/assets/tools-of-the-trade.html.9e3c72a0.js new file mode 100644 index 0000000000..5ec9b65ca4 --- /dev/null +++ b/assets/tools-of-the-trade.html.9e3c72a0.js @@ -0,0 +1,14 @@ +import{_ as o,o as t,c as i,a,b as e,d as n,f as p,r}from"./app.0e1565ce.js";const l={},c=p(`

    Tools of the Trade

    To begin, there are a few things we'll assume you know, at least to some degree. Since all of Atom is implemented using web technologies, we have to assume you know web technologies such as JavaScript and CSS. Specifically, we'll be using Less, which is a preprocessor for CSS.

    While much of Atom has been converted to JavaScript, a lot of older code has been left implemented in CoffeeScript because changing it would have been too risky. Additionally, Atom's default configuration language is CSON, which is based on CoffeeScript. If you don't know CoffeeScript, but you are familiar with JavaScript, you shouldn't have too much trouble. Here is an example of some simple CoffeeScript code:

    MyPackageView = require './my-package-view'
    +
    +module.exports =
    +  myPackageView: null
    +
    +  activate: (state) ->
    +    @myPackageView = new MyPackageView(state.myPackageViewState)
    +
    +  deactivate: ->
    +    @myPackageView.destroy()
    +
    +  serialize: ->
    +    myPackageViewState: @myPackageView.serialize()
    +
    `,4),u={href:"http://coffeescript.org",target:"_blank",rel:"noopener noreferrer"},d={href:"http://lesscss.org/",target:"_blank",rel:"noopener noreferrer"};function k(m,f){const s=r("ExternalLinkIcon");return t(),i("div",null,[c,a("p",null,[e("We'll go over examples like this in a bit, but this is what the language looks like. Just about everything you can do with CoffeeScript in Atom is also doable in JavaScript. You can brush up on CoffeeScript at "),a("a",u,[e("coffeescript.org"),n(s)]),e(".")]),a("p",null,[e("Less is an even simpler transition from CSS. It adds a number of useful things like variables and functions to CSS. You can learn about Less at "),a("a",d,[e("lesscss.org"),n(s)]),e(". Our usage of Less won't get too complex in this book however, so as long as you know basic CSS you should be fine.")])])}const v=o(l,[["render",k],["__file","tools-of-the-trade.html.vue"]]);export{v as default}; diff --git a/assets/tools-of-the-trade.html.af54b9d7.js b/assets/tools-of-the-trade.html.af54b9d7.js new file mode 100644 index 0000000000..b91e518b28 --- /dev/null +++ b/assets/tools-of-the-trade.html.af54b9d7.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-5aa3b8b8","path":"/docs/atom-archive/hacking-atom/sections/tools-of-the-trade.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"Tools of the Trade","slug":"tools-of-the-trade","link":"#tools-of-the-trade","children":[]}],"git":{"updatedTime":1664050274000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":2}]},"readingTime":{"minutes":0.82,"words":245},"filePathRelative":"docs/atom-archive/hacking-atom/sections/tools-of-the-trade.md"}');export{e as data}; diff --git a/assets/tools.1553d192.png b/assets/tools.1553d192.png new file mode 100644 index 0000000000..5b11e5c9b8 Binary files /dev/null and b/assets/tools.1553d192.png differ diff --git a/assets/tools.52491106.js b/assets/tools.52491106.js new file mode 100644 index 0000000000..e15c1233fe --- /dev/null +++ b/assets/tools.52491106.js @@ -0,0 +1 @@ +const s="/assets/tools.1553d192.png";export{s as _}; diff --git a/assets/tree-sitter-advanced-indentation-part-1.33a3a459.mp4 b/assets/tree-sitter-advanced-indentation-part-1.33a3a459.mp4 new file mode 100644 index 0000000000..8ae3752fa8 Binary files /dev/null and b/assets/tree-sitter-advanced-indentation-part-1.33a3a459.mp4 differ diff --git a/assets/tree-sitter-advanced-indentation-part-1.98936604.webm b/assets/tree-sitter-advanced-indentation-part-1.98936604.webm new file mode 100644 index 0000000000..a81a48361e Binary files /dev/null and b/assets/tree-sitter-advanced-indentation-part-1.98936604.webm differ diff --git a/assets/tree-sitter-advanced-indentation-part-2.9c4500ba.mp4 b/assets/tree-sitter-advanced-indentation-part-2.9c4500ba.mp4 new file mode 100644 index 0000000000..5e66385f71 Binary files /dev/null and b/assets/tree-sitter-advanced-indentation-part-2.9c4500ba.mp4 differ diff --git a/assets/tree-sitter-advanced-indentation-part-2.e941a3ba.webm b/assets/tree-sitter-advanced-indentation-part-2.e941a3ba.webm new file mode 100644 index 0000000000..22b84cc981 Binary files /dev/null and b/assets/tree-sitter-advanced-indentation-part-2.e941a3ba.webm differ diff --git a/assets/tree-sitter-block-comment-code-fold.28ab19f5.mp4 b/assets/tree-sitter-block-comment-code-fold.28ab19f5.mp4 new file mode 100644 index 0000000000..b18577939d Binary files /dev/null and b/assets/tree-sitter-block-comment-code-fold.28ab19f5.mp4 differ diff --git a/assets/tree-sitter-block-comment-code-fold.b6a82fbf.webm b/assets/tree-sitter-block-comment-code-fold.b6a82fbf.webm new file mode 100644 index 0000000000..60843b020e Binary files /dev/null and b/assets/tree-sitter-block-comment-code-fold.b6a82fbf.webm differ diff --git a/assets/tree-sitter-comment-scopes-diagram.88665730.png b/assets/tree-sitter-comment-scopes-diagram.88665730.png new file mode 100644 index 0000000000..6c8436d89d Binary files /dev/null and b/assets/tree-sitter-comment-scopes-diagram.88665730.png differ diff --git a/assets/tree-sitter-html-parser-injection.b2a40e1c.png b/assets/tree-sitter-html-parser-injection.b2a40e1c.png new file mode 100644 index 0000000000..1058710aee Binary files /dev/null and b/assets/tree-sitter-html-parser-injection.b2a40e1c.png differ diff --git a/assets/tree-sitter-injection-illustration.c0de6ada.png b/assets/tree-sitter-injection-illustration.c0de6ada.png new file mode 100644 index 0000000000..1adf89637c Binary files /dev/null and b/assets/tree-sitter-injection-illustration.c0de6ada.png differ diff --git a/assets/tree-sitter-javascript-block-comment.812590a9.png b/assets/tree-sitter-javascript-block-comment.812590a9.png new file mode 100644 index 0000000000..69e1394010 Binary files /dev/null and b/assets/tree-sitter-javascript-block-comment.812590a9.png differ diff --git a/assets/tree-sitter-log-cursor-scope.1a459f7a.png b/assets/tree-sitter-log-cursor-scope.1a459f7a.png new file mode 100644 index 0000000000..efde6ba222 Binary files /dev/null and b/assets/tree-sitter-log-cursor-scope.1a459f7a.png differ diff --git a/assets/tree-sitter-select-larger-syntax-node.07d04699.mp4 b/assets/tree-sitter-select-larger-syntax-node.07d04699.mp4 new file mode 100644 index 0000000000..8b700871fd Binary files /dev/null and b/assets/tree-sitter-select-larger-syntax-node.07d04699.mp4 differ diff --git a/assets/tree-sitter-select-larger-syntax-node.522d277a.webm b/assets/tree-sitter-select-larger-syntax-node.522d277a.webm new file mode 100644 index 0000000000..b314f60edf Binary files /dev/null and b/assets/tree-sitter-select-larger-syntax-node.522d277a.webm differ diff --git a/assets/tree-sitter-simple-fold-example.8070404a.png b/assets/tree-sitter-simple-fold-example.8070404a.png new file mode 100644 index 0000000000..6593cd20a4 Binary files /dev/null and b/assets/tree-sitter-simple-fold-example.8070404a.png differ diff --git a/assets/tree-sitter-simple-folding.5bf37994.webm b/assets/tree-sitter-simple-folding.5bf37994.webm new file mode 100644 index 0000000000..5df0dbe751 Binary files /dev/null and b/assets/tree-sitter-simple-folding.5bf37994.webm differ diff --git a/assets/tree-sitter-simple-folding.ac70c9fb.mp4 b/assets/tree-sitter-simple-folding.ac70c9fb.mp4 new file mode 100644 index 0000000000..4b5c52cebd Binary files /dev/null and b/assets/tree-sitter-simple-folding.ac70c9fb.mp4 differ diff --git a/assets/tree-sitter-simple-indentation.6a2dba5c.webm b/assets/tree-sitter-simple-indentation.6a2dba5c.webm new file mode 100644 index 0000000000..60431b108b Binary files /dev/null and b/assets/tree-sitter-simple-indentation.6a2dba5c.webm differ diff --git a/assets/tree-sitter-simple-indentation.7d0eae68.mp4 b/assets/tree-sitter-simple-indentation.7d0eae68.mp4 new file mode 100644 index 0000000000..2732ea046e Binary files /dev/null and b/assets/tree-sitter-simple-indentation.7d0eae68.mp4 differ diff --git a/assets/tree-sitter-snippet-context-example.8124fdcd.mp4 b/assets/tree-sitter-snippet-context-example.8124fdcd.mp4 new file mode 100644 index 0000000000..f62331ecb7 Binary files /dev/null and b/assets/tree-sitter-snippet-context-example.8124fdcd.mp4 differ diff --git a/assets/tree-sitter-snippet-context-example.f7c0921c.webm b/assets/tree-sitter-snippet-context-example.f7c0921c.webm new file mode 100644 index 0000000000..bf0a8b3e39 Binary files /dev/null and b/assets/tree-sitter-snippet-context-example.f7c0921c.webm differ diff --git a/assets/tree-sitter-string-scopes-diagram.24d020b9.png b/assets/tree-sitter-string-scopes-diagram.24d020b9.png new file mode 100644 index 0000000000..ad85e6bb37 Binary files /dev/null and b/assets/tree-sitter-string-scopes-diagram.24d020b9.png differ diff --git a/assets/tree-sitter-string-scopes-diagram.2db37c5c.js b/assets/tree-sitter-string-scopes-diagram.2db37c5c.js new file mode 100644 index 0000000000..000fd6abdf --- /dev/null +++ b/assets/tree-sitter-string-scopes-diagram.2db37c5c.js @@ -0,0 +1 @@ +const s="/assets/tree-sitter-string-scopes-diagram.24d020b9.png";export{s as _}; diff --git a/assets/tree-sitter-todo-url-injection.231723ac.png b/assets/tree-sitter-todo-url-injection.231723ac.png new file mode 100644 index 0000000000..858351a58a Binary files /dev/null and b/assets/tree-sitter-todo-url-injection.231723ac.png differ diff --git a/assets/tree-sitter-tools-comment-tree.f78df2f6.png b/assets/tree-sitter-tools-comment-tree.f78df2f6.png new file mode 100644 index 0000000000..7ad9caba6f Binary files /dev/null and b/assets/tree-sitter-tools-comment-tree.f78df2f6.png differ diff --git a/assets/tree-sitter-tools-css-block.47b94e88.png b/assets/tree-sitter-tools-css-block.47b94e88.png new file mode 100644 index 0000000000..5949b6a5af Binary files /dev/null and b/assets/tree-sitter-tools-css-block.47b94e88.png differ diff --git a/assets/tree-sitter-tools-indentation-example.52c421af.png b/assets/tree-sitter-tools-indentation-example.52c421af.png new file mode 100644 index 0000000000..e481b57340 Binary files /dev/null and b/assets/tree-sitter-tools-indentation-example.52c421af.png differ diff --git a/assets/tree-sitter-tools-string-tree.3fe45d43.png b/assets/tree-sitter-tools-string-tree.3fe45d43.png new file mode 100644 index 0000000000..0d238198fd Binary files /dev/null and b/assets/tree-sitter-tools-string-tree.3fe45d43.png differ diff --git a/assets/tree-sitter-tools-style-element.6c7c3556.png b/assets/tree-sitter-tools-style-element.6c7c3556.png new file mode 100644 index 0000000000..a2d1862bab Binary files /dev/null and b/assets/tree-sitter-tools-style-element.6c7c3556.png differ diff --git a/assets/tree-sitter-tools-template-string.34be245b.png b/assets/tree-sitter-tools-template-string.34be245b.png new file mode 100644 index 0000000000..d9b38d330b Binary files /dev/null and b/assets/tree-sitter-tools-template-string.34be245b.png differ diff --git a/assets/tree-sitter-tools-tree-list.7475c4eb.png b/assets/tree-sitter-tools-tree-list.7475c4eb.png new file mode 100644 index 0000000000..046d38b94f Binary files /dev/null and b/assets/tree-sitter-tools-tree-list.7475c4eb.png differ diff --git a/assets/tree-sitter.0892e73e.png b/assets/tree-sitter.0892e73e.png new file mode 100644 index 0000000000..5fdeecaadd Binary files /dev/null and b/assets/tree-sitter.0892e73e.png differ diff --git a/assets/tree-sitter.6a39e323.js b/assets/tree-sitter.6a39e323.js new file mode 100644 index 0000000000..c522c08514 --- /dev/null +++ b/assets/tree-sitter.6a39e323.js @@ -0,0 +1 @@ +const e="/assets/tree-sitter.0892e73e.png";export{e as _}; diff --git a/assets/unity-theme.516a5ae9.js b/assets/unity-theme.516a5ae9.js new file mode 100644 index 0000000000..15460aa39b --- /dev/null +++ b/assets/unity-theme.516a5ae9.js @@ -0,0 +1 @@ +const s="/assets/packages-install.5546715f.png",t="/assets/package-specific-settings.970b531c.png",a="/assets/themes.40122774.png",e="/assets/unity-theme.a9891126.png";export{s as _,t as a,a as b,e as c}; diff --git a/assets/unity-theme.a9891126.png b/assets/unity-theme.a9891126.png new file mode 100644 index 0000000000..35719702c0 Binary files /dev/null and b/assets/unity-theme.a9891126.png differ diff --git a/assets/update-atom-macos.07b32cce.js b/assets/update-atom-macos.07b32cce.js new file mode 100644 index 0000000000..f85fae448f --- /dev/null +++ b/assets/update-atom-macos.07b32cce.js @@ -0,0 +1 @@ +const s="/assets/update-atom-macos.6e7a13d8.jpeg";export{s as _}; diff --git a/assets/update-atom-macos.6e7a13d8.jpeg b/assets/update-atom-macos.6e7a13d8.jpeg new file mode 100644 index 0000000000..f0f27df567 Binary files /dev/null and b/assets/update-atom-macos.6e7a13d8.jpeg differ diff --git a/assets/upgrading-your-package.html.6a49dd16.js b/assets/upgrading-your-package.html.6a49dd16.js new file mode 100644 index 0000000000..15ab1a05a2 --- /dev/null +++ b/assets/upgrading-your-package.html.6a49dd16.js @@ -0,0 +1,283 @@ +import{_ as c}from"./spec-deps.b3f6a1b6.js";import{_ as i}from"./dep-cop.6353fb49.js";import{_ as l,o as r,c as d,a as n,b as s,d as e,w as u,f as o,r as t}from"./app.0e1565ce.js";const m={},k=o(`

    Upgrading Your Package

    This document will guide you through the large bits of upgrading your package to work with 1.0 APIs.

    TL;DR

    We've set deprecation messages and errors in strategic places to help make sure you don't miss anything. You should be able to get 95% of the way to an updated package just by fixing errors and deprecations. There are a couple of things you can do to get the full effect of all the errors and deprecations.

    Use atom-space-pen-views

    If you use any class from require 'atom' with a $ or View in the name, add the atom-space-pen-views module to your package's package.json file's dependencies:

    {
    +	"dependencies": {
    +		"atom-space-pen-views": "^2.0.3"
    +	}
    +}
    +

    Then run apm install in your package directory.

    Require views from atom-space-pen-views

    Anywhere you are requiring one of the following from atom you need to require them from atom-space-pen-views instead.

    # require these from 'atom-space-pen-views' rather than 'atom'
    +$
    +$$
    +$$$
    +View
    +TextEditorView
    +ScrollView
    +SelectListView
    +

    So this:

    # Old way
    +{$, TextEditorView, View, GitRepository} = require 'atom'
    +

    Would be replaced by this:

    # New way
    +{GitRepository} = require 'atom'
    +{$, TextEditorView, View} = require 'atom-space-pen-views'
    +
    Run specs and test your package

    You wrote specs, right!? Here's where they shine. Run them with cmd-shift-P, and search for run package specs. It will show all the deprecation messages and errors.

    Update the engines field

    When you are deprecation free and all done converting, upgrade the engines field in your package.json:

    {
    +	"engines": {
    +		"atom": ">=0.174.0 <2.0.0"
    +	}
    +}
    +
    Examples
    `,21),v={href:"https://github.com/atom/atom/issues/4011",target:"_blank",rel:"noopener noreferrer"},h=o('

    Deprecations

    All of the methods in Atom core that have changes will emit deprecation messages when called. These messages are shown in two places: your package specs, and in Deprecation Cop.

    Specs

    Just run your specs, and all the deprecations will be displayed in yellow.

    Deprecations in Specs

    Note

    Note: Deprecations are only displayed when executing specs through the "Window: Run Package Specs" command in the Atom UI. Deprecations are not displayed when running specs at the terminal.

    Deprecation Cop

    Run Atom in Dev Mode, atom --dev, with your package loaded, and open Deprecation Cop (search for "deprecation" in the command palette). Deprecated methods will appear in Deprecation Cop only after they have been called.

    Deprecation Cop

    When Deprecation Cop is open, and deprecated methods are called, a Refresh button will appear in the top right of the Deprecation Cop interface. So exercise your package, then come back to Deprecation Cop and click the Refresh button.

    Upgrading your Views

    Previous to 1.0, views were baked into Atom core. These views were based on jQuery and space-pen. They looked something like this:

    # The old way: getting views from atom
    +{$, TextEditorView, View} = require 'atom'
    +
    +module.exports =
    +class SomeView extends View
    +  @content: ->
    +    @div class: 'find-and-replace', =>
    +      @div class: 'block', =>
    +        @subview 'myEditor', new TextEditorView(mini: true)
    +  #...
    +
    The New

    require 'atom' no longer provides view helpers or jQuery. Atom Core is now 'view agnostic'. The preexisting view system is available from a new Node module: atom-space-pen-views.

    atom-space-pen-views now provides jQuery, space-pen views, and Atom specific views:

    # These are now provided by atom-space-pen-views
    +$
    +$$
    +$$$
    +View
    +TextEditorView
    +ScrollView
    +SelectListView
    +
    Adding the module dependencies

    To use the new views, you need to specify the atom-space-pen-views module in your package's package.json file's dependencies:

    {
    +	"dependencies": {
    +		"atom-space-pen-views": "^2.0.3"
    +	}
    +}
    +

    space-pen bundles jQuery. If you do not need space-pen or any of the views, you can require jQuery directly.

    {
    +	"dependencies": {
    +		"jquery": "^2"
    +	}
    +}
    +
    Converting your views

    Sometimes it is as simple as converting the requires at the top of each view page. I assume you read the 'TL;DR' section and have updated all of your requires.

    Upgrading classes extending any space-pen View
    afterAttach and beforeRemove updated

    The afterAttach and beforeRemove hooks have been replaced with attached and detached and the semantics have changed.

    afterAttach was called whenever the node was attached to another DOM node, even if that parent node wasn't present in the DOM. afterAttach also was called with a boolean indicating whether or not the element and its parents were on the DOM. Now the attached hook is only called when the node and all of its parents are actually on the DOM, and is not called with a boolean.

    beforeRemove was only called when $.fn.remove was called, which was typically used when the node was completely removed from the DOM. The new detached hook is called whenever the DOM node is detached, which could happen if the node is being detached for reattachment later. In short, if beforeRemove is called the node is never coming back. With detached it might be attached again later.

    # Old way
    +{View} = require 'atom'
    +class MyView extends View
    +  afterAttach: (onDom) ->
    +    #...
    +
    +  beforeRemove: ->
    +    #...
    +
    # New way
    +{View} = require 'atom-space-pen-views'
    +class MyView extends View
    +  attached: ->
    +    # Always called with the equivalent of @afterAttach(true)!
    +    #...
    +
    +  detached: ->
    +    #...
    +
    subscribe and subscribeToCommand methods removed

    The subscribe and subscribeToCommand methods have been removed. See the Eventing and Disposables section for more info.

    Upgrading to the new TextEditorView
    `,34),b=n("code",null,"TextEditorView",-1),g=n("code",null,"TextEditor",-1),f=n("code",null,"TextEditorView::getModel",-1),w={href:"https://github.com/atom/atom-space-pen-views#texteditorview",target:"_blank",rel:"noopener noreferrer"},y=n("code",null,"TextEditorView",-1),x={href:"https://atom.io/docs/api/latest/TextEditor",target:"_blank",rel:"noopener noreferrer"},_=n("code",null,"TextEditor",-1),V=o(`
    Upgrading classes extending ScrollView

    The ScrollView has very minor changes.

    You can no longer use @off to remove default behavior for core:move-up, core:move-down, etc.

    # Old way to turn off default behavior
    +class ResultsView extends ScrollView
    +  initialize: (@model) ->
    +    super()
    +    # turn off default scrolling behavior from ScrollView
    +    @off 'core:move-up'
    +    @off 'core:move-down'
    +    @off 'core:move-left'
    +    @off 'core:move-right'
    +
    # New way to turn off default behavior
    +class ResultsView extends ScrollView
    +  initialize: (@model) ->
    +    disposable = super()
    +    # turn off default scrolling behavior from ScrollView
    +    disposable.dispose()
    +
    `,5),E={href:"https://github.com/atom/find-and-replace/pull/311/files#diff-9",target:"_blank",rel:"noopener noreferrer"},T={href:"https://github.com/atom/atom-space-pen-views#scrollview",target:"_blank",rel:"noopener noreferrer"},D=o(`
    Upgrading classes extending SelectListView

    Your SelectListView might look something like this:

    # Old!
    +class CommandPaletteView extends SelectListView
    +  initialize: ->
    +    super()
    +    @addClass('command-palette overlay from-top')
    +    atom.workspaceView.command 'command-palette:toggle', => @toggle()
    +
    +  confirmed: ({name, jQuery}) ->
    +    @cancel()
    +    # do something with the result
    +
    +  toggle: ->
    +    if @hasParent()
    +      @cancel()
    +    else
    +      @attach()
    +
    +  attach: ->
    +    @storeFocusedElement()
    +
    +    items = [] # TODO: build items
    +    @setItems(items)
    +
    +    atom.workspaceView.append(this)
    +    @focusFilterEditor()
    +
    +  confirmed: ({name, jQuery}) ->
    +    @cancel()
    +

    This attaches and detaches itself from the DOM when toggled, canceling magically detaches it from the DOM, and it uses the classes overlay and from-top.

    The new SelectListView no longer automatically detaches itself from the DOM when cancelled. It's up to you to implement whatever cancel behavior you want. Using the new APIs to mimic the semantics of the old class, it should look like this:

    # New!
    +class CommandPaletteView extends SelectListView
    +  initialize: ->
    +    super()
    +    # no more need for the \`overlay\` and \`from-top\` classes
    +    @addClass('command-palette')
    +    atom.commands.add 'atom-workspace', 'command-palette:toggle', => @toggle()
    +
    +  # You need to implement the \`cancelled\` method and hide.
    +  cancelled: ->
    +    @hide()
    +
    +  confirmed: ({name, jQuery}) ->
    +    @cancel()
    +    # do something with the result
    +
    +  toggle: ->
    +    # Toggling now checks panel visibility,
    +    # and hides / shows rather than attaching to / detaching from the DOM.
    +    if @panel?.isVisible()
    +      @cancel()
    +    else
    +      @show()
    +
    +  show: ->
    +    # Now you will add your select list as a modal panel to the workspace
    +    @panel ?= atom.workspace.addModalPanel(item: this)
    +    @panel.show()
    +
    +    @storeFocusedElement()
    +
    +    items = [] # TODO: build items
    +    @setItems(items)
    +
    +    @focusFilterEditor()
    +
    +  hide: ->
    +    @panel?.hide()
    +
    `,6),j={href:"https://github.com/atom/command-palette/pull/19/files",target:"_blank",rel:"noopener noreferrer"},q=n("code",null,"CommandPaletteView",-1),C={href:"https://github.com/atom/atom-space-pen-views#selectlistview",target:"_blank",rel:"noopener noreferrer"},A=n("code",null,"SelectListView",-1),O=o(`

    Using the model layer rather than the view layer

    The API no longer exposes any specialized view objects or view classes. atom.workspaceView, and all the view classes: WorkspaceView, EditorView, PaneView, etc. have been globally deprecated.

    Nearly all of the atom-specific actions performed by the old view objects can now be managed via the model layer. For example, here's adding a panel to the interface using the atom.workspace model instead of the workspaceView:

    # Old!
    +div = document.createElement('div')
    +atom.workspaceView.appendToTop(div)
    +
    # New!
    +div = document.createElement('div')
    +atom.workspace.addTopPanel(item: div)
    +

    For actions that still require the view, such as dispatching commands or munging css classes, you'll access the view via the atom.views.getView() method. This will return a subclass of HTMLElement rather than a jQuery object or an instance of a deprecated view class (e.g. WorkspaceView).

    # Old!
    +workspaceView = atom.workspaceView
    +editorView = workspaceView.getActiveEditorView()
    +paneView = editorView.getPaneView()
    +
    # New!
    +# Generally, just use the models
    +workspace = atom.workspace
    +editor = workspace.getActiveTextEditor()
    +pane = editor.getPane()
    +
    +# If you need views, get them with \`getView\`
    +workspaceElement = atom.views.getView(atom.workspace)
    +editorElement = atom.views.getView(editor)
    +paneElement = atom.views.getView(pane)
    +

    Updating Specs

    atom.workspaceView, the WorkspaceView class and the EditorView class have been deprecated. These two objects are used heavily throughout specs, mostly to dispatch events and commands. This section will explain how to remove them while still retaining the ability to dispatch events and commands.

    Removing WorkspaceView references

    WorkspaceView has been deprecated. Everything you could do on the view, you can now do on the Workspace model.

    Requiring WorkspaceView from atom and accessing any methods on it will throw a deprecation warning. Many specs lean heavily on WorkspaceView to trigger commands and fetch EditorView objects.

    Your specs might contain something like this:

    # Old!
    +{WorkspaceView} = require 'atom'
    +describe 'FindView', ->
    +  beforeEach ->
    +    atom.workspaceView = new WorkspaceView()
    +

    Instead, we will use the atom.views.getView() method. This will return a plain HTMLElement, not a WorkspaceView or jQuery object.

    # New!
    +describe 'FindView', ->
    +  workspaceElement = null
    +  beforeEach ->
    +    workspaceElement = atom.views.getView(atom.workspace)
    +
    Attaching the workspace to the DOM

    The workspace needs to be attached to the DOM in some cases. For example, view hooks only work (attached() on View, attachedCallback() on custom elements) when there is a descendant attached to the DOM.

    You might see this in your specs:

    # Old!
    +atom.workspaceView.attachToDom()
    +

    Change it to:

    # New!
    +jasmine.attachToDOM(workspaceElement)
    +
    Removing EditorView references

    Like WorkspaceView, EditorView has been deprecated. Everything you needed to do on the view you are now able to do on the TextEditor model.

    In many cases, you will not even need to get the editor's view anymore. Any of those instances should be updated to use the TextEditor instance instead. You should really only need the editor's view when you plan on triggering a command on the view in a spec.

    Your specs might contain something like this:

    # Old!
    +describe 'Something', ->
    +  [editorView] = []
    +  beforeEach ->
    +    editorView = atom.workspaceView.getActiveView()
    +

    We're going to use atom.views.getView() again to get the editor element. As in the case of the workspaceElement, getView will return a subclass of HTMLElement rather than an EditorView or jQuery object.

    # New!
    +describe 'Something', ->
    +  [editor, editorElement] = []
    +  beforeEach ->
    +    editor = atom.workspace.getActiveTextEditor()
    +    editorElement = atom.views.getView(editor)
    +
    Dispatching commands

    Since the editorElement objects are no longer jQuery objects, they no longer support trigger(). Additionally, Atom has a new command dispatcher, atom.commands, that we use rather than commandeering jQuery's trigger method.

    From this:

    # Old!
    +workspaceView.trigger 'a-package:toggle'
    +editorView.trigger 'find-and-replace:show'
    +

    To this:

    # New!
    +atom.commands.dispatch workspaceElement, 'a-package:toggle'
    +atom.commands.dispatch editorElement, 'find-and-replace:show'
    +

    Eventing and Disposables

    A couple large things changed with respect to events:

    `,38),S={href:"https://atom.io/docs/api/latest/Disposable",target:"_blank",rel:"noopener noreferrer"},N=n("code",null,"Disposable",-1),M=n("li",null,[s("The "),n("code",null,"subscribe()"),s(" method is no longer available on "),n("code",null,"space-pen"),s(),n("code",null,"View"),s(" objects")],-1),R=n("li",null,[s("An Emitter is now provided from "),n("code",null,"require 'atom'")],-1),P=n("h5",{id:"consuming-events",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#consuming-events","aria-hidden":"true"},"#"),s(" Consuming Events")],-1),W={href:"https://atom.io/docs/api/latest/Disposable",target:"_blank",rel:"noopener noreferrer"},I=n("code",null,"Disposable",-1),$=n("code",null,"dispose()",-1),L=o(`
    # Old!
    +editor.on 'changed', ->
    +
    # New!
    +disposable = editor.onDidChange ->
    +
    +# You can unsubscribe at some point in the future via \`dispose()\`
    +disposable.dispose()
    +

    Deprecation warnings will guide you toward the correct methods.

    Using a CompositeDisposable

    You can group multiple disposables into a single disposable with a CompositeDisposable.

    {CompositeDisposable} = require 'atom'
    +
    +class Something
    +  constructor: ->
    +    editor = atom.workspace.getActiveTextEditor()
    +    @disposables = new CompositeDisposable
    +    @disposables.add editor.onDidChange ->
    +    @disposables.add editor.onDidChangePath ->
    +
    +  destroy: ->
    +    @disposables.dispose()
    +
    Removing View::subscribe and Subscriber::subscribe calls

    There were a couple permutations of subscribe(). In these examples, a CompositeDisposable is used as it will commonly be useful where conversion is necessary.

    subscribe(unsubscribable)

    This one is very straight forward.

    # Old!
    +@subscribe editor.on 'changed', ->
    +
    # New!
    +disposables = new CompositeDisposable
    +disposables.add editor.onDidChange ->
    +
    subscribe(modelObject, event, method)

    When the modelObject is an Atom model object, the change is very simple. Just use the correct event method, and add it to your CompositeDisposable.

    # Old!
    +@subscribe editor, 'changed', ->
    +
    # New!
    +disposables = new CompositeDisposable
    +disposables.add editor.onDidChange ->
    +
    subscribe(jQueryObject, selector(optional), event, method)

    Things are a little more complicated when subscribing to a DOM or jQuery element. Atom no longer provides helpers for subscribing to elements. You can use jQuery or the native DOM APIs, whichever you prefer.

    # Old!
    +@subscribe $(window), 'focus', ->
    +
    # New!
    +{Disposable, CompositeDisposable} = require 'atom'
    +disposables = new CompositeDisposable
    +
    +# New with jQuery
    +focusCallback = ->
    +$(window).on 'focus', focusCallback
    +disposables.add new Disposable ->
    +  $(window).off 'focus', focusCallback
    +
    +# New with native APIs
    +focusCallback = ->
    +window.addEventListener 'focus', focusCallback
    +disposables.add new Disposable ->
    +  window.removeEventListener 'focus', focusCallback
    +
    Providing Events: Using the Emitter
    `,21),U=n("code",null,"emissary",-1),Q=n("code",null,"Emitter",-1),Y=n("code",null,"require 'atom'",-1),F=n("code",null,"Emitter",-1),z={href:"https://atom.io/docs/api/latest/Emitter",target:"_blank",rel:"noopener noreferrer"},H=n("code",null,"Emitter",-1),B=o(`
    # New!
    +{Emitter} = require 'atom'
    +
    +class Something
    +  constructor: ->
    +    @emitter = new Emitter
    +
    +  destroy: ->
    +    @emitter.dispose()
    +
    +  onDidChange: (callback) ->
    +    @emitter.on 'did-change', callback
    +
    +  methodThatFiresAChange: ->
    +    @emitter.emit 'did-change', {data: 2}
    +
    +# Using the evented class
    +something = new Something
    +something.onDidChange (eventObject) ->
    +  console.log eventObject.data # => 2
    +something.methodThatFiresAChange()
    +

    Subscribing To Commands

    `,2),G=n("code",null,"$.fn.command",-1),J=n("code",null,"View::subscribeToCommand",-1),K=n("code",null,"atom.commands.add",-1),X=n("code",null,"CompositeDisposable",-1),Z={href:"https://atom.io/docs/api/latest/CommandRegistry#instance-add",target:"_blank",rel:"noopener noreferrer"},nn=o(`
    # Old!
    +atom.workspaceView.command 'core:close core:cancel', ->
    +
    +# When inside a View class, you might see this
    +@subscribeToCommand 'core:close core:cancel', ->
    +
    # New!
    +@disposables.add atom.commands.add 'atom-workspace',
    +  'core:close': ->
    +  'core:cancel': ->
    +
    +# You can register commands directly on individual DOM elements in addition to
    +# using selectors. When in a View class, you should have a \`@element\` object
    +# available. \`@element\` is a plain HTMLElement object
    +@disposables.add atom.commands.add @element,
    +  'core:close': ->
    +  'core:cancel': ->
    +

    Upgrading your stylesheet's selectors

    `,3),sn={href:"https://blog.atom.io/2014/11/18/avoiding-style-pollution-with-the-shadow-dom.html",target:"_blank",rel:"noopener noreferrer"};function en(an,on){const a=t("ExternalLinkIcon"),p=t("RouterLink");return r(),d("div",null,[k,n("p",null,[s("We have upgraded all the core packages. Please see "),n("a",v,[s("this issue"),e(a)]),s(" for a link to all the upgrade PRs.")]),h,n("p",null,[s("All of the atom-specific methods available on the "),b,s(" have been moved to the "),g,s(", available via "),f,s(". See the "),n("a",w,[y,s(" docs"),e(a)]),s(" and "),n("a",x,[_,s(" docs"),e(a)]),s(" for more info.")]),V,n("ul",null,[n("li",null,[s("Check out "),n("a",E,[s("an example"),e(a)]),s(" from find-and-replace.")]),n("li",null,[s("See the "),n("a",T,[s("docs"),e(a)]),s(" for all the options.")])]),D,n("ul",null,[n("li",null,[s("And check out the "),n("a",j,[s("conversion of "),q,e(a)]),s(" as a real-world example.")]),n("li",null,[s("See the "),n("a",C,[A,s(" docs"),e(a)]),s(" for all options.")])]),O,n("ol",null,[n("li",null,[s("All model events are now exposed as event subscription methods that return "),n("a",S,[N,e(a)]),s(" objects")]),M,R]),P,n("p",null,[s("All events from the Atom API are now methods that return a "),n("a",W,[I,e(a)]),s(" object, on which you can call "),$,s(" to unsubscribe.")]),L,n("p",null,[s("You no longer need to require "),U,s(" to get an emitter. We now provide an "),Q,s(" class from "),Y,s(". We have a specific pattern for use of the "),F,s(". Rather than mixing it in, we instantiate a member variable, and create explicit subscription methods. For more information see the "),n("a",z,[H,s(" docs"),e(a)]),s(".")]),B,n("p",null,[G,s(" and "),J,s(" are no longer available. Now we use "),K,s(", and collect the results in a "),X,s(". See "),n("a",Z,[s("the docs"),e(a)]),s(" for more info.")]),nn,n("p",null,[s("Many selectors have changed, and we have introduced the "),n("a",sn,[s("Shadow DOM"),e(a)]),s(" to the editor. See the "),e(p,{to:"/upgrading-to-1-0-apis/sections/upgrading-your-ui-theme-or-package-selectors/"},{default:u(()=>[s("Upgrading Your UI Theme And Package Selectors guide")]),_:1}),s(" for more information in upgrading your package stylesheets.")])])}const ln=l(m,[["render",en],["__file","upgrading-your-package.html.vue"]]);export{ln as default}; diff --git a/assets/upgrading-your-package.html.901ab9e5.js b/assets/upgrading-your-package.html.901ab9e5.js new file mode 100644 index 0000000000..f4b35c852f --- /dev/null +++ b/assets/upgrading-your-package.html.901ab9e5.js @@ -0,0 +1 @@ +const a=JSON.parse('{"key":"v-8f696676","path":"/docs/atom-archive/upgrading-to-1-0-apis/sections/upgrading-your-package.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"Upgrading Your Package","slug":"upgrading-your-package","link":"#upgrading-your-package","children":[]}],"git":{"updatedTime":1667692723000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1},{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":8.5,"words":2551},"filePathRelative":"docs/atom-archive/upgrading-to-1-0-apis/sections/upgrading-your-package.md"}');export{a as data}; diff --git a/assets/upgrading-your-syntax-theme.html.01458e4b.js b/assets/upgrading-your-syntax-theme.html.01458e4b.js new file mode 100644 index 0000000000..4e0ebff72c --- /dev/null +++ b/assets/upgrading-your-syntax-theme.html.01458e4b.js @@ -0,0 +1,12 @@ +import{_ as o,o as a,c as l,a as e,b as t,d as s,f as i,r as c}from"./app.0e1565ce.js";const r={},d=e("div",{class:"custom-container note"},[e("p",{class:"custom-container-title"},"Note"),e("p",null,[e("strong",null,"Note:"),t(" The Shadow DOM was removed in Atom "),e("code",null,"1.13"),t(". The "),e("code",null,":host"),t(" selector described below won't work and should not be used anymore.")])],-1),h=e("h3",{id:"upgrading-your-syntax-theme",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#upgrading-your-syntax-theme","aria-hidden":"true"},"#"),t(" Upgrading Your Syntax Theme")],-1),p={href:"https://www.html5rocks.com/en/tutorials/webcomponents/shadowdom",target:"_blank",rel:"noopener noreferrer"},u=e("p",null,[t("Syntax themes are specifically intended to style only text editor content, so they are automatically loaded directly into the text editor's shadow DOM when it is enabled. This happens automatically when the theme's "),e("code",null,"package.json"),t(" contains a "),e("code",null,'theme: "syntax"'),t(" declaration, so you don't need to change anything to target the appropriate context.")],-1),m=e("em",null,"outside",-1),_=e("code",null,".editor",-1),k=e("code",null,".editor-colors",-1),b=e("code",null,":host",-1),g=e("code",null,"atom-text-editor",-1),v={href:"https://www.html5rocks.com/en/tutorials/webcomponents/shadowdom-201#toc-style-host",target:"_blank",rel:"noopener noreferrer"},y=e("code",null,":host",-1),w=i(`

    Here's an example from Atom's light syntax theme. Note that the atom-text-editor selector intended to target the editor from the outside has been retained to allow the theme to keep working during the transition phase when it is possible to disable the shadow DOM.

    atom-text-editor,
    +:host {
    +	/* :host added */
    +	background-color: @syntax-background-color;
    +	color: @syntax-text-color;
    +
    +	.invisible-character {
    +		color: @syntax-invisible-character-color;
    +	}
    +	/* more nested selectors... */
    +}
    +
    `,2);function x(f,M){const n=c("ExternalLinkIcon");return a(),l("div",null,[d,h,e("p",null,[t("Text editor content is now rendered in the shadow DOM, which shields it from being styled by global style sheets to protect against accidental style pollution. For more background on the shadow DOM, check out the "),e("a",p,[t("Shadow DOM 101"),s(n)]),t(" on HTML 5 Rocks.")]),u,e("p",null,[t("When theme style sheets are loaded into the text editor's shadow DOM, selectors intended to target the editor from the "),m,t(" no longer make sense. Styles targeting the "),_,t(" and "),k,t(" classes instead need to target the "),b,t(" pseudo-element, which matches against the containing "),g,t(" node. Check out the "),e("a",v,[t("Shadow DOM 201"),s(n)]),t(" article for more information about the "),y,t(" pseudo-element.")]),w])}const O=o(r,[["render",x],["__file","upgrading-your-syntax-theme.html.vue"]]);export{O as default}; diff --git a/assets/upgrading-your-syntax-theme.html.7650d04c.js b/assets/upgrading-your-syntax-theme.html.7650d04c.js new file mode 100644 index 0000000000..c86bd0ff07 --- /dev/null +++ b/assets/upgrading-your-syntax-theme.html.7650d04c.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-416277a8","path":"/docs/atom-archive/upgrading-to-1-0-apis/sections/upgrading-your-syntax-theme.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"Upgrading Your Syntax Theme","slug":"upgrading-your-syntax-theme","link":"#upgrading-your-syntax-theme","children":[]}],"git":{"updatedTime":1667692723000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1},{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":0.94,"words":282},"filePathRelative":"docs/atom-archive/upgrading-to-1-0-apis/sections/upgrading-your-syntax-theme.md"}');export{e as data}; diff --git a/assets/upgrading-your-ui-theme-or-package-selectors.html.1c26203d.js b/assets/upgrading-your-ui-theme-or-package-selectors.html.1c26203d.js new file mode 100644 index 0000000000..3ea1c14942 --- /dev/null +++ b/assets/upgrading-your-ui-theme-or-package-selectors.html.1c26203d.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-3bf32d2b","path":"/docs/atom-archive/upgrading-to-1-0-apis/sections/upgrading-your-ui-theme-or-package-selectors.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"Upgrading Your UI Theme Or Package Selectors","slug":"upgrading-your-ui-theme-or-package-selectors","link":"#upgrading-your-ui-theme-or-package-selectors","children":[]}],"git":{"updatedTime":1667692723000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1},{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":3.17,"words":952},"filePathRelative":"docs/atom-archive/upgrading-to-1-0-apis/sections/upgrading-your-ui-theme-or-package-selectors.md"}');export{e as data}; diff --git a/assets/upgrading-your-ui-theme-or-package-selectors.html.32b0695c.js b/assets/upgrading-your-ui-theme-or-package-selectors.html.32b0695c.js new file mode 100644 index 0000000000..60bfe745c4 --- /dev/null +++ b/assets/upgrading-your-ui-theme-or-package-selectors.html.32b0695c.js @@ -0,0 +1,50 @@ +import{_ as o}from"./dep-cop.6353fb49.js";import{_ as l,o as i,c as d,a as e,b as t,d as n,f as a,r as c}from"./app.0e1565ce.js";const r={},p=a('

    Note

    Note: The Shadow DOM was removed in Atom 1.13. The ::shadow and /deep/ selectors and the context-targeted style sheets described below won't work and should not be used anymore.

    Upgrading Your UI Theme Or Package Selectors

    In addition to changes in Atom's scripting API, we'll also be making some breaking changes to Atom's DOM structure, requiring style sheets and keymaps in both packages and themes to be updated.

    Deprecation Cop

    Deprecation Cop will list usages of deprecated selector patterns to guide you. You can access it via the Command Palette (cmd-shift-p, then search for Deprecation). It breaks the deprecations down by package:

    Deprecation Cop

    Custom Tags

    Rather than adding classes to standard HTML elements to indicate their role, Atom now uses custom element names. For example, <div class="workspace"> has now been replaced with <atom-workspace>. Selectors should be updated accordingly. Note that tag names have lower specificity than classes in CSS, so you'll need to take care in converting things.

    Old SelectorNew Selector
    .editoratom-text-editor
    .editor.miniatom-text-editor[mini]
    .workspaceatom-workspace
    .horizontalatom-workspace-axis.horizontal
    .verticalatom-workspace-axis.vertical
    .pane-containeratom-pane-container
    .paneatom-pane
    .tool-panelatom-panel
    .panel-topatom-panel.top
    .panel-bottomatom-panel.bottom
    .panel-leftatom-panel.left
    .panel-rightatom-panel.right
    .overlayatom-panel.modal

    Supporting the Shadow DOM

    ',10),u={href:"https://www.html5rocks.com/en/tutorials/webcomponents/shadowdom",target:"_blank",rel:"noopener noreferrer"},h=e("ul",null,[e("li",null,"Highlight decorations"),e("li",null,"Gutter decorations"),e("li",null,"Line decorations"),e("li",null,"Scrollbar styling"),e("li",null,[t("Anything targeting a child selector of "),e("code",null,".editor")])],-1),m=e("p",null,"During a transition phase, it will be possible to enable or disable the text editor's shadow DOM in the settings, so themes will need to be compatible with both approaches.",-1),b=e("h5",{id:"shadow-dom-selectors",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#shadow-dom-selectors","aria-hidden":"true"},"#"),t(" Shadow DOM Selectors")],-1),g=e("code",null,"::shadow",-1),k=e("code",null,"/deep/",-1),v={href:"https://www.html5rocks.com/en/tutorials/webcomponents/shadowdom-201#toc-style-cat-hat",target:"_blank",rel:"noopener noreferrer"},y=a(`
    ::shadow

    The ::shadow pseudo-element allows you to bypass a single shadow root. For example, say you want to update a highlight decoration for a linter package. Initially, the style looks as follows:

    // Without shadow DOM support
    +atom-text-editor .highlight.my-linter {
    +	background: hotpink;
    +}
    +

    In order for this style to apply with the shadow DOM enabled, you will need to add a second selector with the ::shadow pseudo-element. You should leave the original selector in place so your theme continues to work with the shadow DOM disabled during the transition period.

    // With shadow DOM support
    +atom-text-editor .highlight.my-linter,
    +atom-text-editor::shadow .highlight.my-linter {
    +	background: hotpink;
    +}
    +
    `,5),w={href:"https://github.com/atom/find-and-replace/blob/95351f261bc384960a69b66bf12eae8002da63f9/stylesheets/find-and-replace.less#L9-L29",target:"_blank",rel:"noopener noreferrer"},x=e("code",null,"::shadow",-1),f=a(`
    /deep/

    The /deep/ combinator overrides all shadow boundaries, making it useful for rules you want to apply globally such as scrollbar styling. Here's a snippet containing scrollbar styling for the Atom Dark UI theme before shadow DOM support:

    // Without shadow DOM support
    +.scrollbars-visible-always {
    +	::-webkit-scrollbar {
    +		width: 8px;
    +		height: 8px;
    +	}
    +
    +	::-webkit-scrollbar-track,
    +	::-webkit-scrollbar-corner {
    +		background: @scrollbar-background-color;
    +	}
    +
    +	::-webkit-scrollbar-thumb {
    +		background: @scrollbar-color;
    +		border-radius: 5px;
    +		box-shadow: 0 0 1px black inset;
    +	}
    +}
    +

    To style scrollbars even inside of the shadow DOM, each rule needs to be prefixed with /deep/. We use /deep/ instead of ::shadow because we don't care about the selector of the host element in this case. We just want our styling to apply everywhere.

    // With shadow DOM support using /deep/
    +.scrollbars-visible-always {
    +	/deep/ ::-webkit-scrollbar {
    +		width: 8px;
    +		height: 8px;
    +	}
    +
    +	/deep/ ::-webkit-scrollbar-track,
    +	/deep/ ::-webkit-scrollbar-corner {
    +		background: @scrollbar-background-color;
    +	}
    +
    +	/deep/ ::-webkit-scrollbar-thumb {
    +		background: @scrollbar-color;
    +		border-radius: 5px;
    +		box-shadow: 0 0 1px black inset;
    +	}
    +}
    +
    Context-Targeted Style Sheets

    The selector features discussed above allow you to target shadow DOM content with specific selectors, but Atom also allows you to target a specific shadow DOM context with an entire style sheet. The context into which a style sheet is loaded is based on the file name. If you want to load a style sheet into the editor, name it with the .atom-text-editor.less or .atom-text-editor.css extensions.

    my-ui-theme/
    +  styles/
    +    index.less                   # loaded globally
    +    index.atom-text-editor.less  # loaded in the text editor shadow DOM
    +
    `,8),_={href:"https://github.com/atom/decoration-example/blob/master/styles/decoration-example.atom-text-editor.less",target:"_blank",rel:"noopener noreferrer"},D=a("

    Inside a context-targeted style sheet, there's no need to use the ::shadow or /deep/ expressions. If you want to refer to the element containing the shadow root, you can use the ::host pseudo-element.

    During the transition phase, style sheets targeting the atom-text-editor context will also be loaded globally. Make sure you update your selectors in a way that maintains compatibility with the shadow DOM being disabled. That means if you use a ::host pseudo element, you should also include the same style rule matches against atom-text-editor.

    ",2);function M(O,S){const s=c("ExternalLinkIcon");return i(),d("div",null,[p,e("p",null,[t("Text editor content is now rendered in the shadow DOM, which shields it from being styled by global style sheets to protect against accidental style pollution. For more background on the shadow DOM, check out the "),e("a",u,[t("Shadow DOM 101"),n(s)]),t(" on HTML 5 Rocks. If you need to style text editor content in a UI theme, you'll need to circumvent this protection for any rules that target the text editor's content. Some examples of the kinds of UI theme styles needing to be updated:")]),h,m,b,e("p",null,[t("Chromium provides two tools for bypassing shadow boundaries, the "),g,t(" pseudo-element and the "),k,t(" combinator. For an in-depth explanation of styling the shadow DOM, see the "),e("a",v,[t("Shadow DOM 201"),n(s)]),t(" article on HTML 5 Rocks.")]),y,e("p",null,[t("Check out the "),e("a",w,[t("find-and-replace"),n(s)]),t(" package for another example of using "),x,t(" to pierce the shadow DOM.")]),f,e("p",null,[t("Check out this "),e("a",_,[t("style sheet"),n(s)]),t(" from the decoration-example package for an example of context-targeting.")]),D])}const C=l(r,[["render",M],["__file","upgrading-your-ui-theme-or-package-selectors.html.vue"]]);export{C as default}; diff --git a/assets/using-ppm.html.4f5504d2.js b/assets/using-ppm.html.4f5504d2.js new file mode 100644 index 0000000000..1ef291e119 --- /dev/null +++ b/assets/using-ppm.html.4f5504d2.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-fbe0eb5e","path":"/docs/launch-manual/sections/core-hacking/sections/using-ppm.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Using ppm (Pulsar Package Manager)","slug":"using-ppm-pulsar-package-manager","link":"#using-ppm-pulsar-package-manager","children":[]}],"git":{"updatedTime":1670812611000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":3},{"name":"confused_techie","email":"dev@lhbasics.com","commits":1}]},"readingTime":{"minutes":0.66,"words":198},"filePathRelative":"docs/launch-manual/sections/core-hacking/sections/using-ppm.md"}');export{e as data}; diff --git a/assets/using-ppm.html.ba74ad98.js b/assets/using-ppm.html.ba74ad98.js new file mode 100644 index 0000000000..74231021ae --- /dev/null +++ b/assets/using-ppm.html.ba74ad98.js @@ -0,0 +1,13 @@ +import{_ as o,o as c,c as r,d as p,w as s,a as e,b as a,f as u,r as d}from"./app.0e1565ce.js";const m={},b=e("h2",{id:"using-ppm-pulsar-package-manager",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#using-ppm-pulsar-package-manager","aria-hidden":"true"},"#"),a(" Using ppm (Pulsar Package Manager)")],-1),h=e("p",null,[e("code",null,"ppm"),a(" is used for installing and managing Pulsar's packages in much the same way that "),e("code",null,"apm"),a(" did on Atom. However at this point in the project there are a few hoops you have to jump through to get it to work correctly.")],-1),v=e("p",null,[a("After following the build instructions you will find the "),e("code",null,"ppm"),a(" binary at "),e("code",null,"pulsar/ppm/bin/apm"),a(" but by default Pulsar will be looking in the wrong place. There will also be issues relating to the Electron version which will prevent install from the package backend. To solve this a couple of environmental variables need to be exported.")],-1),g=e("div",{class:"language-bash ext-sh line-numbers-mode"},[e("pre",{class:"language-bash"},[e("code",null,[e("span",{class:"token builtin class-name"},"export"),a(),e("span",{class:"token assign-left variable"},"ATOM_HOME"),e("span",{class:"token operator"},"="),a("/home/"),e("span",{class:"token operator"},"<"),a("user"),e("span",{class:"token operator"},">"),a(`/.pulsar +`),e("span",{class:"token builtin class-name"},"export"),a(),e("span",{class:"token assign-left variable"},"APM_PATH"),e("span",{class:"token operator"},"="),a(`/ppm/bin/apm +`),e("span",{class:"token builtin class-name"},"export"),a(),e("span",{class:"token assign-left variable"},"ATOM_ELECTRON_VERSION"),e("span",{class:"token operator"},"="),e("span",{class:"token number"},"12.2"),a(`.3 +`)])]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),k=e("div",{class:"language-bash ext-sh line-numbers-mode"},[e("pre",{class:"language-bash"},[e("code",null,`TODO +`)]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"})])],-1),_=e("div",{class:"language-bash ext-sh line-numbers-mode"},[e("pre",{class:"language-bash"},[e("code",null,[e("span",{class:"token builtin class-name"},"set"),a(),e("span",{class:"token assign-left variable"},"ATOM_HOME"),e("span",{class:"token operator"},"="),a("C:"),e("span",{class:"token punctuation"},"\\"),a("Users"),e("span",{class:"token punctuation"},"\\"),e("span",{class:"token operator"},"<"),a("user"),e("span",{class:"token operator"},">"),e("span",{class:"token punctuation"},"\\"),a(`.pulsar +`),e("span",{class:"token builtin class-name"},"set"),a(),e("span",{class:"token assign-left variable"},"APM_PATH"),e("span",{class:"token operator"},"="),e("span",{class:"token punctuation"},"\\"),a("ppm"),e("span",{class:"token punctuation"},"\\"),a("bin"),e("span",{class:"token punctuation"},"\\"),a(`apm +`)])]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),f=u(`

    You can now use the binary to link or install packages.

    For example to install the ide-java package from source:

    # clone the repository and cd into it
    +git clone https://github.com/pulsar-edit/ide-java
    +cd ide-java
    +
    +# from the directory where you are running pulsar source code
    +<pulsar source>/ppm/bin/apm link
    +
    `,3);function w(x,A){const i=d("Tabs");return c(),r("div",null,[b,h,v,p(i,{id:"9",data:[{title:"Linux"},{title:"macOS"},{title:"Windows"}],"tab-id":"core-hacking"},{tab0:s(({title:n,value:t,isActive:l})=>[g]),tab1:s(({title:n,value:t,isActive:l})=>[k]),tab2:s(({title:n,value:t,isActive:l})=>[_]),_:1}),f])}const y=o(m,[["render",w],["__file","using-ppm.html.vue"]]);export{y as default}; diff --git a/assets/version-control-in-atom.html.6159267b.js b/assets/version-control-in-atom.html.6159267b.js new file mode 100644 index 0000000000..97df2621e9 --- /dev/null +++ b/assets/version-control-in-atom.html.6159267b.js @@ -0,0 +1,2 @@ +import{_ as s,a as n,b as r,c as l,d,e as c}from"./open-on-github.6fa9b166.js";import{_ as h,o as m,c as p,a as t,b as e,d as i,f as a,r as f}from"./app.0e1565ce.js";const u={},b=t("h3",{id:"version-control-in-atom",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#version-control-in-atom","aria-hidden":"true"},"#"),e(" Version Control in Atom")],-1),g={href:"https://git-scm.com",target:"_blank",rel:"noopener noreferrer"},_={href:"https://github.com",target:"_blank",rel:"noopener noreferrer"},k=a('

    In order to use version control in Atom, the project root needs to contain the Git repository.

    Checkout HEAD revision

    The Alt+Cmd+ZAlt+Ctrl+Z keybinding checks out the HEAD revision of the file in the editor.

    This is a quick way to discard any saved and staged changes you've made and restore the file to the version in the HEAD commit. This is essentially the same as running git checkout HEAD -- <path> and git reset HEAD -- <path> from the command line for that path.

    Git checkout

    This command goes onto the undo stack so you can use Cmd+ZCtrl+Z afterwards to restore the previous contents.

    Git status list

    ',7),w={href:"https://github.com/atom/fuzzy-finder",target:"_blank",rel:"noopener noreferrer"},y=t("kbd",{class:"platform-mac"},"Cmd+T",-1),v=t("kbd",{class:"platform-windows platform-linux"},"Ctrl+T",-1),G=t("kbd",{class:"platform-mac"},"Cmd+B",-1),A=t("kbd",{class:"platform-windows platform-linux"},"Ctrl+B",-1),x=t("kbd",{class:"platform-mac"},"Cmd+Shift+B",-1),H=t("kbd",{class:"platform-windows platform-linux"},"Ctrl+Shift+B",-1),C=t("code",null,"git status",-1),T=t("p",null,[t("img",{src:s,alt:"Git status list",title:"`git status` list"})],-1),B=t("p",null,"An icon will appear to the right of each file letting you know whether it is untracked or modified.",-1),E=t("h4",{id:"commit-editor",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#commit-editor","aria-hidden":"true"},"#"),e(" Commit editor")],-1),D={href:"https://github.com/atom/language-git",target:"_blank",rel:"noopener noreferrer"},O=a('

    Git commit message highlighting

    You can configure Atom to be your Git commit editor with the following command:

    $ git config --global core.editor "atom --wait"
    +
    `,3),j={href:"https://github.com/atom/language-git",target:"_blank",rel:"noopener noreferrer"},z=t("h4",{id:"status-bar-icons",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#status-bar-icons","aria-hidden":"true"},"#"),e(" Status bar icons")],-1),S={href:"https://github.com/atom/status-bar",target:"_blank",rel:"noopener noreferrer"},V=t("p",null,[t("img",{src:n,alt:"Git Status Bar decorations",title:"Git Status Bar decorations"})],-1),I=t("p",null,"The currently checked out branch name is shown with the number of commits the branch is ahead of or behind its upstream branch. An icon is added if the file is untracked, modified, or ignored. The number of lines added and removed since the file was last committed will be displayed as well.",-1),q=t("h4",{id:"line-diffs",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#line-diffs","aria-hidden":"true"},"#"),e(" Line diffs")],-1),L={href:"https://github.com/atom/git-diff",target:"_blank",rel:"noopener noreferrer"},N=a('

    Git line diff indications

    This package also adds Alt+G Down and Alt+G Up keybindings that allow you to move the cursor to the next or previous diff in the current editor.

    Open on GitHub

    If the project you're working on is on GitHub, there are also some very useful integrations you can use. Most of the commands will take the current file you're viewing and open a view of that file on GitHub - for instance, the blame or commit history of that file.

    • Alt+G O - Open file on GitHub
    • Alt+G B - Open Blame view of file on GitHub
    • Alt+G H - Open History view of file on GitHub
    • Alt+G C - Copy the URL of the current file on GitHub to the clipboard
    • Alt+G R - Branch compare on GitHub

    The branch comparison shows you the commits that are on the branch you're currently working on locally that are not on the mainline branch.

    Open Blame of file on GitHub

    ',7);function Z(R,U){const o=f("ExternalLinkIcon");return m(),p("div",null,[b,t("p",null,[e("Version control is an important aspect of any project and Atom comes with basic "),t("a",g,[e("Git"),i(o)]),e(" and "),t("a",_,[e("GitHub"),i(o)]),e(" integration built in.")]),k,t("p",null,[e("Atom ships with the "),t("a",w,[e("fuzzy-finder package"),i(o)]),e(" which provides "),y,v,e(" to quickly open files in the project and "),G,A,e(" to jump to any open editor. The package also provides "),x,H,e(" which displays a list of all the untracked and modified files in the project. These will be the same files that you would see on the command line if you ran "),C,e(".")]),T,B,E,t("p",null,[e("Atom can be used as your Git commit editor and ships with the "),t("a",D,[e("language-git package"),i(o)]),e(" which adds syntax highlighting to edited commit, merge, and rebase messages.")]),O,t("p",null,[e("The "),t("a",j,[e("language-git"),i(o)]),e(" package will help remind you to be brief by colorizing the first lines of commit messages when they're longer than 50 or 65 characters.")]),z,t("p",null,[e("The "),t("a",S,[e("status-bar"),i(o)]),e(" package that ships with Atom includes several Git decorations that display on the right side of the status bar:")]),V,I,q,t("p",null,[e("The included "),t("a",L,[e("git-diff"),i(o)]),e(" package colorizes the gutter next to lines that have been added, edited, or removed.")]),N])}const $=h(u,[["render",Z],["__file","version-control-in-atom.html.vue"]]);export{$ as default}; diff --git a/assets/version-control-in-atom.html.8e9ff87a.js b/assets/version-control-in-atom.html.8e9ff87a.js new file mode 100644 index 0000000000..b9cfb8dd0a --- /dev/null +++ b/assets/version-control-in-atom.html.8e9ff87a.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-fd5de582","path":"/docs/atom-archive/using-atom/sections/version-control-in-atom.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"Version Control in Atom","slug":"version-control-in-atom","link":"#version-control-in-atom","children":[]}],"git":{"updatedTime":1664634443000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1},{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":2.66,"words":799},"filePathRelative":"docs/atom-archive/using-atom/sections/version-control-in-atom.md"}');export{e as data}; diff --git a/assets/version-control-in-pulsar.html.74f8278b.js b/assets/version-control-in-pulsar.html.74f8278b.js new file mode 100644 index 0000000000..2e1d34c37d --- /dev/null +++ b/assets/version-control-in-pulsar.html.74f8278b.js @@ -0,0 +1 @@ +const i=JSON.parse('{"key":"v-13272930","path":"/docs/launch-manual/sections/using-pulsar/sections/version-control-in-pulsar.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Version Control in Pulsar","slug":"version-control-in-pulsar","link":"#version-control-in-pulsar","children":[{"level":3,"title":"Checkout HEAD revision","slug":"checkout-head-revision","link":"#checkout-head-revision","children":[]},{"level":3,"title":"Git status list","slug":"git-status-list","link":"#git-status-list","children":[]},{"level":3,"title":"Commit editor","slug":"commit-editor","link":"#commit-editor","children":[]},{"level":3,"title":"Status bar icons","slug":"status-bar-icons","link":"#status-bar-icons","children":[]},{"level":3,"title":"Line diffs","slug":"line-diffs","link":"#line-diffs","children":[]},{"level":3,"title":"Open on GitHub","slug":"open-on-github","link":"#open-on-github","children":[]}]}],"git":{"updatedTime":1669229414000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":3}]},"readingTime":{"minutes":2.48,"words":743},"filePathRelative":"docs/launch-manual/sections/using-pulsar/sections/version-control-in-pulsar.md"}');export{i as data}; diff --git a/assets/version-control-in-pulsar.html.af5df576.js b/assets/version-control-in-pulsar.html.af5df576.js new file mode 100644 index 0000000000..fa0b2d0a1d --- /dev/null +++ b/assets/version-control-in-pulsar.html.af5df576.js @@ -0,0 +1,2 @@ +import{_ as s,c as a,a as r,b as l,d,e as h}from"./open-on-github.6fa9b166.js";import{_ as c,o as u,c as p,a as e,b as t,d as i,e as m,f as n,r as b}from"./app.0e1565ce.js";const g={},f=e("h2",{id:"version-control-in-pulsar",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#version-control-in-pulsar","aria-hidden":"true"},"#"),t(" Version Control in Pulsar")],-1),_={href:"https://git-scm.com",target:"_blank",rel:"noopener noreferrer"},k={href:"https://github.com",target:"_blank",rel:"noopener noreferrer"},y=n('

    In order to use version control in Pulsar, the project root needs to contain the Git repository.

    Checkout HEAD revision

    The LNX/WIN: Alt+Ctrl+Z - MAC: Alt+Cmd+Z keybinding checks out the HEAD revision of the file in the editor.

    This is a quick way to discard any saved and staged changes you've made and restore the file to the version in the HEAD commit. This is essentially the same as running git checkout HEAD -- <path> and git reset HEAD -- <path> from the command line for that path.

    Git checkout

    This command goes onto the undo stack so you can use LNX/WIN: Ctrl+Z - MAC: Cmd+Z afterwards to restore the previous contents.

    Git status list

    ',7),v={href:"https://github.com/pulsar-edit/fuzzy-finder",target:"_blank",rel:"noopener noreferrer"},w=e("strong",null,[e("em",null,"LNX/WIN")],-1),G=e("kbd",null,"Ctrl+T",-1),C=e("strong",null,[e("em",null,"MAC")],-1),A=e("kbd",null,"Cmd+T",-1),H=e("strong",null,[e("em",null,"LNX/WIN")],-1),x=e("kbd",null,"Ctrl+B",-1),T=e("strong",null,[e("em",null,"MAC")],-1),N=e("kbd",null,"Cmd+B",-1),B=e("strong",null,[e("em",null,"LNX/WIN")],-1),I=e("kbd",null,"Ctrl+Shift+B",-1),E=e("strong",null,[e("em",null,"MAC")],-1),L=e("kbd",null,"Cmd+Shift+B",-1),O=e("code",null,"git status",-1),P=e("p",null,[e("img",{src:s,alt:"Git status list",title:"`git status` list"})],-1),D=e("p",null,"An icon will appear to the right of each file letting you know whether it is untracked or modified.",-1),V=e("h3",{id:"commit-editor",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#commit-editor","aria-hidden":"true"},"#"),t(" Commit editor")],-1),j={href:"https://github.com/pulsar-edit/language-git",target:"_blank",rel:"noopener noreferrer"},z=e("p",null,[e("img",{src:a,alt:"Git commit message highlighting",title:"Git commit message highlighting"})],-1),M=e("p",null,"You can configure Pulsar to be your Git commit editor with the following command:",-1),S=n(`
    $ git config --global core.editor "pulsar --wait"
    +
    `,1),W={href:"https://github.com/pulsar-edit/language-git",target:"_blank",rel:"noopener noreferrer"},X=e("h3",{id:"status-bar-icons",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#status-bar-icons","aria-hidden":"true"},"#"),t(" Status bar icons")],-1),q={href:"https://github.com/pulsar-edit/status-bar",target:"_blank",rel:"noopener noreferrer"},Z=e("p",null,[e("img",{src:r,alt:"Git Status Bar decorations",title:"Git Status Bar decorations"})],-1),R=e("p",null,"The currently checked out branch name is shown with the number of commits the branch is ahead of or behind its upstream branch. An icon is added if the file is untracked, modified, or ignored. The number of lines added and removed since the file was last committed will be displayed as well.",-1),U=e("h3",{id:"line-diffs",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#line-diffs","aria-hidden":"true"},"#"),t(" Line diffs")],-1),Y={href:"https://github.com/pulsar-edit/git-diff",target:"_blank",rel:"noopener noreferrer"},$=n('

    Git line diff indications

    This package also adds Alt+G Down and Alt+GUp keybindings that allow you to move the cursor to the next or previous diff in the current editor.

    Open on GitHub

    If the project you're working on is on GitHub, there are also some very useful integrations you can use. Most of the commands will take the current file you're viewing and open a view of that file on GitHub - for instance, the blame or commit history of that file.

    • Alt+G O - Open file on GitHub
    • Alt+G B - Open Blame view of file on GitHub
    • Alt+G H - Open History view of file on GitHub
    • Alt+G C - Copy the URL of the current file on GitHub to the clipboard
    • Alt+G R - Branch compare on GitHub

    The branch comparison shows you the commits that are on the branch you're currently working on locally that are not on the mainline branch.

    Open Blame of file on GitHub

    ',7);function F(J,K){const o=b("ExternalLinkIcon");return u(),p("div",null,[f,e("p",null,[t("Version control is an important aspect of any project and Pulsar comes with basic "),e("a",_,[t("Git"),i(o)]),t(" and "),e("a",k,[t("GitHub"),i(o)]),t(" integration built in.")]),y,e("p",null,[t("Pulsar ships with the "),e("a",v,[t("fuzzy-finder package"),i(o)]),t(" which provides "),w,t(": "),G,t(" - "),C,t(": "),A,t(" to quickly open files in the project and "),H,t(": "),x,t(" - "),T,t(": "),N,t(" to jump to any open editor. The package also provides "),B,t(": "),I,t(" - "),E,t(": "),L,t(" which displays a list of all the untracked and modified files in the project. These will be the same files that you would see on the command line if you ran "),O,t(".")]),P,D,V,e("p",null,[t("Pulsar can be used as your Git commit editor and ships with the "),e("a",j,[t("language-git package"),i(o)]),t(" which adds syntax highlighting to edited commit, merge, and rebase messages.")]),z,M,m("TODO: Check this still works in Pulsar"),S,e("p",null,[t("The "),e("a",W,[t("language-git"),i(o)]),t(" package will help remind you to be brief by colorizing the first lines of commit messages when they're longer than 50 or 65 characters.")]),X,e("p",null,[t("The "),e("a",q,[t("status-bar"),i(o)]),t(" package that ships with Pulsar includes several Git decorations that display on the right side of the status bar:")]),Z,R,U,e("p",null,[t("The included "),e("a",Y,[t("git-diff"),i(o)]),t(" package colorizes the gutter next to lines that have been added, edited, or removed.")]),$])}const te=c(g,[["render",F],["__file","version-control-in-pulsar.html.vue"]]);export{te as default}; diff --git a/assets/webassembly.dbe6d3f6.png b/assets/webassembly.dbe6d3f6.png new file mode 100644 index 0000000000..05fc4067f9 Binary files /dev/null and b/assets/webassembly.dbe6d3f6.png differ diff --git a/assets/welcome-screen-checkbox.604a29ce.js b/assets/welcome-screen-checkbox.604a29ce.js new file mode 100644 index 0000000000..44faf2d787 --- /dev/null +++ b/assets/welcome-screen-checkbox.604a29ce.js @@ -0,0 +1 @@ +const e="/assets/welcome-screen-checkbox.e654cdf1.png";export{e as _}; diff --git a/assets/welcome-screen-checkbox.e654cdf1.png b/assets/welcome-screen-checkbox.e654cdf1.png new file mode 100644 index 0000000000..53b727ee0b Binary files /dev/null and b/assets/welcome-screen-checkbox.e654cdf1.png differ diff --git a/assets/what-does-atom-cost.html.79addfd4.js b/assets/what-does-atom-cost.html.79addfd4.js new file mode 100644 index 0000000000..f894583206 --- /dev/null +++ b/assets/what-does-atom-cost.html.79addfd4.js @@ -0,0 +1 @@ +import{_ as a,o as n,c as s,a as o,b as e,d as r,r as c}from"./app.0e1565ce.js";const d={},h=o("h3",{id:"what-does-atom-cost",tabindex:"-1"},[o("a",{class:"header-anchor",href:"#what-does-atom-cost","aria-hidden":"true"},"#"),e(" What does Atom cost?")],-1),l=o("sup",null,"th",-1),i={href:"https://github.blog/2014-05-06-atom-free-and-open-source-for-everyone/",target:"_blank",rel:"noopener noreferrer"};function _(f,m){const t=c("ExternalLinkIcon");return n(),s("div",null,[h,o("p",null,[e("Since the 6"),l,e(" of May, 2014, "),o("a",i,[e("Atom has been available for download free of charge for everyone"),r(t)]),e(". This includes business and enterprise use.")])])}const p=a(d,[["render",_],["__file","what-does-atom-cost.html.vue"]]);export{p as default}; diff --git a/assets/what-does-atom-cost.html.f490edb9.js b/assets/what-does-atom-cost.html.f490edb9.js new file mode 100644 index 0000000000..1f0cecce28 --- /dev/null +++ b/assets/what-does-atom-cost.html.f490edb9.js @@ -0,0 +1 @@ +const t=JSON.parse('{"key":"v-25bdd335","path":"/docs/atom-archive/faq/sections/what-does-atom-cost.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"What does Atom cost?","slug":"what-does-atom-cost","link":"#what-does-atom-cost","children":[]}],"git":{"updatedTime":1667690985000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1},{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":0.14,"words":43},"filePathRelative":"docs/atom-archive/faq/sections/what-does-atom-cost.md"}');export{t as data}; diff --git a/assets/what-does-safe-mode-do.html.c28e8226.js b/assets/what-does-safe-mode-do.html.c28e8226.js new file mode 100644 index 0000000000..f3b171210b --- /dev/null +++ b/assets/what-does-safe-mode-do.html.c28e8226.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-7e76b4c2","path":"/docs/atom-archive/faq/sections/what-does-safe-mode-do.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"What does Safe Mode do?","slug":"what-does-safe-mode-do","link":"#what-does-safe-mode-do","children":[]}],"git":{"updatedTime":1678042600000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1},{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":1},{"name":"icecream17","email":"nguyeste008@students.garlandisd.net","commits":1}]},"readingTime":{"minutes":0.47,"words":142},"filePathRelative":"docs/atom-archive/faq/sections/what-does-safe-mode-do.md"}');export{e as data}; diff --git a/assets/what-does-safe-mode-do.html.d38fba60.js b/assets/what-does-safe-mode-do.html.d38fba60.js new file mode 100644 index 0000000000..8761f26af5 --- /dev/null +++ b/assets/what-does-safe-mode-do.html.d38fba60.js @@ -0,0 +1 @@ +import{_ as t,o as n,c as d,a as o,b as e,d as i,f as s,r as c}from"./app.0e1565ce.js";const r={},l=s('

    What does Safe Mode do?

    Atom's Safe Mode, which can be activated by completely exiting all instances of Atom and launching it again using the command atom --safe from the command line, does the following:

    1. Does not load any packages from ~/.atom/packages or ~/.atom/dev/packages
    2. Does not run your init.coffee
    3. Loads only default-installed themes

    The intent of Safe Mode is to determine if a problem is being caused by a community package or is caused by built-in functionality of Atom. Disabling the init script was added because people tend to use the init script as a mini-package of sorts by adding code, commands and other functionality that would normally be in a package.

    ',4),m={href:"https://pulsar-edit.dev/docs/atom-archive/hacking-atom/#debugging",target:"_blank",rel:"noopener noreferrer"};function f(h,g){const a=c("ExternalLinkIcon");return n(),d("div",null,[l,o("p",null,[e("For more information on Safe Mode, check the "),o("a",m,[e("debugging section"),i(a)]),e(".")])])}const u=t(r,[["render",f],["__file","what-does-safe-mode-do.html.vue"]]);export{u as default}; diff --git a/assets/what-is-this-line-on-the-right-in-the-editor-view.html.92c5016f.js b/assets/what-is-this-line-on-the-right-in-the-editor-view.html.92c5016f.js new file mode 100644 index 0000000000..e2a2f08797 --- /dev/null +++ b/assets/what-is-this-line-on-the-right-in-the-editor-view.html.92c5016f.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-8a4db88a","path":"/docs/atom-archive/faq/sections/what-is-this-line-on-the-right-in-the-editor-view.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"What is this line on the right in the editor view?","slug":"what-is-this-line-on-the-right-in-the-editor-view","link":"#what-is-this-line-on-the-right-in-the-editor-view","children":[]}],"git":{"updatedTime":1667690985000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":2},{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":0.23,"words":70},"filePathRelative":"docs/atom-archive/faq/sections/what-is-this-line-on-the-right-in-the-editor-view.md"}');export{e as data}; diff --git a/assets/what-is-this-line-on-the-right-in-the-editor-view.html.e82e70ee.js b/assets/what-is-this-line-on-the-right-in-the-editor-view.html.e82e70ee.js new file mode 100644 index 0000000000..5c8dfab518 --- /dev/null +++ b/assets/what-is-this-line-on-the-right-in-the-editor-view.html.e82e70ee.js @@ -0,0 +1 @@ +import{_ as t}from"./wrap-guide-line.159c54bb.js";import{_ as i,o,c as n,a as e,b as h}from"./app.0e1565ce.js";const s={},a=e("h3",{id:"what-is-this-line-on-the-right-in-the-editor-view",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#what-is-this-line-on-the-right-in-the-editor-view","aria-hidden":"true"},"#"),h(" What is this line on the right in the editor view?")],-1),r=e("p",null,[e("img",{src:t,alt:"Wrap guide line"})],-1),d=e("p",null,"That's the wrap guide. It is a visual indicator of when your lines of code are getting too long. It defaults to the column that your Preferred Line Length is set to.",-1),c=e("p",null,"If you want to turn it off, you can disable the wrap-guide package in the Settings View.",-1),l=[a,r,d,c];function _(u,f){return o(),n("div",null,l)}const p=i(s,[["render",_],["__file","what-is-this-line-on-the-right-in-the-editor-view.html.vue"]]);export{p as default}; diff --git a/assets/what-platforms-does-atom-run-on.html.88de8aff.js b/assets/what-platforms-does-atom-run-on.html.88de8aff.js new file mode 100644 index 0000000000..c1a74cb8be --- /dev/null +++ b/assets/what-platforms-does-atom-run-on.html.88de8aff.js @@ -0,0 +1 @@ +import{_ as n,o as r,c as a,a as o,b as e,d as s,r as l}from"./app.0e1565ce.js";const i={},d=o("h3",{id:"what-platforms-does-atom-run-on",tabindex:"-1"},[o("a",{class:"header-anchor",href:"#what-platforms-does-atom-run-on","aria-hidden":"true"},"#"),e(" What platforms does Atom run on?")],-1),m=o("p",null,"Prebuilt versions of Atom are available for OS X 10.10 or later, Windows 7 or later, RedHat Linux, and Ubuntu Linux.",-1),c={href:"https://github.com/atom/atom/blob/master/README.md#building",target:"_blank",rel:"noopener noreferrer"};function u(f,_){const t=l("ExternalLinkIcon");return r(),a("div",null,[d,m,o("p",null,[e("If you would like to build from source on Windows, Linux, or OS X, see the "),o("a",c,[e("Atom README"),s(t)]),e(" for more information.")])])}const p=n(i,[["render",u],["__file","what-platforms-does-atom-run-on.html.vue"]]);export{p as default}; diff --git a/assets/what-platforms-does-atom-run-on.html.e67bfbea.js b/assets/what-platforms-does-atom-run-on.html.e67bfbea.js new file mode 100644 index 0000000000..86616f20f6 --- /dev/null +++ b/assets/what-platforms-does-atom-run-on.html.e67bfbea.js @@ -0,0 +1 @@ +const t=JSON.parse('{"key":"v-39dbf018","path":"/docs/atom-archive/faq/sections/what-platforms-does-atom-run-on.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"What platforms does Atom run on?","slug":"what-platforms-does-atom-run-on","link":"#what-platforms-does-atom-run-on","children":[]}],"git":{"updatedTime":1667690985000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1},{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":0.19,"words":56},"filePathRelative":"docs/atom-archive/faq/sections/what-platforms-does-atom-run-on.md"}');export{t as data}; diff --git a/assets/what-s-the-difference-between-an-ide-and-an-editor.html.1f47c819.js b/assets/what-s-the-difference-between-an-ide-and-an-editor.html.1f47c819.js new file mode 100644 index 0000000000..fa8a32029e --- /dev/null +++ b/assets/what-s-the-difference-between-an-ide-and-an-editor.html.1f47c819.js @@ -0,0 +1 @@ +import{_ as a,o as r,c as i,a as o,b as e,d as n,r as s}from"./app.0e1565ce.js";const l={},d=o("h3",{id:"what-s-the-difference-between-an-ide-and-an-editor",tabindex:"-1"},[o("a",{class:"header-anchor",href:"#what-s-the-difference-between-an-ide-and-an-editor","aria-hidden":"true"},"#"),e(" What's the difference between an IDE and an editor?")],-1),h=o("p",null,[e('The term "IDE" comes from '),o("strong",null,"I"),e("ntegrated "),o("strong",null,"D"),e("evelopment "),o("strong",null,"E"),e("nvironment. It is intended as a set of tools that all work together: text editor, compiler, build or make integration, debugging, etc. Virtually all IDEs are tied specifically to a language or framework or tightly collected set of languages or frameworks. Some examples: Visual Studio for .NET and other Microsoft languages, RubyMine for Ruby, IntelliJ for Java, XCode for Apple technologies.")],-1),u={href:"https://daringfireball.net/projects/markdown/syntax",target:"_blank",rel:"noopener noreferrer"},c={href:"http://orgmode.org/",target:"_blank",rel:"noopener noreferrer"},f=o("p",null,[e("The tradeoff here is that while you can generally get off the ground faster if you're working within the realm of a given IDE, over the long term you spend a bunch of time retraining yourself when you inevitably change from one language or toolchain to the next. If you use an editor, you can continue to use the same workflows that you always have. Tools that you've built into your editor can be carried over to the next language and framework. Your editor becomes more powerful and more customized to how you want to work not just over years but potentially "),o("em",null,"decades"),e(". Just ask people who use vim or Emacs ... both of which have been available for over 25 years!")],-1),g=o("p",null,"So, if you want something that you can just jump into and be productive right away in a specific technology, perhaps an IDE is what you're looking for. If you want a tool that you can shape and customize into exactly what you want out of it even if it costs you some time up front configuring things, then an editor is probably more your speed \u26A1",-1);function m(y,p){const t=s("ExternalLinkIcon");return r(),i("div",null,[d,h,o("p",null,[e("An editor is simply that, a tool that is designed to edit text. Typically they are optimized for programming languages though many programmer's text editors are branching out and adding features for non-programming text like "),o("a",u,[e("Markdown"),n(t)]),e(" or "),o("a",c,[e("Org Mode"),n(t)]),e(". The key here is that text editors are designed to work with whatever language or framework you choose.")]),f,g])}const _=a(l,[["render",m],["__file","what-s-the-difference-between-an-ide-and-an-editor.html.vue"]]);export{_ as default}; diff --git a/assets/what-s-the-difference-between-an-ide-and-an-editor.html.7377822b.js b/assets/what-s-the-difference-between-an-ide-and-an-editor.html.7377822b.js new file mode 100644 index 0000000000..0eca1a3f73 --- /dev/null +++ b/assets/what-s-the-difference-between-an-ide-and-an-editor.html.7377822b.js @@ -0,0 +1 @@ +const e=JSON.parse(`{"key":"v-09b88655","path":"/docs/atom-archive/faq/sections/what-s-the-difference-between-an-ide-and-an-editor.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"What's the difference between an IDE and an editor?","slug":"what-s-the-difference-between-an-ide-and-an-editor","link":"#what-s-the-difference-between-an-ide-and-an-editor","children":[]}],"git":{"updatedTime":1667690985000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1},{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":1.13,"words":340},"filePathRelative":"docs/atom-archive/faq/sections/what-s-the-difference-between-an-ide-and-an-editor.md"}`);export{e as data}; diff --git a/assets/whitespace-settings.13b26c2a.js b/assets/whitespace-settings.13b26c2a.js new file mode 100644 index 0000000000..93416598c7 --- /dev/null +++ b/assets/whitespace-settings.13b26c2a.js @@ -0,0 +1 @@ +const s="/assets/whitespace-settings.7095448b.png";export{s as _}; diff --git a/assets/whitespace-settings.7095448b.png b/assets/whitespace-settings.7095448b.png new file mode 100644 index 0000000000..215182c374 Binary files /dev/null and b/assets/whitespace-settings.7095448b.png differ diff --git a/assets/whitespace.59413c09.png b/assets/whitespace.59413c09.png new file mode 100644 index 0000000000..e378c40ef1 Binary files /dev/null and b/assets/whitespace.59413c09.png differ diff --git a/assets/why-atom.html.4fb90fd5.js b/assets/why-atom.html.4fb90fd5.js new file mode 100644 index 0000000000..9d3a3d4ef4 --- /dev/null +++ b/assets/why-atom.html.4fb90fd5.js @@ -0,0 +1 @@ +const t=JSON.parse('{"key":"v-60602147","path":"/docs/atom-archive/getting-started/sections/why-atom.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"Why Atom?","slug":"why-atom","link":"#why-atom","children":[]}],"git":{"updatedTime":1663961800000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":2.41,"words":723},"filePathRelative":"docs/atom-archive/getting-started/sections/why-atom.md"}');export{t as data}; diff --git a/assets/why-atom.html.68b5b6aa.js b/assets/why-atom.html.68b5b6aa.js new file mode 100644 index 0000000000..164eeaa40e --- /dev/null +++ b/assets/why-atom.html.68b5b6aa.js @@ -0,0 +1 @@ +import{_ as e,o as t,c as a,f as o}from"./app.0e1565ce.js";const i={},n=o('

    Why Atom?

    There are a lot of text editors out there; why should you spend your time learning about and using Atom?

    Editors like Sublime and TextMate offer convenience but only limited extensibility. On the other end of the spectrum, Emacs and Vim offer extreme flexibility, but they aren't very approachable and can only be customized with special-purpose scripting languages.

    We think we can do better. Our goal is a zero-compromise combination of hackability and usability: an editor that will be welcoming to an elementary school student on their first day learning to code, but also a tool they won't outgrow as they develop into seasoned hackers.

    As we've used Atom to build Atom, what began as an experiment has gradually matured into a tool we can't live without. On the surface, Atom is the modern desktop text editor you've come to expect. Pop the hood, however, and you'll discover a system begging to be hacked on.

    The Nucleus of Atom

    The web is not without its faults, but two decades of development has forged it into an incredibly malleable and powerful platform. So when we set out to write a text editor that we ourselves would want to extend, web technology was the obvious choice. But first, we had to free it from its chains.

    The Native Web

    Web browsers are great for browsing web pages, but writing code is a specialized activity that warrants dedicated tools. More importantly, the browser severely restricts access to the local system for security reasons, and for us, a text editor that couldn't write files or run local subprocesses was a non-starter.

    For this reason, we didn't build Atom as a traditional web application. Instead, Atom is a specialized variant of Chromium designed to be a text editor rather than a web browser. Every Atom window is essentially a locally-rendered web page.

    All the APIs available to a typical Node.js application are also available to the code running in each window's JavaScript context. This hybrid provides a unique client-side development experience.

    Since everything is local, you don't have to worry about asset pipelines, script concatenation, and asynchronous module definitions. If you want to load some code, just require it at the top of your file. Node's module system makes it easy to break the system down into lots of small, focused packages.

    JavaScript, Meet C++

    Interacting with native code is also really simple. For example, we wrote a wrapper around the Oniguruma regular expression engine for our TextMate grammar support. In a browser, that would have required adventures with NaCl or Esprima. Node integration made it easy.

    In addition to the Node APIs, we also expose APIs for native dialogs, adding application and context menu items, manipulating the window dimensions, etc.

    Web Tech: The Fun Parts

    Another great benefit, that comes with writing code for Atom, is the guarantee that it's running on the newest version of Chromium. That means we can ignore issues like browser compatibility and polyfills. We can use all the web's shiny features of tomorrow, today.

    For example, the layout of our workspace and panes is based on flexbox. It's an emerging standard and has gone through a lot of change since we started using it, but none of that mattered as long as it worked.

    With the entire industry pushing web technology forward, we're confident that we're building Atom on fertile ground. Native UI technologies come and go, but the web is a standard that becomes more capable and ubiquitous with every passing year. We're excited to dig deeper into its toolbox.

    An Open-Source Text Editor

    We see Atom as a perfect complement to GitHub's primary mission of building better software by working together. Atom is a long-term investment, and GitHub will continue to support its development with a dedicated team going forward. But we also know that we can't achieve our vision for Atom alone. As Emacs and Vim have demonstrated over the past three decades, if you want to build a thriving, long-lasting community around a text editor, it has to be open source.

    The entire Atom editor is free and open source and is available under the https://github.com/atom organization.

    ',22),r=[n];function s(d,h){return t(),a("div",null,r)}const c=e(i,[["render",s],["__file","why-atom.html.vue"]]);export{c as default}; diff --git a/assets/why-does-atom-collect-usage-data.html.56dfada2.js b/assets/why-does-atom-collect-usage-data.html.56dfada2.js new file mode 100644 index 0000000000..b02327fd5a --- /dev/null +++ b/assets/why-does-atom-collect-usage-data.html.56dfada2.js @@ -0,0 +1 @@ +import{_ as t,o as a,c as o,a as e,b as s}from"./app.0e1565ce.js";const i={},n=e("h3",{id:"why-does-atom-collect-usage-data",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#why-does-atom-collect-usage-data","aria-hidden":"true"},"#"),s(" Why does Atom collect usage data?")],-1),c=e("p",null,"In the same way that aggregate usage information is important when developing a web application, we've found that it's just as important for desktop applications. By knowing which Atom features are being used the most, and how the editor is performing, we can focus our development efforts in the right place. For details on what data Atom is sending or to learn how to disable metrics gathering, visit https://github.com/atom/metrics.",-1),r=[n,c];function d(h,l){return a(),o("div",null,r)}const u=t(i,[["render",d],["__file","why-does-atom-collect-usage-data.html.vue"]]);export{u as default}; diff --git a/assets/why-does-atom-collect-usage-data.html.fd1638d4.js b/assets/why-does-atom-collect-usage-data.html.fd1638d4.js new file mode 100644 index 0000000000..1bd8d98821 --- /dev/null +++ b/assets/why-does-atom-collect-usage-data.html.fd1638d4.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-616d7939","path":"/docs/atom-archive/faq/sections/why-does-atom-collect-usage-data.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"Why does Atom collect usage data?","slug":"why-does-atom-collect-usage-data","link":"#why-does-atom-collect-usage-data","children":[]}],"git":{"updatedTime":1667690985000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1},{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":0.27,"words":80},"filePathRelative":"docs/atom-archive/faq/sections/why-does-atom-collect-usage-data.md"}');export{e as data}; diff --git a/assets/why-does-macos-say-that-atom-wants-to-access-my-calendar-contacts-photos-etc.html.d4b59bf7.js b/assets/why-does-macos-say-that-atom-wants-to-access-my-calendar-contacts-photos-etc.html.d4b59bf7.js new file mode 100644 index 0000000000..afbd92edbd --- /dev/null +++ b/assets/why-does-macos-say-that-atom-wants-to-access-my-calendar-contacts-photos-etc.html.d4b59bf7.js @@ -0,0 +1 @@ +import{_ as c,o as n,c as i,a as t,b as e,d as a,f as s,r}from"./app.0e1565ce.js";const h={},l=s('

    Why does macOS say that Atom wants to access my calendar, contacts, photos, etc.?

    With macOS 10.14 Mojave, Apple introduced new privacy protections similar to the existing protections found in iOS. Whenever an application attempts to access the files inside certain newly-protected directories, macOS asks the user whether they want to allow the application to access the content in those directories. These new privacy protections apply to the directories that contain your calendars, contacts, photos, mail, messages, and Time Machine backups.

    Applications trigger these new macOS prompts when attempting to access these directories in any way. Simply attempting to list the files in one of these directories is enough to trigger these prompts. These protections even apply to Apple's own applications. For example, if you open Terminal.app and try to list the files in ~/Library/Calendars, macOS shows a prompt saying, '"Terminal" would like access to your calendar.'

    screen shot 2018-10-03 at 14 04 40 pm

    Why does Atom need access to my calendar, contacts, photos, etc.?

    ',5),d=t("code",null,"~",-1),p={href:"https://flight-manual.atom.io/getting-started/sections/atom-basics/#opening-a-file-in-a-project",target:"_blank",rel:"noopener noreferrer"},m=t("img",{width:"1030",alt:"fuzzy-finder can trigger prompt",src:"https://user-images.githubusercontent.com/2988/46432185-9bfe9f80-c71b-11e8-83f7-758c3212d16c.png"},null,-1),y={href:"https://flight-manual.atom.io/using-atom/sections/find-and-replace/",target:"_blank",rel:"noopener noreferrer"},u=s('

    In addition to containing the files you're intending to edit inside Atom, your home directory also contains your files that have new OS-level protections in Mojave:

    • Calendar files (~/Library/Calendars)
    • Contacts files (~/Library/Application\\ Support/AddressBook
    • Mail files (~/Library/Mail)
    • Photos files (~/Pictures/Photos\\ Library.photoslibrary)

    Before letting Atom read these files, Mojave is understandably asking whether you want Atom to be able to access this personal data.

    What should I do when I see these prompts?

    Most people don't use Atom to view or edit their calendar files, contact files, photo library, etc. If you don't intend to use Atom to view/edit these files, then Atom doesn't need access to them. If you see a prompt from macOS saying that Atom would like to access these items, simply click Don't Allow.

    What happens if I allow Atom to access my calendar, contacts, photos, etc.?

    To Atom, these items are just files on disk. Atom treats them exactly like any other file you would view in Atom. Therefore, if you allow Atom to access these items, you'll be able to use Atom to browse the directories that contain these items, and you'll be able to view the files in those directories. That's it. Nothing more.

    You'll only be prompted once

    Fortunately, macOS will only prompt you once for each type of personal data. In other words, you might see a prompt asking you whether Atom can access your calendar, and you might see a prompt asking you whether Atom can access your contacts, but once you make those decisions, you won't see those prompts again.

    What if I change my mind?

    At any time, you can change your choices via System Preferences. Inside System Preferences, go to Security and Privacy, click the Privacy tab, and then click on Calendars to manage which apps can access your Calendars. The same goes for Contacts, Photos, etc.:

    manage access in system preferences

    What if I never want to see these prompts?

    Many people understandably expect their text editor to be able to open any file on disk. And that's exactly how things worked prior to macOS Mojave. If you would like to restore that behavior, you can proactively instruct macOS to allow you to access all files with Atom. To do so:

    1. Open your Applications folder in the Finder
    2. Open System Preferences, click the Security and Privacy icon, click the Privacy tab, and then click on Full Disk Access in the left-hand sidebar
    3. Click the lock icon to unlock System Preferences
    4. Drag Atom into Full Disk Access as shown below

    restore pre-mojave security via full-disk access

    ',16);function f(w,g){const o=r("ExternalLinkIcon");return n(),i("div",null,[l,t("p",null,[e("Atom doesn't need access to these items, but you might unintentionally cause Atom to try to access these items. This commonly occurs when you open your home directory ("),d,e(") inside Atom and run a command that examines all files and directories beneath your home directory. For example, when you open the "),t("a",p,[e("fuzzy-finder"),a(o)]),e(", it indexes the currently-open directory so that it can show you the available files:")]),m,t("p",null,[e("Similarly, using "),t("a",y,[e("find-and-replace"),a(o)]),e(" across the entire home directory will cause Atom to scan all files under your home directory.")]),u])}const A=c(h,[["render",f],["__file","why-does-macos-say-that-atom-wants-to-access-my-calendar-contacts-photos-etc.html.vue"]]);export{A as default}; diff --git a/assets/why-does-macos-say-that-atom-wants-to-access-my-calendar-contacts-photos-etc.html.dfe60df2.js b/assets/why-does-macos-say-that-atom-wants-to-access-my-calendar-contacts-photos-etc.html.dfe60df2.js new file mode 100644 index 0000000000..055856f98b --- /dev/null +++ b/assets/why-does-macos-say-that-atom-wants-to-access-my-calendar-contacts-photos-etc.html.dfe60df2.js @@ -0,0 +1 @@ +const a=JSON.parse('{"key":"v-43d2cead","path":"/docs/atom-archive/faq/sections/why-does-macos-say-that-atom-wants-to-access-my-calendar-contacts-photos-etc.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"Why does macOS say that Atom wants to access my calendar, contacts, photos, etc.?","slug":"why-does-macos-say-that-atom-wants-to-access-my-calendar-contacts-photos-etc","link":"#why-does-macos-say-that-atom-wants-to-access-my-calendar-contacts-photos-etc","children":[]}],"git":{"updatedTime":1667690985000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1},{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":2.67,"words":802},"filePathRelative":"docs/atom-archive/faq/sections/why-does-macos-say-that-atom-wants-to-access-my-calendar-contacts-photos-etc.md"}');export{a as data}; diff --git a/assets/why-is-atom-deleting-trailing-whitespace-why-is-there-a-newline-at-the-end-of-the-file.html.009bf895.js b/assets/why-is-atom-deleting-trailing-whitespace-why-is-there-a-newline-at-the-end-of-the-file.html.009bf895.js new file mode 100644 index 0000000000..3af9b8ab26 --- /dev/null +++ b/assets/why-is-atom-deleting-trailing-whitespace-why-is-there-a-newline-at-the-end-of-the-file.html.009bf895.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-65c8d593","path":"/docs/atom-archive/faq/sections/why-is-atom-deleting-trailing-whitespace-why-is-there-a-newline-at-the-end-of-the-file.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"Why is Atom deleting trailing whitespace? Why is there a newline at the end of the file?","slug":"why-is-atom-deleting-trailing-whitespace-why-is-there-a-newline-at-the-end-of-the-file","link":"#why-is-atom-deleting-trailing-whitespace-why-is-there-a-newline-at-the-end-of-the-file","children":[]}],"git":{"updatedTime":1667690985000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":2},{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":0.38,"words":115},"filePathRelative":"docs/atom-archive/faq/sections/why-is-atom-deleting-trailing-whitespace-why-is-there-a-newline-at-the-end-of-the-file.md"}');export{e as data}; diff --git a/assets/why-is-atom-deleting-trailing-whitespace-why-is-there-a-newline-at-the-end-of-the-file.html.4baf2edb.js b/assets/why-is-atom-deleting-trailing-whitespace-why-is-there-a-newline-at-the-end-of-the-file.html.4baf2edb.js new file mode 100644 index 0000000000..fe8cc77f55 --- /dev/null +++ b/assets/why-is-atom-deleting-trailing-whitespace-why-is-there-a-newline-at-the-end-of-the-file.html.4baf2edb.js @@ -0,0 +1 @@ +import{_ as s}from"./whitespace-settings.13b26c2a.js";import{_ as h,o as r,c as l,a as e,b as t,d as i,w as c,r as a}from"./app.0e1565ce.js";const d={},p=e("h3",{id:"why-is-atom-deleting-trailing-whitespace-why-is-there-a-newline-at-the-end-of-the-file",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#why-is-atom-deleting-trailing-whitespace-why-is-there-a-newline-at-the-end-of-the-file","aria-hidden":"true"},"#"),t(" Why is Atom deleting trailing whitespace? Why is there a newline at the end of the file?")],-1),f={href:"https://github.com/atom/whitespace",target:"_blank",rel:"noopener noreferrer"},_=e("code",null,"whitespace",-1),g={href:"http://stackoverflow.com/a/729795/1459498",target:"_blank",rel:"noopener noreferrer"},w=e("p",null,"You can disable this feature by going to the Packages list in the Settings View and finding the whitespace package:",-1),m=e("p",null,[e("img",{src:s,alt:"Whitespace package settings",title:"Whitespace package settings"})],-1);function u(k,y){const n=a("ExternalLinkIcon"),o=a("RouterLink");return r(),l("div",null,[p,e("p",null,[t("Atom ships with the "),e("a",f,[_,t(" package"),i(n)]),t(", which by default strips trailing whitespace from lines in your file, and inserts a final trailing newline to indicate end-of-file "),e("a",g,[t("as per the POSIX standard"),i(n)]),t(".")]),w,m,e("p",null,[t("Take a look at "),i(o,{to:"/using-atom/sections/editing-and-deleting-text/#whitespace"},{default:c(()=>[t("the Whitespace section")]),_:1}),t(" for more information.")])])}const W=h(d,[["render",u],["__file","why-is-atom-deleting-trailing-whitespace-why-is-there-a-newline-at-the-end-of-the-file.html.vue"]]);export{W as default}; diff --git a/assets/why-pulsar.html.36fb91be.js b/assets/why-pulsar.html.36fb91be.js new file mode 100644 index 0000000000..5b74c4e8f3 --- /dev/null +++ b/assets/why-pulsar.html.36fb91be.js @@ -0,0 +1 @@ +import{_ as o,o as r,c as i,e as n,a as t,b as e,d as s,f as d,r as l}from"./app.0e1565ce.js";const h={},u=t("h2",{id:"why-pulsar",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#why-pulsar","aria-hidden":"true"},"#"),e(" Why Pulsar?")],-1),c=d('

    There are a lot of text editors out there; why should you spend your time learning about and using Pulsar? Editors like Sublime and TextMate offer convenience but only limited extensibility. On the other end of the spectrum, Emacs and Vim offer extreme flexibility, but they aren't very approachable and can only be customized with special-purpose scripting languages.

    We think we can do better. Our goal is a zero-compromise combination of hackability and usability: an editor that will be welcoming to an elementary school student on their first day learning to code, but also a tool they won't outgrow as they develop into seasoned hackers.

    As we've used Pulsar to build Pulsar, what began as an experiment has gradually matured into a tool we can't live without. On the surface, Pulsar is the modern desktop text editor you've come to expect. Pop the hood, however, and you'll discover a system begging to be hacked on.

    The Nucleus of Pulsar

    The web is not without its faults, but two decades of development has forged it into an incredibly malleable and powerful platform. So when we set out to write a text editor that we ourselves would want to extend, web technology was the obvious choice. But first, we had to free it from its chains.

    The Native Web

    Web browsers are great for browsing web pages, but writing code is a specialized activity that warrants dedicated tools. More importantly, the browser severely restricts access to the local system for security reasons, and for us, a text editor that couldn't write files or run local sub-processes was a non-starter.

    For this reason, we didn't build Pulsar as a traditional web application. Instead, Pulsar is a specialized variant of Chromium designed to be a text editor rather than a web browser. Every Pulsar window is essentially a locally-rendered web page.

    All the APIs available to a typical Node.js application are also available to the code running in each window's JavaScript context. This hybrid provides a unique client-side development experience.

    Since everything is local, you don't have to worry about asset pipelines, script concatenation, and asynchronous module definitions. If you want to load some code, just require it at the top of your file. Node's module system makes it easy to break the system down into lots of small, focused packages.

    JavaScript, Meet C++

    Interacting with native code is also really simple. For example, we wrote a wrapper around the Oniguruma regular expression engine for our TextMate grammar support. In a browser, that would have required adventures with NaCl or Esprima. Node integration made it easy.

    In addition to the Node APIs, we also expose APIs for native dialogs, adding application and context menu items, manipulating the window dimensions, etc.

    Web Tech: The Fun Parts

    Another great benefit, that comes with writing code for Pulsar, is the guarantee that it's running on the newest version of Chromium. That means we can ignore issues like browser compatibility and polyfills. We can use all the web's shiny features of tomorrow, today.

    For example, the layout of our workspace and panes is based on flexbox. It's an emerging standard and has gone through a lot of change since we started using it, but none of that mattered as long as it worked.

    With the entire industry pushing web technology forward, we're confident that we're building Pulsar on fertile ground. Native UI technologies come and go, but the web is a standard that becomes more capable and ubiquitous with every passing year. We're excited to dig deeper into its toolbox.

    An Open-Source Text Editor

    We see Pulsar as a great replacement for Atom but we can't do it without your support going forward, since we know that we can't achieve our vision for Pulsar alone. As Emacs and Vim have demonstrated over the past three decades, if you want to build a thriving, long-lasting community around a text editor, it has to be open source.

    ',19),p={href:"https://github.com/pulsar-edit",target:"_blank",rel:"noopener noreferrer"};function w(b,m){const a=l("ExternalLinkIcon");return r(),i("div",null,[u,n('TODO: We probably want to make this our own, it is very "Atom" still'),c,t("p",null,[e("The entire Pulsar editor is free and open source and available under our "),t("a",p,[e("Organizational"),s(a)]),e(" repositories.")])])}const g=o(h,[["render",w],["__file","why-pulsar.html.vue"]]);export{g as default}; diff --git a/assets/why-pulsar.html.62b37d90.js b/assets/why-pulsar.html.62b37d90.js new file mode 100644 index 0000000000..bc22538c9a --- /dev/null +++ b/assets/why-pulsar.html.62b37d90.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-1ca4faf8","path":"/docs/launch-manual/sections/getting-started/sections/why-pulsar.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Why Pulsar?","slug":"why-pulsar","link":"#why-pulsar","children":[{"level":3,"title":"The Nucleus of Pulsar","slug":"the-nucleus-of-pulsar","link":"#the-nucleus-of-pulsar","children":[]},{"level":3,"title":"The Native Web","slug":"the-native-web","link":"#the-native-web","children":[]},{"level":3,"title":"JavaScript, Meet C++","slug":"javascript-meet-c","link":"#javascript-meet-c","children":[]},{"level":3,"title":"Web Tech: The Fun Parts","slug":"web-tech-the-fun-parts","link":"#web-tech-the-fun-parts","children":[]},{"level":3,"title":"An Open-Source Text Editor","slug":"an-open-source-text-editor","link":"#an-open-source-text-editor","children":[]}]}],"git":{"updatedTime":1670961579000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":2.4,"words":719},"filePathRelative":"docs/launch-manual/sections/getting-started/sections/why-pulsar.md"}');export{e as data}; diff --git a/assets/windows-downloads.16f9b58b.png b/assets/windows-downloads.16f9b58b.png new file mode 100644 index 0000000000..333ec33af3 Binary files /dev/null and b/assets/windows-downloads.16f9b58b.png differ diff --git a/assets/windows-downloads.e9b59e10.js b/assets/windows-downloads.e9b59e10.js new file mode 100644 index 0000000000..bca64f0f0b --- /dev/null +++ b/assets/windows-downloads.e9b59e10.js @@ -0,0 +1 @@ +const s="/assets/linux-downloads.66db3662.png",o="/assets/mac-downloads.a1802ab2.png",a="/assets/windows-downloads.16f9b58b.png";export{s as _,o as a,a as b}; diff --git a/assets/windows-system-settings.141561e2.js b/assets/windows-system-settings.141561e2.js new file mode 100644 index 0000000000..af1cf53491 --- /dev/null +++ b/assets/windows-system-settings.141561e2.js @@ -0,0 +1 @@ +const s="/assets/windows-system-settings.9698827f.png";export{s as _}; diff --git a/assets/windows-system-settings.9698827f.png b/assets/windows-system-settings.9698827f.png new file mode 100644 index 0000000000..74feaf948a Binary files /dev/null and b/assets/windows-system-settings.9698827f.png differ diff --git a/assets/wordcount.cf347e3c.png b/assets/wordcount.cf347e3c.png new file mode 100644 index 0000000000..c29e770805 Binary files /dev/null and b/assets/wordcount.cf347e3c.png differ diff --git a/assets/wrap-guide-line.159c54bb.js b/assets/wrap-guide-line.159c54bb.js new file mode 100644 index 0000000000..9693bf0353 --- /dev/null +++ b/assets/wrap-guide-line.159c54bb.js @@ -0,0 +1 @@ +const s="/assets/wrap-guide-line.efbabdc6.png";export{s as _}; diff --git a/assets/wrap-guide-line.efbabdc6.png b/assets/wrap-guide-line.efbabdc6.png new file mode 100644 index 0000000000..eea72fc74d Binary files /dev/null and b/assets/wrap-guide-line.efbabdc6.png differ diff --git a/assets/writing-in-atom.html.1d45f779.js b/assets/writing-in-atom.html.1d45f779.js new file mode 100644 index 0000000000..bb666f049c --- /dev/null +++ b/assets/writing-in-atom.html.1d45f779.js @@ -0,0 +1,4 @@ +import{_ as s,a as i}from"./preview.2eaf146a.js";import{_ as r,o as l,c,a as t,b as e,d as o,f as n,r as p}from"./app.0e1565ce.js";const d={},h=t("h3",{id:"writing-in-atom",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#writing-in-atom","aria-hidden":"true"},"#"),e(" Writing in Atom")],-1),u={href:"https://help.github.com/articles/about-writing-and-formatting-on-github/",target:"_blank",rel:"noopener noreferrer"},m=n('

    In these docs, we'll concentrate on writing in Markdown; however, other prose markup languages like Asciidoc have packages that provide similar functionality.

    Spell Checking

    If you're working in text (which includes plain text files, GitHub markdown, and Git commit messages by default), Atom will automatically try to check your spelling.

    Any misspelled words will be highlighted (by default with a dashed red line beneath the word), and you can pull up a menu of possible corrections by hitting Cmd+Shift+;Ctrl+Shift+; (or by choosing "Correct Spelling" from the right-click context menu or from the Command Palette).

    Checking your spelling

    To add more types of files to the list of what Atom will try to spell check, go to the Spell Check package settings in your Settings view and add any grammars you want to spell check.

    The default grammars to spell check are text.plain, source.gfm, text.git-commit, source.asciidoc, source.rst, and text.restructuredtext but you can add other grammars if you wish to check those types of files too.

    ',7),k={href:"https://github.com/atom/spell-check",target:"_blank",rel:"noopener noreferrer"},f=n('

    Previews

    When writing prose in a markup language, it's often very useful to get an idea of what the content will look like when it's rendered. Atom ships with a package for previewing Markdown by default.

    • Ctrl+Shift+M - Will toggle Preview mode for Markdown.

    Preview your prose

    As you edit the text, the preview will also update automatically. This makes it fairly easy to check your syntax as you type.

    You can also copy the rendered HTML from the preview pane into your system clipboard when the preview is focused and you press Cmd+CCtrl+CCtrl+Ins or if you right-click in the preview pane and choose "Copy as HTML".

    ',6),g={href:"https://github.com/atom/markdown-preview",target:"_blank",rel:"noopener noreferrer"},w=n(`

    Snippets

    There are also a number of great snippets available for writing Markdown quickly.

    If you type img and hit tab you get a Markdown-formatted image embed code like ![](). If you type table and hit tab you get a nice example table to fill out.

    | Header One | Header Two |
    +| :--------- | :--------- |
    +| Item One   | Item Two   |
    +

    Although there are only a handful of Markdown snippets (b for bold, i for italic, code for a code block, etc), they save you from having to look up the more obscure syntaxes. Again, you can easily see a list of all available snippets for the type of file you're currently in by choosing "Snippets: Available" in the Command Palette.

    `,5);function b(y,v){const a=p("ExternalLinkIcon");return l(),c("div",null,[h,t("p",null,[e("Though it is probably most common to use Atom to write software code, Atom can also be used to write prose quite effectively. Most often this is done in some sort of markup language such as Asciidoc or "),t("a",u,[e("Markdown"),o(a)]),e(" (in which this manual is written). Here we'll quickly cover a few of the tools Atom provides for helping you write prose.")]),m,t("p",null,[e("The spell checking is implemented in the "),t("a",k,[e("spell-check"),o(a)]),e(" package.")]),f,t("p",null,[e("Markdown preview is implemented in the "),t("a",g,[e("markdown-preview"),o(a)]),e(" package.")]),w])}const C=r(d,[["render",b],["__file","writing-in-atom.html.vue"]]);export{C as default}; diff --git a/assets/writing-in-atom.html.7240ac7e.js b/assets/writing-in-atom.html.7240ac7e.js new file mode 100644 index 0000000000..871225f760 --- /dev/null +++ b/assets/writing-in-atom.html.7240ac7e.js @@ -0,0 +1 @@ +const i=JSON.parse('{"key":"v-783f784b","path":"/docs/atom-archive/using-atom/sections/writing-in-atom.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"Writing in Atom","slug":"writing-in-atom","link":"#writing-in-atom","children":[]}],"git":{"updatedTime":1664634443000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1},{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":1.87,"words":560},"filePathRelative":"docs/atom-archive/using-atom/sections/writing-in-atom.md"}');export{i as data}; diff --git a/assets/writing-in-pulsar.html.226cb384.js b/assets/writing-in-pulsar.html.226cb384.js new file mode 100644 index 0000000000..99187d34d8 --- /dev/null +++ b/assets/writing-in-pulsar.html.226cb384.js @@ -0,0 +1,4 @@ +import{_ as s,a as i}from"./preview.2eaf146a.js";import{_ as r,o as l,c,a as t,b as e,d as o,f as n,r as p}from"./app.0e1565ce.js";const d={},u=t("h2",{id:"writing-in-pulsar",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#writing-in-pulsar","aria-hidden":"true"},"#"),e(" Writing in Pulsar")],-1),h={href:"https://help.github.com/articles/about-writing-and-formatting-on-github/",target:"_blank",rel:"noopener noreferrer"},m=n('

    In these docs, we'll concentrate on writing in Markdown; however, other prose markup languages like Asciidoc have packages that provide similar functionality.

    Spell Checking

    If you're working in text (which includes plain text files, GitHub markdown, and Git commit messages by default), Pulsar will automatically try to check your spelling.

    Any misspelled words will be highlighted (by default with a dashed red line beneath the word), and you can pull up a menu of possible corrections by hitting LNX/WIN: Ctrl+Shift+; - MAC: Cmd+Shift+; (or by choosing "Correct Spelling" from the right-click context menu or from the Command Palette).

    Checking your spelling

    To add more types of files to the list of what Pulsar will try to spell check, go to the Spell Check package settings in your Settings view and add any grammars you want to spell check.

    The default grammars to spell check are text.plain, source.gfm, text.git-commit, source.asciidoc, source.rst, and text.restructuredtext but you can add other grammars if you wish to check those types of files too.

    ',7),k={href:"https://github.com/pulsar-edit/spell-check",target:"_blank",rel:"noopener noreferrer"},g=n('

    Previews

    When writing prose in a markup language, it's often very useful to get an idea of what the content will look like when it's rendered. Pulsar ships with a package for previewing Markdown by default.

    • Ctrl+Shift+M - Will toggle Preview mode for Markdown.

    Preview your prose

    As you edit the text, the preview will also update automatically. This makes it fairly easy to check your syntax as you type.

    You can also copy the rendered HTML from the preview pane into your system clipboard when the preview is focused and you press LNX: Ctrl+Ins - WIN: Ctrl+C - MAC: Cmd+C or if you right-click in the preview pane and choose "Copy as HTML".

    ',6),f={href:"https://github.com/pulsar-edit/markdown-preview",target:"_blank",rel:"noopener noreferrer"},b=n(`

    Snippets

    There are also a number of great snippets available for writing Markdown quickly.

    If you type img and hit tab you get a Markdown-formatted image embed code like ![](). If you type table and hit tab you get a nice example table to fill out.

    | Header One | Header Two |
    +| :--------- | :--------- |
    +| Item One   | Item Two   |
    +

    Although there are only a handful of Markdown snippets (b for bold, i for italic, code for a code block, etc), they save you from having to look up the more obscure syntaxes. Again, you can easily see a list of all available snippets for the type of file you're currently in by choosing Snippets: Available in the Command Palette.

    `,5);function w(y,v){const a=p("ExternalLinkIcon");return l(),c("div",null,[u,t("p",null,[e("Though it is probably most common to use Pulsar to write software code, Pulsar can also be used to write prose quite effectively. Most often this is done in some sort of markup language such as Asciidoc or "),t("a",h,[e("Markdown"),o(a)]),e(" (in which this manual is written). Here we'll quickly cover a few of the tools Pulsar provides for helping you write prose.")]),m,t("p",null,[e("The spell checking is implemented in the "),t("a",k,[e("spell-check"),o(a)]),e(" package.")]),g,t("p",null,[e("Markdown preview is implemented in the "),t("a",f,[e("markdown-preview"),o(a)]),e(" package.")]),b])}const C=r(d,[["render",w],["__file","writing-in-pulsar.html.vue"]]);export{C as default}; diff --git a/assets/writing-in-pulsar.html.e1c1e98e.js b/assets/writing-in-pulsar.html.e1c1e98e.js new file mode 100644 index 0000000000..c85ca2f77f --- /dev/null +++ b/assets/writing-in-pulsar.html.e1c1e98e.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-e092af18","path":"/docs/launch-manual/sections/using-pulsar/sections/writing-in-pulsar.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Writing in Pulsar","slug":"writing-in-pulsar","link":"#writing-in-pulsar","children":[{"level":3,"title":"Spell Checking","slug":"spell-checking","link":"#spell-checking","children":[]},{"level":3,"title":"Previews","slug":"previews","link":"#previews","children":[]},{"level":3,"title":"Snippets","slug":"snippets","link":"#snippets","children":[]}]}],"git":{"updatedTime":1669229414000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":3}]},"readingTime":{"minutes":1.81,"words":544},"filePathRelative":"docs/launch-manual/sections/using-pulsar/sections/writing-in-pulsar.md"}');export{e as data}; diff --git a/assets/writing-specs.html.3849103b.js b/assets/writing-specs.html.3849103b.js new file mode 100644 index 0000000000..9f685cbcc2 --- /dev/null +++ b/assets/writing-specs.html.3849103b.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-28a937ee","path":"/docs/launch-manual/sections/core-hacking/sections/writing-specs.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":2,"title":"Writing Specs","slug":"writing-specs","link":"#writing-specs","children":[{"level":3,"title":"Create a New Spec","slug":"create-a-new-spec","link":"#create-a-new-spec","children":[]},{"level":3,"title":"Asynchronous Specs","slug":"asynchronous-specs","link":"#asynchronous-specs","children":[]},{"level":3,"title":"Running Specs","slug":"running-specs","link":"#running-specs","children":[]},{"level":3,"title":"Customizing your test runner","slug":"customizing-your-test-runner","link":"#customizing-your-test-runner","children":[]}]}],"git":{"updatedTime":1670466847000,"contributors":[{"name":"Daeraxa","email":"58074586+Daeraxa@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":4.5,"words":1351},"filePathRelative":"docs/launch-manual/sections/core-hacking/sections/writing-specs.md"}');export{e as data}; diff --git a/assets/writing-specs.html.44b3bd70.js b/assets/writing-specs.html.44b3bd70.js new file mode 100644 index 0000000000..99401ec155 --- /dev/null +++ b/assets/writing-specs.html.44b3bd70.js @@ -0,0 +1,89 @@ +import{_ as o,o as p,c,a as s,b as n,d as t,f as e,r as i}from"./app.0e1565ce.js";const u={},l=s("h2",{id:"writing-specs",tabindex:"-1"},[s("a",{class:"header-anchor",href:"#writing-specs","aria-hidden":"true"},"#"),n(" Writing Specs")],-1),r=s("p",null,"We've looked at and written a few specs through the examples already. Now it's time to take a closer look at the spec framework itself. How exactly do you write tests in Pulsar?",-1),d={href:"https://jasmine.github.io/archives/1.3/introduction",target:"_blank",rel:"noopener noreferrer"},k=s("h3",{id:"create-a-new-spec",tabindex:"-1"},[s("a",{class:"header-anchor",href:"#create-a-new-spec","aria-hidden":"true"},"#"),n(" Create a New Spec")],-1),h={href:"https://github.com/pulsar-edit/pulsar/tree/master/spec",target:"_blank",rel:"noopener noreferrer"},m={href:"https://github.com/pulsar-edit/markdown-preview/tree/master/spec",target:"_blank",rel:"noopener noreferrer"},v=s("code",null,"spec",-1),f=e(`

    Create a Spec File

    Spec files must end with -spec so add sample-spec.js to the spec directory.

    Add One or More describe Methods

    The describe method takes two arguments, a description and a function. If the description explains a behavior it typically begins with when; if it is more like a unit test it begins with the method name.

    describe("when a test is written", function () {
    +	// contents
    +});
    +

    or

    describe("Editor::moveUp", function () {
    +	// contents
    +});
    +

    Add One or More it Methods

    The it method also takes two arguments, a description and a function. Try and make the description flow with the it method. For example, a description of "this should work" doesn't read well as "it this should work". But a description of "should work" sounds great as "it should work".

    describe("when a test is written", function () {
    +	it("has some expectations that should pass", function () {
    +		// Expectations
    +	});
    +});
    +

    Add One or More Expectations

    `,11),b={href:"https://jasmine.github.io/archives/1.3/introduction#section-Expectations",target:"_blank",rel:"noopener noreferrer"},g=e(`
    describe("when a test is written", function () {
    +	it("has some expectations that should pass", function () {
    +		expect("apples").toEqual("apples");
    +		expect("oranges").not.toEqual("apples");
    +	});
    +});
    +
    Custom Matchers

    In addition to the Jasmine's built-in matchers, Pulsar includes the following:

    `,3),w={href:"https://github.com/velesin/jasmine-jquery",target:"_blank",rel:"noopener noreferrer"},y=e("
  • The toBeInstanceOf matcher is for the instanceof operator
  • The toHaveLength matcher compares against the .length property
  • The toExistOnDisk matcher checks if the file exists in the filesystem
  • The toHaveFocus matcher checks if the element currently has focus
  • The toShow matcher tests if the element is visible in the dom
  • ",5),q={href:"https://github.com/pulsar-edit/pulsar/blob/master/spec/spec-helper.js",target:"_blank",rel:"noopener noreferrer"},x=e(`

    Asynchronous Specs

    Writing Asynchronous specs can be tricky at first. Some examples.

    Promises

    Working with promises is rather easy in Pulsar. You can use our waitsForPromise function.

    describe("when we open a file", function () {
    +	it("should be opened in an editor", function () {
    +		waitsForPromise(function () {
    +			atom.workspace
    +				.open("c.coffee")
    +				.then((editor) => expect(editor.getPath()).toContain("c.coffee"));
    +		});
    +	});
    +});
    +

    This method can be used in the describe, it, beforeEach and afterEach functions.

    describe("when we open a file", function () {
    +	beforeEach(function () {
    +		waitsForPromise(() => atom.workspace.open("c.coffee"));
    +	});
    +
    +	it("should be opened in an editor", function () {
    +		expect(atom.workspace.getActiveTextEditor().getPath()).toContain(
    +			"c.coffee"
    +		);
    +	});
    +});
    +

    If you need to wait for multiple promises use a new waitsForPromise function for each promise. (Caution: Without beforeEach this example will fail!)

    describe("waiting for the packages to load", function () {
    +	beforeEach(function () {
    +		waitsForPromise(() => atom.workspace.open("sample.js"));
    +
    +		waitsForPromise(() => atom.packages.activatePackage("tabs"));
    +
    +		waitsForPromise(() => atom.packages.activatePackage("tree-view"));
    +	});
    +
    +	it("should have waited long enough", function () {
    +		expect(atom.packages.isPackageActive("tabs")).toBe(true);
    +		expect(atom.packages.isPackageActive("tree-view")).toBe(true);
    +	});
    +});
    +

    waitsForPromise can take an additional object argument before the function. The object can have the following properties:

    • shouldReject Whether the promise should reject or resolve (default: false)
    • timeout The amount of time (in ms) to wait for the promise to be resolved or rejected (default: process.env.CI ? 60000 : 5000)
    • label The label to display if promise times out (default: 'promise to be resolved or rejected')
    describe("when we open a file", function () {
    +	it("should be opened in an editor", function () {
    +		waitsForPromise(
    +			{
    +				shouldReject: false,
    +				timeout: 5000,
    +				label: "promise to be resolved or rejected",
    +			},
    +			() =>
    +				atom.workspace
    +					.open("c.coffee")
    +					.then((editor) => expect(editor.getPath()).toContain("c.coffee"))
    +		);
    +	});
    +});
    +

    Asynchronous Functions with Callbacks

    Specs for asynchronous functions can be done using the waitsFor and runs functions. A simple example.

    describe("fs.readdir(path, cb)", function () {
    +	it("is async", function () {
    +		const spy = jasmine.createSpy("fs.readdirSpy");
    +		fs.readdir("/tmp/example", spy);
    +
    +		waitsFor(() => spy.callCount > 0);
    +
    +		runs(function () {
    +			const exp = [null, ["example.coffee"]];
    +
    +			expect(spy.mostRecentCall.args).toEqual(exp);
    +			expect(spy).toHaveBeenCalledWith(null, ["example.coffee"]);
    +		});
    +	});
    +});
    +
    `,15),_={href:"https://jasmine.github.io/archives/1.3/introduction#section-Asynchronous_Support",target:"_blank",rel:"noopener noreferrer"},j=e(`

    Running Specs

    Most of the time you'll want to run specs by triggering the window:run-package-specs command. This command is not only to run package specs, it can also be used to run Pulsar core specs when working on Pulsar itself. This will run all the specs in the current project's spec directory.

    To run a limited subset of specs use the fdescribe or fit methods. You can use those to focus a single spec or several specs. Modified from the example above, focusing an individual spec looks like this:

    describe("when a test is written", function () {
    +	fit("has some expectations that should pass", function () {
    +		expect("apples").toEqual("apples");
    +		expect("oranges").not.toEqual("apples");
    +	});
    +});
    +

    Running on CI

    `,5),P={href:"https://blog.atom.io/2014/04/25/ci-for-your-packages.html",target:"_blank",rel:"noopener noreferrer"},A={href:"http://blog.atom.io/2014/07/28/windows-ci-for-your-packages.html",target:"_blank",rel:"noopener noreferrer"},T=e(`

    Running via the Command Line

    To run tests on the command line, run Pulsar with the --test flag followed by one or more paths to test files or directories. You can also specify a --timeout option, which will force-terminate your tests after a certain number of seconds have passed.

    pulsar --test --timeout 60 ./test/test-1.js ./test/test-2.js
    +

    Customizing your test runner

    By default, package tests are run with Jasmine 1.3, which is outdated but can't be changed for compatibility reasons. You can specify your own custom test runner by including an atomTestRunner field in your package.json. Pulsar will require whatever module you specify in this field, so you can use a relative path or the name of a module in your package's dependencies.

    Your test runner module must export a single function, which Pulsar will call within a new window to run your package's tests. Your function will be called with the following parameters:

    • testPaths An array of paths to tests to run. Could be paths to files or directories.
    • buildAtomEnvironment A function that can be called to construct an instance of the atom global. No atom global will be explicitly assigned, but you can assign one in your runner if desired. This function should be called with the following parameters:
      • applicationDelegate An object responsible for Pulsar's interaction with the browser process and host OS. Use buildDefaultApplicationDelegate for a default instance. You can override specific methods on this object to prevent or test these interactions.
      • window A window global.
      • document A document global.
      • configDirPath A path to the configuration directory (usually ~/.pulsar).
      • enablePersistence A boolean indicating whether the Pulsar environment should save or load state from the file system. You probably want this to be false.
    • buildDefaultApplicationDelegate A function that builds a default instance of the application delegate, suitable to be passed as the applicationDelegate parameter to buildAtomEnvironment.
    • logFile An optional path to a log file to which test output should be logged.
    • headless A boolean indicating whether or not the tests are being run from the command line via pulsar --test.
    • legacyTestRunner This function can be invoked to run the legacy Jasmine runner, giving your package a chance to transition to a new test runner while maintaining a subset of its tests in the old environment.

    Your function should return a promise that resolves to an exit code when your tests are finished running. This exit code will be returned when running your tests via the command line.

    `,8);function E(C,F){const a=i("ExternalLinkIcon");return p(),c("div",null,[l,r,s("p",null,[n("Pulsar uses "),s("a",d,[n("Jasmine"),t(a)]),n(" as its spec framework. Any new functionality should have specs to guard against regressions.")]),k,s("p",null,[s("a",h,[n("Pulsar specs"),t(a)]),n(" and "),s("a",m,[n("package specs"),t(a)]),n(" are added to their respective "),v,n(" directory. The example below creates a spec for Pulsar core.")]),f,s("p",null,[n("The best way to learn about expectations is to read the "),s("a",b,[n("Jasmine documentation"),t(a)]),n(" about them. Below is a simple example.")]),g,s("ul",null,[s("li",null,[s("a",w,[n("jasmine-jquery"),t(a)])]),y]),s("p",null,[n("These are defined in "),s("a",q,[n("spec/spec-helper.js"),t(a)]),n(".")]),x,s("p",null,[n("For a more detailed documentation on asynchronous tests please visit the "),s("a",_,[n("Jasmine documentation"),t(a)]),n(".")]),j,s("p",null,[n("It is now easy to run the specs in a CI environment like Travis and AppVeyor. See the "),s("a",P,[n("Travis CI For Your Packages"),t(a)]),n(" and "),s("a",A,[n("AppVeyor CI For Your Packages"),t(a)]),n(" posts for more details.")]),T])}const I=o(u,[["render",E],["__file","writing-specs.html.vue"]]);export{I as default}; diff --git a/assets/writing-specs.html.ccd20a54.js b/assets/writing-specs.html.ccd20a54.js new file mode 100644 index 0000000000..67a1713c07 --- /dev/null +++ b/assets/writing-specs.html.ccd20a54.js @@ -0,0 +1,89 @@ +import{_ as o,o as p,c,a as s,b as n,d as t,f as e,r as i}from"./app.0e1565ce.js";const u={},l=s("h3",{id:"writing-specs",tabindex:"-1"},[s("a",{class:"header-anchor",href:"#writing-specs","aria-hidden":"true"},"#"),n(" Writing Specs")],-1),r=s("p",null,"We've looked at and written a few specs through the examples already. Now it's time to take a closer look at the spec framework itself. How exactly do you write tests in Atom?",-1),d={href:"https://jasmine.github.io/archives/1.3/introduction",target:"_blank",rel:"noopener noreferrer"},k=s("h4",{id:"create-a-new-spec",tabindex:"-1"},[s("a",{class:"header-anchor",href:"#create-a-new-spec","aria-hidden":"true"},"#"),n(" Create a New Spec")],-1),h={href:"https://github.com/atom/atom/tree/master/spec",target:"_blank",rel:"noopener noreferrer"},m={href:"https://github.com/atom/markdown-preview/tree/master/spec",target:"_blank",rel:"noopener noreferrer"},v=s("code",null,"spec",-1),f=e(`
    Create a Spec File

    Spec files must end with -spec so add sample-spec.coffee to the spec directory.

    Add One or More describe Methods

    The describe method takes two arguments, a description and a function. If the description explains a behavior it typically begins with when; if it is more like a unit test it begins with the method name.

    describe("when a test is written", function () {
    +	// contents
    +});
    +

    or

    describe("Editor::moveUp", function () {
    +	// contents
    +});
    +
    Add One or More it Methods

    The it method also takes two arguments, a description and a function. Try and make the description flow with the it method. For example, a description of "this should work" doesn't read well as "it this should work". But a description of "should work" sounds great as "it should work".

    describe("when a test is written", function () {
    +	it("has some expectations that should pass", function () {
    +		// Expectations
    +	});
    +});
    +
    Add One or More Expectations
    `,11),b={href:"https://jasmine.github.io/archives/1.3/introduction#section-Expectations",target:"_blank",rel:"noopener noreferrer"},g=e(`
    describe("when a test is written", function () {
    +	it("has some expectations that should pass", function () {
    +		expect("apples").toEqual("apples");
    +		expect("oranges").not.toEqual("apples");
    +	});
    +});
    +
    Custom Matchers

    In addition to the Jasmine's built-in matchers, Atom includes the following:

    `,3),w={href:"https://github.com/velesin/jasmine-jquery",target:"_blank",rel:"noopener noreferrer"},y=e("
  • The toBeInstanceOf matcher is for the instanceof operator
  • The toHaveLength matcher compares against the .length property
  • The toExistOnDisk matcher checks if the file exists in the filesystem
  • The toHaveFocus matcher checks if the element currently has focus
  • The toShow matcher tests if the element is visible in the dom
  • ",5),q={href:"https://github.com/atom/atom/blob/master/spec/spec-helper.coffee",target:"_blank",rel:"noopener noreferrer"},x=e(`

    Asynchronous Specs

    Writing Asynchronous specs can be tricky at first. Some examples.

    Promises

    Working with promises is rather easy in Atom. You can use our waitsForPromise function.

    describe("when we open a file", function () {
    +	it("should be opened in an editor", function () {
    +		waitsForPromise(function () {
    +			atom.workspace
    +				.open("c.coffee")
    +				.then((editor) => expect(editor.getPath()).toContain("c.coffee"));
    +		});
    +	});
    +});
    +

    This method can be used in the describe, it, beforeEach and afterEach functions.

    describe("when we open a file", function () {
    +	beforeEach(function () {
    +		waitsForPromise(() => atom.workspace.open("c.coffee"));
    +	});
    +
    +	it("should be opened in an editor", function () {
    +		expect(atom.workspace.getActiveTextEditor().getPath()).toContain(
    +			"c.coffee"
    +		);
    +	});
    +});
    +

    If you need to wait for multiple promises use a new waitsForPromise function for each promise. (Caution: Without beforeEach this example will fail!)

    describe("waiting for the packages to load", function () {
    +	beforeEach(function () {
    +		waitsForPromise(() => atom.workspace.open("sample.js"));
    +
    +		waitsForPromise(() => atom.packages.activatePackage("tabs"));
    +
    +		waitsForPromise(() => atom.packages.activatePackage("tree-view"));
    +	});
    +
    +	it("should have waited long enough", function () {
    +		expect(atom.packages.isPackageActive("tabs")).toBe(true);
    +		expect(atom.packages.isPackageActive("tree-view")).toBe(true);
    +	});
    +});
    +

    waitsForPromise can take an additional object argument before the function. The object can have the following properties:

    • shouldReject Whether the promise should reject or resolve (default: false)
    • timeout The amount of time (in ms) to wait for the promise to be resolved or rejected (default: process.env.CI ? 60000 : 5000)
    • label The label to display if promise times out (default: 'promise to be resolved or rejected')
    describe("when we open a file", function () {
    +	it("should be opened in an editor", function () {
    +		waitsForPromise(
    +			{
    +				shouldReject: false,
    +				timeout: 5000,
    +				label: "promise to be resolved or rejected",
    +			},
    +			() =>
    +				atom.workspace
    +					.open("c.coffee")
    +					.then((editor) => expect(editor.getPath()).toContain("c.coffee"))
    +		);
    +	});
    +});
    +
    Asynchronous Functions with Callbacks

    Specs for asynchronous functions can be done using the waitsFor and runs functions. A simple example.

    describe("fs.readdir(path, cb)", function () {
    +	it("is async", function () {
    +		const spy = jasmine.createSpy("fs.readdirSpy");
    +		fs.readdir("/tmp/example", spy);
    +
    +		waitsFor(() => spy.callCount > 0);
    +
    +		runs(function () {
    +			const exp = [null, ["example.coffee"]];
    +
    +			expect(spy.mostRecentCall.args).toEqual(exp);
    +			expect(spy).toHaveBeenCalledWith(null, ["example.coffee"]);
    +		});
    +	});
    +});
    +
    `,15),_={href:"https://jasmine.github.io/archives/1.3/introduction#section-Asynchronous_Support",target:"_blank",rel:"noopener noreferrer"},j=e(`

    Running Specs

    Most of the time you'll want to run specs by triggering the window:run-package-specs command. This command is not only to run package specs, it can also be used to run Atom core specs when working on Atom itself. This will run all the specs in the current project's spec directory.

    To run a limited subset of specs use the fdescribe or fit methods. You can use those to focus a single spec or several specs. Modified from the example above, focusing an individual spec looks like this:

    describe("when a test is written", function () {
    +	fit("has some expectations that should pass", function () {
    +		expect("apples").toEqual("apples");
    +		expect("oranges").not.toEqual("apples");
    +	});
    +});
    +
    Running on CI
    `,5),A={href:"https://blog.atom.io/2014/04/25/ci-for-your-packages.html",target:"_blank",rel:"noopener noreferrer"},T={href:"http://blog.atom.io/2014/07/28/windows-ci-for-your-packages.html",target:"_blank",rel:"noopener noreferrer"},P=e(`
    Running via the Command Line

    To run tests on the command line, run Atom with the --test flag followed by one or more paths to test files or directories. You can also specify a --timeout option, which will force-terminate your tests after a certain number of seconds have passed.

    atom --test --timeout 60 ./test/test-1.js ./test/test-2.js
    +

    Customizing your test runner

    WARNING

    Warning: This API is available as of 1.2.0-beta0, and it is experimental and subject to change. Test runner authors should be prepared to test their code against future beta releases until it stabilizes.

    By default, package tests are run with Jasmine 1.3, which is outdated but can't be changed for compatibility reasons. You can specify your own custom test runner by including an atomTestRunner field in your package.json. Atom will require whatever module you specify in this field, so you can use a relative path or the name of a module in your package's dependencies.

    Your test runner module must export a single function, which Atom will call within a new window to run your package's tests. Your function will be called with the following parameters:

    • testPaths An array of paths to tests to run. Could be paths to files or directories.
    • buildAtomEnvironment A function that can be called to construct an instance of the atom global. No atom global will be explicitly assigned, but you can assign one in your runner if desired. This function should be called with the following parameters:
      • applicationDelegate An object responsible for Atom's interaction with the browser process and host OS. Use buildDefaultApplicationDelegate for a default instance. You can override specific methods on this object to prevent or test these interactions.
      • window A window global.
      • document A document global.
      • configDirPath A path to the configuration directory (usually ~/.atom).
      • enablePersistence A boolean indicating whether the Atom environment should save or load state from the file system. You probably want this to be false.
    • buildDefaultApplicationDelegate A function that builds a default instance of the application delegate, suitable to be passed as the applicationDelegate parameter to buildAtomEnvironment.
    • logFile An optional path to a log file to which test output should be logged.
    • headless A boolean indicating whether or not the tests are being run from the command line via atom --test.
    • legacyTestRunner This function can be invoked to run the legacy Jasmine runner, giving your package a chance to transition to a new test runner while maintaining a subset of its tests in the old environment.

    Your function should return a promise that resolves to an exit code when your tests are finish running. This exit code will be returned when running your tests via the command line.

    `,9);function E(C,F){const a=i("ExternalLinkIcon");return p(),c("div",null,[l,r,s("p",null,[n("Atom uses "),s("a",d,[n("Jasmine"),t(a)]),n(" as its spec framework. Any new functionality should have specs to guard against regressions.")]),k,s("p",null,[s("a",h,[n("Atom specs"),t(a)]),n(" and "),s("a",m,[n("package specs"),t(a)]),n(" are added to their respective "),v,n(" directory. The example below creates a spec for Atom core.")]),f,s("p",null,[n("The best way to learn about expectations is to read the "),s("a",b,[n("Jasmine documentation"),t(a)]),n(" about them. Below is a simple example.")]),g,s("ul",null,[s("li",null,[s("a",w,[n("jasmine-jquery"),t(a)])]),y]),s("p",null,[n("These are defined in "),s("a",q,[n("spec/spec-helper.coffee"),t(a)]),n(".")]),x,s("p",null,[n("For a more detailed documentation on asynchronous tests please visit the "),s("a",_,[n("Jasmine documentation"),t(a)]),n(".")]),j,s("p",null,[n("It is now easy to run the specs in a CI environment like Travis and AppVeyor. See the "),s("a",A,[n("Travis CI For Your Packages"),t(a)]),n(" and "),s("a",T,[n("AppVeyor CI For Your Packages"),t(a)]),n(" posts for more details.")]),P])}const I=o(u,[["render",E],["__file","writing-specs.html.vue"]]);export{I as default}; diff --git a/assets/writing-specs.html.de9c57a1.js b/assets/writing-specs.html.de9c57a1.js new file mode 100644 index 0000000000..4548042139 --- /dev/null +++ b/assets/writing-specs.html.de9c57a1.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-f2fe7262","path":"/docs/atom-archive/hacking-atom/sections/writing-specs.html","title":"","lang":"en-US","frontmatter":{},"excerpt":"","headers":[{"level":3,"title":"Writing Specs","slug":"writing-specs","link":"#writing-specs","children":[]}],"git":{"updatedTime":1664050274000,"contributors":[{"name":"Josh","email":"16845458+kaosine@users.noreply.github.com","commits":3}]},"readingTime":{"minutes":4.62,"words":1385},"filePathRelative":"docs/atom-archive/hacking-atom/sections/writing-specs.md"}');export{e as data}; diff --git a/assets/zoom.50f0dc7b.js b/assets/zoom.50f0dc7b.js new file mode 100644 index 0000000000..3ec157a9d4 --- /dev/null +++ b/assets/zoom.50f0dc7b.js @@ -0,0 +1 @@ +const s="/assets/zoom.bfb256f6.png";export{s as _}; diff --git a/assets/zoom.bfb256f6.png b/assets/zoom.bfb256f6.png new file mode 100644 index 0000000000..25bea0edb8 Binary files /dev/null and b/assets/zoom.bfb256f6.png differ diff --git a/blog/20221112-Daeraxa-ExamplePost.html b/blog/20221112-Daeraxa-ExamplePost.html new file mode 100644 index 0000000000..434f04b7d1 --- /dev/null +++ b/blog/20221112-Daeraxa-ExamplePost.html @@ -0,0 +1,223 @@ + + + + + + + + Example post | + + + + + + +

    Example post

    Less than 1 minute


    title: Example Post author: Daeraxa date: 2022-11-12 category:

    • website
    • testing tag:
    • new feature
    • placeholder sticky: false star: false article: false

    This is the excerpt.

    This is the body of the article.

    image

    + + + diff --git a/blog/20221127-confused-Techie-SunsetMisadventureBackend.html b/blog/20221127-confused-Techie-SunsetMisadventureBackend.html new file mode 100644 index 0000000000..e656aa14b7 --- /dev/null +++ b/blog/20221127-confused-Techie-SunsetMisadventureBackend.html @@ -0,0 +1,226 @@ + + + + + + + + A Sunset and the Misadventures of a Backend | + + + + + + +

    A Sunset and the Misadventures of a Backend

    confused-TechieNovember 27, 2022
    • dev
    • log
    • backend
    • sunset
    About 6 min

    What's the natural response to finding out an application you use is being sunset? Spend the next several months creating a complex backend infrastructure of course!

    In this blog post, which is my first so apologies if the style is widely inconsistent with everything else, I hope to get a place to talk about the journey that's occurred after the announcement of Atom's Sunset and the larger-than-assumed effect it has had on my life.

    Like some others, I'm assuming, on a normal day I was browsing Reddit when I saw the initial announcement. On June 8th of 2022 GitHub officially announcedopen in new window that they were going to sunset Atom.

    Now to give some background on where I was, I've never actually looked at the Atom source code. I used it every day, was the first thing I installed on new computers. But I liked it as a tool, and never paid much mind to how it was internally structured. But one of my favourite things was packages. The ability to install a package that can do nearly anything has always been amazing to me. I know many other applications can do this, but with this one feature Atom was everything I had ever wanted out of a text editor, and more so could be anything I ever want in the future.

    So I knew with the announcement that this would mean I wouldn't be able to install packages. So right away I did what anyone would, find out how to keep packages going.

    The Archival

    On June 12th of 2022, I wrote a super simple scriptopen in new window, one that would just absolutely hammer Atom's package repository and get everything I possibly could. The only goal here was to archive every single package and all of their versions. Hoping that later I'd figure out what I could do with this data.

    It was around that same time I started to see Atom Communityopen in new window mentioned as the most likely fork to succeed after Atom. So of course seeing their GitHub I joined their Discord and got going with a group of other talented volunteers to start seeing what needed to be done.

    Now I'll skip over the details about Atom Community, and the eventual split that occurred and focus on what my role was in the programming aspect.

    After creating this small script, I think what really made all of this feel like an actual team effort was the first PR I received for this project. A small diff, only +4 | -1 over on confused-Techie/AtomPackagesArchiveopen in new window by @DeeDeeG, but what it really meant was that truly there was a community here that cared about keeping this whole ecosystem alive the same way I did. And from there, that's all that was needed to be off to the races.

    What to do with this Data

    So now armed with a few thousand JSON files of Atom's Archived data, I had to figure out what made their backend tick. And since the Atom Package Repository is seemingly the only part of Atom that wasn't open source, I had nothing to base this on at all. So I just started hammering their servers.

    Hitting every single endpoint that was in the documentation with each variance of query parameters and HTTP Methods that I could think of. To see what broke, how it broke, and what it says when it breaks.

    On June 13th of 2022, I started the atom-community-server-backendopen in new window, what I thought would be the home of the new Backend for Atom Community, and there I delved into the strange idiosyncrasies of Atom. Still, the strangest one that sticks in my head, is I could tell how strange the codebase was just from the error messages. The best example, GET atom.io/api/packages/:invalidName Returns { message: "Not Found" } but hitting GET atom.io/api/users/:invalidName/stars returns { message: "Not found" }. Meaning that there was completely different code handling not found in one place, and in another. Notice the difference in capitalization.

    But soon after creating the repo, I realized that no one else on this small team we had was familiar with Golang at all, which is what I had initially written this in. So I switched over to JavaScript, which in the long run was likely a great choice as that is my most familiar language. And that's where I created confused-Techie/atom-community-server-backend-JS, a mouthful I know, but was much later renamed just to confused-Techie/atom-backendopen in new window.

    The Misadventures Part

    At this point speed was picking up, there was so much to do and so little time. Rapid fire code was being written and things were being tested.

    One thing that needed to be done was to take this mess of data I had archived and check it all. The most important thing I wanted to do, checked if everything was still valid, that is see if the GitHub repo each package pointed to still existed.

    Now in doing this, I ended up needing to look up a repo on GitHub many times. So many times I accidentally got my IP blocked from them. A huge oversight on my part, and not at all an issue with them, but I remember the stress that was caused when attempting to work together with several others on GitHub while being blocked by GitHub.

    In the end, the confused-Techie/atom-package-migratoropen in new window was able to check if every single package was valid, while also removing a list of banned package names that we had put together.

    Now this of itself was a feat, considering the following:

    • Total Packages Archived from Atom: 12,470
    • Total Packages Migrated: 11,074
    • Total Packages Unavailable: 1,381
    • Total Packages w/ Banned Names: 10
    • Total Packages w/ URL-Unsafe Names: 5

    But from there we now had 11,074 packages that we needed to keep safe and find a way to distribute to any user of the new Atom, wherever that ended up being.

    At this point, the other hardest part was that the Atom website for lack of better words, was on its last legs. With constant errors, crashes, and generally just failing to respond, it was clear things were nearing an end. Even worse was that when it would work, it was inundated with thousands of Spam packages. In the days after my initial archive, the original Package Repository had ballooned with spam packages now with their total packages totaling in... well. I just can't quite say. Even now during the time of writing I've spent ten minutes refreshing Atom.io/packages and it won't stop timing out or returning 500 errors. But for the sake of my point, I've kept trying, to confirm that the original Package Repository has ballooned with spam packages now to contain 405,137 Packages. But remember when I did my initial archival there were only 12,470. Just to convey how much spam this is, and how ridiculous it is that the service turned into this immediately after the announcement.

    But the last hardest part of this stage was the problems that I created for myself.

    Now you see, up until this point I've never had to worry about handling this much data. It wasn't until it was too late did I find out that my methodology was ill-fitting. I had written a nearly complete feature parity Backend for the Atom Package Repository, that expected all of its data to be JSON. Now anyone that's done this before will say that was a dumb choice, but forgive me for only being smaller scale prior. The excitement of being nearly done immediately faded, when I got the new backend running in the cloud, and threw all of my data at it. Every single package.

    Then simply I requested the default screen /api/packages which, in my poor implementation would scan every single package, then would filter it based on query parameters. Which would be iterating through every single one of them. Now you may hear this and think of course performance was terrible, and trust me it was a concern I had to, on commit f792a4975f932f20528b871e189300bd97585dacopen in new window I wrote the following comment after creating this terrible method.

    // One note of concern with chaining all of these together, is that this will potentially loop
    +// through the entire array of packages 3 times, resulting in a
    +// linear time complexity of O(3). But testing will have to determine how much that is a factor of concern.
    +

    Now I'm no mathematician and my understanding of Time Complexity is best described as weak, but I knew it could be a bad idea. And boy was it.

    Resuming after I had pushed everything to the cloud and hit my endpoint, I got no response. Or rather I thought it was no response. The page took well over 300 seconds before most often it would time out.

    So this meant I had to rewrite the entire thing to no longer assume we had JSON data, that's when we had to migrate the entire Backend Codebase to use an SQL server.

    The Good News

    Once the rewrite was done I posted the following on our Discord server.


    But heres some news I'm very excited to see, about the improvements of performance on the iterations of the backend, when querying /api/packages (As in get all packages with default sorts)

    • Reading all data from JSON on disk, took <300 seconds
    • Reading all packages from shoehorned, unoptimized SQL 39800ms or ~40 seconds
    • Reading all packages from optimized, properly normalized SQL 365ms or 0.365 seconds

    Keep in mind these times are from the server itself. So that includes the express server getting the request, filtering query parameters, querying the SQL server over the web, and rebuilding/pruning the JSON response. But this is great to see, and is finally something that seems shippable, even if it can be further optimized


    So with that, it was time to finish rewriting the backend, which while the struggles were not over, we had at least gotten over the (as so far) biggest hurdle.

    But beyond this, it was at this point we had a proper community. A team of skilled contributors are all willing to pull their weight to make this happen. We all have lives outside this project, we all live in different areas, in different timezones, and speak different languages. But the one thing we have in common is our mission, of keeping an Open Source Project we care about alive.

    Throughout all of this, that has been my biggest takeaway. The most important lesson I think is available to learn here, and one I don't want to forget any time soon.


    What's the natural response to finding out an application you use is being sunset?

    Take pride in the Open-Source community around you, and be thankful we live in a time where all that's needed to build a community and team is the text editor you choose to use.

    Thanks for reading, and as I always say thanks for contributing.

    + + + diff --git a/blog/20221208-Daeraxa-DistroTubeVideo.html b/blog/20221208-Daeraxa-DistroTubeVideo.html new file mode 100644 index 0000000000..517fc2e38f --- /dev/null +++ b/blog/20221208-Daeraxa-DistroTubeVideo.html @@ -0,0 +1,223 @@ + + + + + + + + Featured on DistroTube! | + + + + + + +

    Featured on DistroTube!

    DaeraxaDecember 8, 2022
    • news
    • video
    Less than 1 minute

    Today we were featured on the DistroTube YouTube channel!
    Watch hereopen in new window

    DistoTube is a channel/website all about free and open source software and one of our members reached out to tell them about the Pulsar project and he decided to feature us in an entire video!

    If you haven't seen it then click hereopen in new window or watch below:

    Or if you prefer you can watch it on Odyseeopen in new window

    And if you see this DT, thank you very much for the feature, we will be taking your comments to heart!

    + + + diff --git a/blog/20221215-confused-Techie-v1.100.1-beta.html b/blog/20221215-confused-Techie-v1.100.1-beta.html new file mode 100644 index 0000000000..b7586c6813 --- /dev/null +++ b/blog/20221215-confused-Techie-v1.100.1-beta.html @@ -0,0 +1,223 @@ + + + + + + + + Our First Release! | + + + + + + +

    Our First Release!

    confused-TechieDecember 15, 2022
    • dev
    • sunset
    About 7 min

    Check out our first GitHub Release for Pulsar! Available Now!open in new window

    First (Beta) Tagged Release of Pulsar -- Get it while it's hot!

    We have some nice changes to give you a good first experience of what Pulsar is going to be like. Mostly, it's Atom, but still functioning. We've replaced the Package Backend (previously closed source) with a brand new one (Open Source!) -- see this blog postopen in new window for more details. We have even migrated all original Packages from Atom to Pulsar, so feel free to find and download your favourites! A quick note about packages, if you have any questions or concerns about our migrated packages refer to hereopen in new window.

    Also new is Electron 12, and binaries for ARM, both on Linux and MacOS (yes, that's an Apple Silicon native build). In general, several new package formats are available, feel free to try them outopen in new window and find what suits you.

    We've gotten some miscellaneous fixes and upgrades in, as well as a (mostly) thorough rebranding from Atom to Pulsar. Feel free to check out the Changelogopen in new window for more details.

    Over all, we hope you enjoy the release, take a look, kick the tires, let us know what you think. Report any issues you find, and maybe we'll be able to put together a fix. Likewise, if you have a fix handy, feel free to open a Pull Request. Happy coding, see you among the stars!

    • The Pulsar Team

    • Bump to Electron 12 and Node 14
    • Added a rebranding API
    • Removed experimental file watchers on the editor
    • Ability to install packages from git repositories
    • New Pulsar Package Repository Backend
    • Better error messages when installing a package fails
    • Config watching fixes
    • Bump tree-sitter to 0.20.1 and all grammars to their recent versions
    • Native support for Apple Silicon and ARM Linux
    • Removed Benchmark Startup Mode
    • Removed all telemetry from Core Editor
    • New Pulsar Website
    • New Test Runner to Improve Testing
    • Added Apple Silicon support to github Package v0.36.13

    Pulsar

    ppm

    autocomplete-html

    settings-view

    snippets

    background-tips

    + + + diff --git a/blog/20230114-confused-Techie-v1.101.0-beta.html b/blog/20230114-confused-Techie-v1.101.0-beta.html new file mode 100644 index 0000000000..260e53004d --- /dev/null +++ b/blog/20230114-confused-Techie-v1.101.0-beta.html @@ -0,0 +1,223 @@ + + + + + + + + Our Second Beta! | + + + + + + +

    Our Second Beta!

    confused-TechieJanuary 15, 2023
    • dev
    About 4 min

    Check out our Second GitHub Release for Pulsar! Available Now!open in new window

    Second (Beta) Tagged Release of Pulsar -- Many improvements available!

    With the release of Pulsar Beta 1.101.0 we have quite a number of improvements and fixes to various issues that have been identified and logged by our wonderful community.

    Have a read of the changelog for the full details but some highlights include fixes to the Pulsar logo on Windows and Linux, a bunch of macOS changes to bring back some of the original functionality with opening files, and the resolution of an annoying issue on Linux that could stop the app from launching at all!

    Thank you so much to everyone who has been involved in this release, every comment to our discussions board or Discord server, every issue logged on GitHub and every pull request created have all contributed to getting us where we are today.

    And a special thank you for all of the donations we have received, we have some plans to use these wisely for the good of the project so look forward to the next big release!

    So please, if you have any comments then please join us on our Discord or discussions page, if you find any bugs or regressions then let us know via a GitHub issue (and if you can fix it (or anything else) then feel free to open a pull request).

    And as always, happy coding, see you among the stars!

    • The Pulsar Team

    • Fixed a bug where macOS menus like "Open" don't do anything
    • Fixed a bug where macOS wouldn't open files by dragging them onto the dock.
    • Fixed a bug where devtools won't open (https://github.com/pulsar-edit/pulsar/issues/260)
    • Fixed a bug where the editor refused to open with the message "GPU process isn't usable. Goodbye" (https://github.com/pulsar-edit/pulsar/issues/233)
    • Fixed logo artifacts on Linux
    • Fixed Windows Taskbar Icon being 'Cut in Half'
    • Fixed commands like --version, --package or --help did not show outputs
    • Fixed additional flags not being sent to --package
    • Small improvement on the binary size
    • Fixed "install command line tools" on Mac and Windows
    • Cached queries for featured packages (featured packages will load faster, and fewer errors on the settings-view regarding package info)
    • Added warning when settings-view is disabled, describing how to re-enable it

    Pulsar

    ppm

    + + + diff --git a/blog/20230201-Daeraxa-FebUpdate.html b/blog/20230201-Daeraxa-FebUpdate.html new file mode 100644 index 0000000000..21ffacb619 --- /dev/null +++ b/blog/20230201-Daeraxa-FebUpdate.html @@ -0,0 +1,223 @@ + + + + + + + + Community Update | + + + + + + +

    Community Update

    DaeraxaFebruary 1, 2023
    • news
    • log
    • update
    About 3 min

    What has the Pulsar team and community been up to lately?

    Community Update

    Hi everyone, welcome to our first post in what we hope will be a regular (or at least semi-regular) set of updates from the Pulsar team to let you know what is going on in the background. We see updates, work and improvements to the entire Pulsar ecosystem nearly every single day but many of these either aren't very obvious or requires a very close following of the various communication channels.
    This update format is designed to let people know of our major wins and exciting updates of things we are working on so you can share in the progress we are making.
    We hope you enjoy reading this and getting to know what is going on with the project and as ever we will see you among the stars!

    Tree-sitter

    Those who have been keeping up with the development of Pulsar know that one of our major goals is to upgrade to newer versions of Electron. Unfortunately this comes with quite a few challenges, one of which is the implementation of Tree-sitteropen in new window, a library used for syntax highlighting who's history is entwined with Atom's.

    Ultimately Tree-sitter moved on from Atom leaving it with a different implementation and one which is difficult to use with the changes in newer Electron versions.

    So instead of trying to fix the old one, the current goal is to look at using the WASM versionopen in new window and @Maurício Szaboopen in new window has been doing some fantastic work on getting this implemented into Pulsar. This post isn't for the specifics of this work save to say we have seen "interesting" issues and challenges with this but the work is looking very promising and if all works out then our main barrier to a major Electron upgrade will be out of our way.

    Package repository

    There has been an awful lot of work going on in this area and it is mostly work that, in theory, should be pretty invisible to most users of Pulsar if all goes well.

    @confused-techieopen in new window and @Digitalone1open in new window have been doing some amazing work to improve this area with updates to the way it handles versioning and a refactoring of the git and github interactions so hopefully we might support other platforms in the future.

    @Spiker985open in new window has been creating and updating the Swagger/OpenAPI definitionopen in new window for the backend API so we have a proper definition and validation of it and @confused-techieopen in new window started the Architecture documentopen in new window which explains how everything works at a high level to allow people to more easily contribute to the project. We hope to include more of these in other repositories as time goes on.

    In terms of some package related housekeeping, one of our main goals was to preserve as many of the Atom packages as possible. So when we found that some packages had some ambiguous or missing licences we have made the effort to contact the owner of each package and ask for permission to host them. The good news is that this has been well received and we have managed to preserve more than a few of them.

    Donations and binary signing

    We have had an awful lot of generous donations sent to us recently. In particular we have to give a massive shout out to @SubAtomic who has donated more money to the project alone than we thought we get in a whole year.

    @anonCoffee also gave us a very generous donation and was also the first sponsor on our new GitHub Sponsorshipopen in new window which is an alternative should you want to donate by a platform other than our existing Open Collectiveopen in new window.

    We also now have a webhook set up for the GitHub sponsors so now when we get a donation we can properly thank them as it pops up on our Discord #donations channel.

    A huge thank you to literally all of our other donators on both platforms, we appreciate everything you give and make so much of what we want to do, possible.

    Our first big expenditure we voted for (outside of ongoing hosting costs) will be to pay for Code Signing Certificates from Apple and Microsoft so that we can finally sign our binaries to prevent the issue of new users being told that they are broken, dangerous or may contain harmful code. @Meadowsysopen in new window has already started on the macOS process of this so with any luck the need to run a special command just to run our software will be a thing of the past.

    Matrix space

    @kaosineopen in new window has been working to set us up a Matrixopen in new window space for an alternative for people who would prefer to use an open source alternative to our main Discord Serveropen in new window. However, we do not want to split the community in this way so before we start publicising it and providing links we would rather get our Discord bridge set up first so that all messages can be seen on both platforms seamlessly. Once it is up and live we will update the community with all the info that could be needed.

    + + + diff --git a/blog/20230209-mauricioszabo-tree-sitter-part-1.html b/blog/20230209-mauricioszabo-tree-sitter-part-1.html new file mode 100644 index 0000000000..ba0dd5d57b --- /dev/null +++ b/blog/20230209-mauricioszabo-tree-sitter-part-1.html @@ -0,0 +1,223 @@ + + + + + + + + Tales of Tree-Sitter part 1: the start of a tale | + + + + + + +

    Tales of Tree-Sitter part 1: the start of a tale

    MaurícioFebruary 9, 2023
    • dev
    • modernization
    • tree-sitter
    About 3 min

    How did I decide to start working on tree-sitter, and all preparations to modernize it on Pulsar

    Tree-Sitter

    First, what is tree-sitter? Tree-sitter is basically a library that reads the source code and parses into an abstract syntax tree. That tree can then be used to find indentation rules or highlight elements like local variables, symbols, tags and everything.

    So, it's an alternative for TextMate grammars, but more powerful and faster. It also supports "incremental parsing", meaning that if you have a huge file that takes some time to highlight, when you make changes to that document you only need to wait for a fraction of time to parse the contents.

    Current Pulsar implementation

    The way that tree-sitter works right now is basically is the same way that it was implemented in Atom a long time ago: it basically tries to match the elements of the AST and convert that into TextMate scopes. So, supposing that it finds, for example, a left right assignment (something like a = 10)- it will basically try to convert the "left token" to a variable.

    That's where the problems begin. The new versions of tree-sitter are changing the AST in a way that Atom (or Pulsar) does not support. The second problem is this remapping: currently, it uses some pattern in CSON files, with some kind of "CSS selector"-based metalanguage. CSON is basically a YAML version of JSON and it only works in Atom (and now in Pulsar)...

    Basically - it means that all this remapping only works on Pulsar, and have no other editors that supports this; which, again, means that every version bump for grammars will have to keep remapping things over and over again (considering that all tree elements and nodes are considered "internal state" of each grammar) which is far from ideal (and does not scale, specially considering that we're a small team). Finally, it means that we need a huge number of tests that are copies of Tree-Sitter and TextMate just to be sure we're doing the same job that others are doing (again, far from ideal, specially considering that programming languages evolve, and we need to keep track of all this evolution, for all languages that Pulsar supports - a work that, basically, these grammars are already doing, and will probably do better than us).

    So, we would like to reuse what other editors use, and have the exact same experience without having to, from time to time, revisit all these "remappings" and that is where the queries come into action:

    Modern tree-sitter uses queries, that are basically a scheme-inspired file that basically maps AST to syntax elements (for highlighting), tags elements as local variables (also to help highlighting) and basically is more powerfull than the metalanguage we use currently in Pulsar.

    Modernizing the current implementation

    So recently, I was surprised with some very rare free time, and decided to work on modernizing tree-sitter on the editor. The main reason is also because, when we upgraded Pulsar to use Electron 12, we also have to update both tree-sitter and grammars, some of these are now not highlighting as they should. That, combined with the fact that the version of tree-sitter we're using (the native one, that have bindings to Node.JS) will stop working after electron 14 (that is, two versions for now) made me try to implement a WASM-based version.

    And that where the problems begin.

    Basically, nobody knows exactly how a "modern implementation" of tree-sitter looks like. I tried to talk with different plugin authors on different editor and they all gave me different responses. Most people rewrite the queries that come with tree-sitter, for example... questions that I asked, like for example, if there's a pattern for tokens, were answered with "if there's a pattern nobody actually learned that or even documented it"; also about queries, most people answered me that "that the query result is up to interpretation" which is not fine for my approaches...

    And so, without any official guide, I decided to try to match what the tree-sitter command-line interface does. Tree-sitter has a CLI that allows to highlight files. You run with tree-sitter highlight <name-of-file>, basically, and I found that it uses a new library that's basically not yet complete and does not have bindings for the browser (WASM) or Node. So first, I tried to analyze if it was going to be possible to expose this library to JS, but I didn't find a way to make "incremental parsing" (a huge interesting feature of tree-sitter where you basically inform to the highlighter what changed instead of the whole tree, and the library will only re-tokenize that specific fragment instead).

    Then, I decided to do what some every sane person would probably not do - implement everything by hand, improvising where things didn't work. Tokenize with the CLI, and try to match the same structure in Pulsar, fixing things where that didn't work... and that's part one.

    + + + diff --git a/blog/20230215-Daeraxa-v1.102.0.html b/blog/20230215-Daeraxa-v1.102.0.html new file mode 100644 index 0000000000..81225dfb23 --- /dev/null +++ b/blog/20230215-Daeraxa-v1.102.0.html @@ -0,0 +1,223 @@ + + + + + + + + New Regular Release (v1.102.0) | + + + + + + +

    New Regular Release (v1.102.0)

    DaeraxaFebruary 15, 2023
    • dev
    • release
    About 3 min

    Check out our newest Regular Release for Pulsar! Available Now!open in new window

    The next Pulsar Release 1.102.0!

    With the release of Pulsar 1.102.0 we have packed it full of improvements!

    With a huge focus on testing this time around we hope that this will result in a more stable and functional editor in all future releases.

    We've also added new icons for macOS and updated many of Pulsar and core packages dependencies.

    But for the big news, our macOS releases are now signed! Meaning no fancy commands need to be run prior to installation and you get a guarantee that Pulsar is made and published by us. Also as you might've noticed, this version has no beta in its name. We are now adopting a Rolling Release model and this can be considered the first of many stable releases. Take a look at our website for more information!

    And again, special thank you to all of our wonderful community members that have helped us write code, log issues, respond to everyone, and donate to the project. We truly could not do this without each and every one of you.

    And as always, happy coding, see you among the stars!

    • The Pulsar Team

    • Fixed a bug where pulsar on Windows could never trigger
    • Fixed github package shelling out to git on macOS
    • Fixed minor bugs found during fixes to tests
    • Improved our testing infastructure to aide in finding and fixing further bugs
    • Updated many dependencies of Pulsar and its core packages
    • New Pulsar Icon on macOS
    • Selected text is styled by default
    • Restored right-clicked CSS class on tags
    • Fixed syntax highlighting on C++
    • Updated JavaScript snippets to modern ES6 syntax
    • PPM no longer assumes master for git branches

    Pulsar

    ppm

    github

    + + + diff --git a/blog/20230216-Daeraxa-ReleaseStrategyUpdate.html b/blog/20230216-Daeraxa-ReleaseStrategyUpdate.html new file mode 100644 index 0000000000..694950a65e --- /dev/null +++ b/blog/20230216-Daeraxa-ReleaseStrategyUpdate.html @@ -0,0 +1,223 @@ + + + + + + + + Changes to our release strategy | + + + + + + +

    Changes to our release strategy

    DaeraxaFebruary 16, 2023
    • news
    • releases
    • rolling
    • regular
    About 5 min

    We recently decided to change our release strategy to reflect what we are actually doing and address some potential inaccuracies in our existing terminology.

    Why are we changing our release strategy?

    As many of you might already have noticed we have decided to make an adjustment in our release terminology and focus. Realistically nothing has actually changed, we have just made what has become our de-facto standard our official standard.

    This change was recently voted on in a poll on our Discord serveropen in new window to change our focus from a "point release" model to a "rolling release" one. There were a number of factors involved in this decision which had been discussed numerous times since our first release but this is the first time we had an official consensus on it.

    Essentially we felt that the terminology we were using was at best, unintentionally mildly misleading and at worst, incorrect so with this change we hope to address these issues.

    The previous model was as follows:

    • We would provide manually tagged releases at strategic milestones from our master branch. These milestones were never formally decided upon so we instead went with a semi-arbitrary release date of the 15th of each month simply to get these releases updated with all of our recent fixes.
    • These releases would be tagged beta or latest on our website to make it clear if it was a "stable" milestone release or a beta release candidate.
    • We had a second section on our page dedicated to "Cirrus CI Binaries". These binaries are produced from our CI platform automatically on pushes to our master branch. The idea of these were that they could be treated as dev, alpha or nightly type releases which were potentially unstable but had the latest updates.

    However it became clear that this wasn't what we were actually doing. What ended up happening was that our "stable" releases never got a proper milestone criteria agreed upon and quickly became out of date compared to our "Cirrus CI" releases.
    We also found that because all the fixes going into our master branch were so thoroughly reviewed and tested by both the Pulsar team and wider community, these ended up being more stable than the "stable" releases which quickly meant that, whilst our intention was correct, the reality was either incorrect or misleading.

    More than a few people commented on the use of the beta tag meaning it was unfinished or unstable software (even more so with the alpha and dev terms used on the Cirrus builds) so were avoiding Pulsar entirely whilst they waited for a proper, stable, release which often required us to manually explain the concepts - and those are just the vocal people we heard from so we don't even know how many people got turned off from the project entirely based on this which was never our intention.
    We also found that the very concept of the Cirrus CI binaries confused some people, particularly due to the name, so this also needed to be addressed.

    So after some lengthy discussions and the aforementioned poll, we decided to address these issues by reflecting what is actually going on in practice.

    So what does this mean for you? In terms of the application and releases themselves? Nothing. The biggest change is that the links and headers on our downloads pageopen in new window have been reorganised and renamed to reflect how we want to go forward with our releases. The sections below will go into detail on each type.

    Rolling Release

    The "Rolling Release" is the new name for what we previously called "Cirrus CI Binaries" and we have promoted this to the top of our downloads page to make it more obvious as we have a (soft) preference for its use - after all this is the "hyper-hackable text editor" and this seems to fit our mission statement nicely.

    This type of release has a number of benefits for everyone:

    • New functionality is available shortly after the relevant PR is merged. This means no waiting for a formal release milestone to include some desired features or fixes.
    • No formal release process is needed, we have a standard download link for each release binary that automatically pulls the very latest build from Cirrus CI.
    • Issues can be easily tracked to a particular PR or change because each version stamp is different for each PR so we can narrow down the problem rather easily.
    • Issues can be addressed quickly without the need for a formal tagged hotfix release process.

    Of course there are some potential drawbacks:

    • Despite our best efforts with reviews and testing it is entirely possible that things will fall through the cracks leading to a regression or issue meaning that you would need to downgrade or swap to the Regular Release until the issue gets resolved.
    • Whilst we don't have automatic updates implemented yet, when we do it means that an updated version will be almost constantly shown which might be seen as a curse or blessing from your standpoint.

    Regular Releases

    This is the new name for the section previously just labelled as "Releases". These have been moved down on the page but remain a valid option for those that want to use a more regular release model.
    Currently we are sticking to a release date of the 15th of every month (which initially was just a happy coincidence but we found we liked it and makes for easy organisation) but this not guaranteed and may change as needed.

    This also has some benefits for some over the rolling release:

    • Changes from one release to the next are more obvious as we provide an easy to read changelog that shows all the new functionality and fixes.
    • It is a somewhat more known quantity to the Pulsar team and community when it comes to support. It is easier to remember generally what kind of changes got added in v1.101.0 vs 1.101.2023021600.
    • Fewer updates made available - if you don't need to be up to date at all times then this slower release schedule may be of benefit as you won't have to download a new binary just to get a single bugfix for something that may not affect you personally - particularly relevant when we have hopefully automatic updates available in the future.
    • Allows us to make consolidated announcements with the changes to keep the wider community up to date via our various social channels.

    Of course there are also some potential drawbacks:

    • Fixes and features available in the Rolling Releases won't be available (unless urgent) until the next release slot. This also means that some people go to the effort of logging an issue on GitHub or in Discord only to be told that it has already been fixed.
    • Requires a more formal release process from the team where we have to be organized to tag and update the links which currently takes a fair amount of organization and effort from many people.
    • Unintentional implications that this is a "stable" or "LTS" type release over the Rolling Release. This is not the case as we consider all of our releases to be stable unless otherwise indicated and we do not support anything other than the latest released version (both Rolling and Regular).

    What can I expect going forwards?

    We don't see this strategy changing in the near future but, as prompted this change in the first place, circumstances change and if we need to change our release model again in the future then we have the flexibility to do so. Nothing is ever set in stone and you are more than welcome to comment on and discuss this change this via any of our community areasopen in new window.

    One thing that we haven't addressed yet is the concept of automated updates. These come in a few different flavours - in-app updates and package repository updates. At the moment to update you have to manually download every application binary from the website and re-install (if using a type that requires installing of course) but we hope to somewhat automate this.
    We hope to publish Pulsar to a number of software repositories which will allow the package managers to actually deal with the updates themselves. We will likely need to create two "channels" for the Rolling and Regular releases to reflect our normal process and make the whole thing unified.

    For the "in-app" updates, we first need to get this functional which is currently on our radar. We have had discussions and ideas from the community as to how we could do this including "on the fly" switching between the Rolling and Regular release channels in the application itself.

    Overall we think this is a positive step forward for the community, it may be a little unorthodox compared to what you may be used to from software releases but it has been working very well for us so far so we think it makes sense to finally make it official.

    We hope that you enjoyed this update, we want to keep the wider community kept as updated as much as possible outside of our closer communication channels and to be as transparent as possible for our reasoning behind making such changes, especially for changes like this.

    As ever, happy coding, see you among the stars!

    + + + diff --git a/blog/20230227-Daeraxa-Survey1.html b/blog/20230227-Daeraxa-Survey1.html new file mode 100644 index 0000000000..dca04c28ba --- /dev/null +++ b/blog/20230227-Daeraxa-Survey1.html @@ -0,0 +1,223 @@ + + + + + + + + Survey: How did you hear about us? | + + + + + + +

    Survey: How did you hear about us?

    DaeraxaFebruary 27, 2023
    • survey
    • community
    • socials
    Less than 1 minute

    A short survey asking how you heard about the Pulsar project as well as feedback on our community and social presence.

    We are curious to see where people are finding out about the project and also where people are looking for news, updates and information.

    If this goes well we may look at doing further community surveys on other aspects of Pulsar.

    There is no need to sign into any account to respond to this survey and no personal data is being collected (emails, names etc.).

    You can find the Survey (Google Forms) hereopen in new window.

    + + + diff --git a/blog/20230301-Daeraxa-MarUpdate.html b/blog/20230301-Daeraxa-MarUpdate.html new file mode 100644 index 0000000000..def88ca870 --- /dev/null +++ b/blog/20230301-Daeraxa-MarUpdate.html @@ -0,0 +1,223 @@ + + + + + + + + Community Update | + + + + + + +

    Community Update

    DaeraxaMarch 1, 2023
    • news
    • log
    • update
    About 3 min

    What has the Pulsar team and community been up to lately? Find out here!

    Welcome to the March community update

    Hi everyone, welcome to the March edition of our regular update posts about what is going on with the project, small wins and other details you may have missed.
    Obviously in the last month we have made some more obviously big updates such as the v1.102.0 releaseopen in new window and the changes to our release strategyopen in new window but this post is about the things that you might otherwise not have seen but still deserve to be known about.

    We also launched our first community surveyopen in new window which, as of time of writing, is still open. If this goes well (and depending on feedback) then we will be creating more in the future such as asking about new features, platform/use case surveys and general application feedback.

    With that said - onto the updates!

    Community Spotlight - HTML Tim on YouTube

    First of all I want to let everyone know about @htmltimopen in new window on YouTube. I came across his channel the other day and found a whole host of videos being made on Pulsar.
    He has covered a bunch of topics in his videos such as general overview of Pulsar, migrating from other editors, a look at a bunch of useful community packages and advice on HTML writing & editing.
    Definitely give his videosopen in new window or websiteopen in new window a look if you want to know how to get the most out of Pulsar!

    macOS Binary Signing

    We finally have signed binaries on macOS thanks to @meadowsysopen in new window. This was mentioned as being underway last month but this has now been achieved so no misleading messages on macOS when trying to install Pulsar. From the v1.102.0 release onwards (and including all rolling releases) you will no longer have to execute a terminal command to get this to run.

    Tree-sitter Modernization

    @maurício szaboopen in new window is still pushing ahead to get a modern implementation of tree-sitteropen in new window working on Pulsar.
    See Maurício's blog postopen in new window for more info on the topic but recent wins include getting code folding working which is no small achievement.

    With the new implementation of tree-sitter, we're gaining a better understanding of Atom's tokenizer. With this, we can simplify some of the work that will open the door to more interesting experiments, like packages that contribute to syntax elements, such as semantic highlighting.

    Autocomplete CSS/HTML Automatic Updates

    It was found that two of our core packages for HTML and CSS autocompletion had out of date or missing references. The project that was supplying the data for these completions was no longer being maintained so a new source of data had to be found.
    However it seems a like-for-like replacement from a reliable source is hard to find so @confused-techieopen in new window has been working on this to produce a reliable replacement to get this functionality working properly again so completions remain up to date with current standards.
    These changes will hopefully be implemented shortly and included in an forthcoming update.

    Backend Version Updates

    A refactoring on the Pulsar package backed for a new versioning system has been underway by @confused-techieopen in new window and @digitalone1open in new window. The update is intended to make the system more permissive for package authors, provide easier version management and allow for the concept of release channels.
    There is also a change to add a new "VCS service system" which will allow for publishing of channels from other systems than just GitHub so authors can instead publish from the platform of their choice.

    Snippets Package Updates

    @savetheclocktoweropen in new window has recently been heavily contributing to the project. As somebody originally involved (and experienced) with the atom/snippets package he has been adding new functionality and fixing existing issues.
    Particularly exciting updates here include the ability to map a snippet to a command as well as introducing the concept of snippet variables to Pulsar.
    Look forward to these updates in an upcoming version of Pulsar.

    action-pulsar-dependency Stabilization Updates

    action-pulsar-dependencyopen in new window is a GitHub action used for testing Pulsar packages. @spiker985open in new window has been making some updates here to ensure Windows path registration and macOS symlinking is working correctly. Any GitHub CI is now able to autonomously install the most recent Pulsar rolling release for package testing.

    + + + diff --git a/blog/20230315-Daeraxa-v1.103.0.html b/blog/20230315-Daeraxa-v1.103.0.html new file mode 100644 index 0000000000..42052bd553 --- /dev/null +++ b/blog/20230315-Daeraxa-v1.103.0.html @@ -0,0 +1,223 @@ + + + + + + + + It's that time again, Pulsar 1.103.0 is available now! | + + + + + + +

    It's that time again, Pulsar 1.103.0 is available now!

    DaeraxaMarch 15, 2023
    • dev
    • release
    About 2 min

    Check out our newest Regular Release for Pulsar! Available Now!open in new window

    What is new in 1.103.0?

    With this release, we have a number of quality-of-life updates to make things just that little bit easier.

    One big change is a new settings search page! No longer will you have to trawl through the settings and packages to find that one pesky bit of config, now everything is just a few keystrokes away! This feature is still experimental and will have more updates and tweaks coming, but please feel free to provide feedbackopen in new window so we can continue to improve it.

    We have some updates to our snippets, autocomplete-css, and github packages, which you can read about in the change log, that bring about a whole host of improvements from updated UI to new functionality to really improve your coding experience!

    Of course, no update would be complete without a massive thank you to our wonderful community, especially our contributors and donors who are making all this possible.

    And as ever, happy coding. See you among the stars!

    • The Pulsar Team

    • Added a new feature to Search for Pulsar's settings
    • Updated the completions provided by autocomplete-css to be as bleeding edge as possible.
    • Updated the instructions and look of the login flow for the github package.
    • Snippet transformations no longer have an implied global flag, bringing them into compatibility with snippets in most other editors.
    • Snippets can now be given command names instead of tab triggers, and thus can now be assigned to key shortcuts in keymap.cson.

    Pulsar

    Snippets

    Github

    PPM

    + + + diff --git a/blog/20230319-confused-Techie-HowLicenseNoneDeletedPackages.html b/blog/20230319-confused-Techie-HowLicenseNoneDeletedPackages.html new file mode 100644 index 0000000000..c0c9515b24 --- /dev/null +++ b/blog/20230319-confused-Techie-HowLicenseNoneDeletedPackages.html @@ -0,0 +1,238 @@ + + + + + + + + How 'license: none' Deleted Packages | + + + + + + +

    How 'license: none' Deleted Packages

    confused-TechieMarch 19, 2023
    • dev
    • backend
    About 7 min

    How setting license: 'none' removed almost 100 Packages from the Pulsar Package Registry.

    Licenses

    Something you're sure to have heard about if you've been around open source software for any length of time is licenses.

    Licensing your software is a fantastic way to tell the world simply and easily how they can use your code. A license can specify that your code is free and available to everyone, that people can charge for your code, or even that nobody is allowed to touch it except you.

    A quick refresher for anybody less familiar, some of the most popular open source licenses (as taken from ChooseALicense.comopen in new window):

    A license is an agreed upon binding contract between the code author and anyone that finds themselves using it. That is if it's used properly.

    Packages on Pulsar

    Now often times when a developer first begins working in the open source world licenses might be confusing, or seen as a hassle they don't want to deal with.

    Maybe they don't feel like researching which one is the right one to choose, or don't want to be locked into the wrong choice.

    Now for us at Pulsar we often times prefer to go with the MIT license, as it's one of the more permissive choices, and was what all the core developers could agree on. You can even see that listed in the package.jsonopen in new window of the code that hosts this blog.

    But while developers may not want to take the time to understand a license, the tools they use do their best to help them.

    With NPM when you run npm init (to automatically make a package.json on your system) will automatically choose the ISCopen in new window as the default license.

    Or even Pulsar's own package-generatoropen in new window package defaulting to the MIT license when setting up a package for developers.

    But even then, of course, developers are able to and should modify their package's licenses to however they see fit.

    Why This Matters

    Now, for anyone that's read my previous blog postopen in new window about initially creating the Pulsar package registry, you'll know that we had taken every single package from the original Atom package registry, and hosted it for Pulsar. Because like I say there, the package ecosystem is one of my major driving factors of using and loving Pulsar/Atom.

    When this was originally done though, we had no concerns over the license of a package, at least at first.

    But it wasn't long after talking to someone much more familiar with digital law, and Intellectual Property laws than anyone else on the team, we realized that re-hosting this content (even though it was just the metadata of the packages themselves, as the Pulsar Package Registry doesn't actually host the code of a package) counts as distribution, and redistribution. Which, the permission to do so is entirely governed by the package license, and the will of the author.

    Now when a developer had published something to the Atom package registry it doesn't matter the license they choose. As by act of them publishing the package there, they are giving permission to Atom for distributing that content. And the same is true when a package is purposefully published to Pulsar. But because the developers of the packages we had at first had never had implicitly given us their permission, all we had was the license of their package to go off of.

    How did we Handle This

    The night after learning of this potential break of authors licenses, me and many others on the Pulsar team began the painstaking process of verifying the license of every single package on the Pulsar package registry. Where we had to manually check the license field of 12,470 individual package's package.json.

    Since luckily at this point everything was in a database I was able to filter packages by the licenses they had. But even then on the initial evaluation there was 149 unique values across every single package. Where many of these simply listed License in LICENSE.md which meant we couldn't only rely on the database, we would have to check the repository where this code lived to properly review the license itself.

    From there we started filtering all of the licenses as we read through them, by properly written SPDX License IDsopen in new window. Being able to outright exclude results that contained MIT or mit and variations for all the other licenses listed in the above link for anything that allowed redistribution.

    Doing this I was able to reduce or unique license variations to 116 licenses. But then came the time for the more obscure ones listed. For example we had a lot that simply said GNU AGPL, EPL, GNU LGPGL, or Apache. But those licenses all had multiple versions, and with no version specified we had to read through the terms of every single version to determine if any of them prohibited re-distribution, since if any single version did, then we had to remove the package.

    And let me tell you, as someone unfamiliar with legalese, reading through the terms of these obscure licenses that had no easy breakdown on tl;drLegalopen in new window it wasn't easy reading material. For example, it meant decoding lines like this from the 'Apple Public Source License 2.0'open in new window:

    Externally Deploy verbatim, unmodified copies of the Original Code, for commercial or non-commercial purposes

    Although I think the most fun discovery made through trawling so many different possible licenses is discovering gems like 'WTFPL (Do What The F*ck You Want To Public License)'open in new window.

    Then finally, after 3 non-stop hours of reading the terms of more licenses than I ever knew existed, I had reached the first finish line, leaving me with 30 unique variations of licenses across many packages that were either a completely non-existent license, an invalid license, or simply a variation of "License in LICENSE.md"

    Below you can see the abomination of a filter I wrote as I slowly went through all of the possible licenses.

    SQL Filter Abomination

    And yes I'm aware that I could define an array and filter by that, but in my defense it was 1:39 AM after a few days of having my family over.

    At this point there was very few unique license left. With our only entries being:

    • LicenseRef-LICENSE
    • None
    • [object Object]
    • TBD
    • none
    • SEE LICENSE IN LICENSE
    • NONE
    • Commercial Shared Source

    Starting at 11:06 PM that night, when it hit 3:17 AM I was dead tired and left with the following results:

    • 215 Package with "NONE" as a license that need to be manually checked.
    • 19 Package Maintainers that need to be contacted due to invalid licenses
    • 1 Package that must be removed as redistribution is against their license

    Funnily enough after this most other Core developers concerns were on our [object Object] license. As @mauricioszabo jokingly called the 'Materialistic License'. But was the time I discovered the now depreciated Object Licenseopen in new window and learned we needed a way to properly decode those as well on the backend.

    The Actual Hard Part

    From here, we had our list of packages to manually verify by checking the license in their GitHub repo. Which luckily I had a huge amount of help from @Daeraxa and @Sertonix in doing this.

    While this was a long and arduous process, we got through it quickly, and in the end were left with 101 packages were we would have to contact the package owner to find the next best steps, since they had no valid package for us to check.

    It was at this point we littered GitHub far and wide with the following issues to every repo we had to:

    Hello, this is confused-Techie from the Pulsar Team.
    +
    +Pulsar is an actively developed fork of Atom and one of our major efforts is to preserve the ecosystem of Community Packages that were previously published on Atom.io
    +
    +While we want to list every single package that existed on Atom's Package Repository, we wanted to respect the license of each package within and ensure to only list packages that either the author has agreed for us to or packages that have a license that specify that redistribution is okay.
    +
    +That is exactly why we are contacting you today, it seems your package <package-name> has a license that makes it hard for us to determine if we should be listing it for redistribution.
    +
    +All that needs to happen for you as a package maintainer is for you to give authorization for us to list your package. Just reply to this issue either way and we will respect your wishes.
    +
    +Otherwise within 3 weeks, if there is no response, we will assume the answer is no and remove your package from the Pulsar Package Registry.
    +
    +And if you've missed the deadline we have and still want your package to be published, feel free to republish to Pulsar.
    +
    +Thanks a ton for your time, hope you have a great day!
    +

    At first it was exciting to see how many package maintainers responded quickly, receiving excited and encouraging responses like:

    Hello! Thanks for visiting my package, I am fine with Pulsar listing my package for redistribution.

    You can publish native-ui with Pulsar. Happy if somebody is still using it!

    You can publish it if you want, but to my knowledge no one uses it any more.

    Hi, I'm already aware of the Pulsar project and I follow your progress... if want to redistribute to Pulsar Package REgistry now - do not hesitate. Best regards, SB

    This was amazing to see, and gave us hope that we wouldn't have to remove many packages, since as you know, my biggest goal has been to ensure nothing is lost.

    But, alas, when our three week self-determined timeline came to an end many issues were left unanswered. Which makes sense, when you consider some packages had not seen any updates in 8 years or more. But was still sad to see, to say the least.

    But now came the time to abide by the deadline and properly respect everyone's licenses.

    Respecting A License

    After quickly drafting up a script to preform a mass deletion of packages, I simply ran npm run tool:delete array and watched as many of the packages we worked to archive were lost.

    In the end we had to remove 82 individual packages from the Pulsar package registry. All of which have of course have been documented in the Backend Admin Actions Logopen in new window.

    While some of the licenses that belonged to removed packages were unique, such as the authors name, the school they attended, their email, or referring to a license file that didn't exist. The majority simply said none or literally didn't exist, or said "license": "".

    To answer one package's license of "I need one?" all I can say is that if 73 packages saying "none" had decided they did, they would still be able to provide the utility they had originally set out to.

    With all of this said though, I hope to see a populous future of packages on Pulsar, and can only hope some of these removed packages get republished or reimplemented. And to all of the package maintainers that had responded to our issues, and to the wonderful team of developers with Pulsar that helped me with this project, I can't appreciate you all enough.

    But as always, thanks for reading, and thanks for contributing.

    confused-Techie

    + + + diff --git a/blog/20230326-Daeraxa-Survey1-Results.html b/blog/20230326-Daeraxa-Survey1-Results.html new file mode 100644 index 0000000000..c69395935b --- /dev/null +++ b/blog/20230326-Daeraxa-Survey1-Results.html @@ -0,0 +1,223 @@ + + + + + + + + Survey Results: How did you hear about us? | + + + + + + +

    Survey Results: How did you hear about us?

    DaeraxaMarch 26, 2023
    • survey
    • community
    • socials
    About 3 min

    Here we have the results and analysis of our first community survey (and an infographic for those who like that kind of thing).

    A big thank you to everyone who participated in our recent social media survey. This really helped answer some questions that we had in order to be able to make sure we were reaching as much of the community as possible. We were honestly surprised at some of the results and we will definitely be taking lots of it on board.

    First of all here is an infographic @Daeraxaopen in new window put together on the data that was provided.

    And now onto the analysis.

    How did you hear about us?

    The first question on the survey was "How did you hear about us"? The idea with this one was to see where people had heard about Pulsar from to see if this aligned with some of our earlier efforts to publicise the project.

    By far the biggest result here was simply people searching the web for an Atom replacement, I don't think there were any shockers here as this is how many of us got involved in the first place.

    A slightly more surprising result was the number of people who found out via Reddit, mostly /r/atomopen in new window but also from a number of programming or other related subreddits.

    We also had a lot of people come from seeing a YouTube video on the project and it seems a considerable number of those were from the DistroTube channelopen in new window (which we mentioned in a previous blogopen in new window). It really goes a long way to show the effect that community content creators in this space can have on a project and its popularity.

    We had a couple of other interesting results here such as word of mouth from colleagues, various web articles, alternative software sites and even the Atom Wikipedia page.

    Which community area do you check for updates?

    This was a very interesting question and one we thought especially important to make sure we were placing the right amount of focus on our various community areas. The last thing we want is for anyone to be left out of important news and updates because of choosing one community area over another.

    What took us a little by surprise was just how many people do actually check the website - 66% of people check the website in one form or another, either by itself or along with other media channels. It is clear that we need to make sure that we put a lot of effort into this to make sure this is as good as it can be.

    I think there was no surprise to see a large number of people check the Discordopen in new window server, this is after all the area in which most of our active community members tend to hang out so if you want to pop in and say hi then this would be the place to do so.

    What took us aback slightly was just how popular the /r/pulsareditopen in new window subreddit, GitHub Discussionsopen in new window forum and Mastodonopen in new window account are. I think we expected to see these a little lower on the popularity but instead it is clear that we need to invest a little more time and effort into these platforms to make them feel equal.

    Other comments on social media presence

    This was deliberately a very freeform question and was really designed as a way for people to say what they wanted on the topic of our social media. We had a bunch of really lovely messages from people thanking us and giving us encouragement so thank you so much for those comments.

    Some comments we will definitely be taking to heart are the ones about improving our presence on some of our other channels (i.e. Mastodonopen in new window, GitHub Discussionsopen in new window, /r/pulsareditopen in new window subreddit) as well as making announcements, releases and new blog posts more immediately obvious on our website.

    Summary

    We also had a bunch of comments about Pulsar itself and around its development, whilst we are listening and will take those comments on board, we won't be focusing on them in this post - that is info we will likely ask in a future poll. And on the topic of future polls it is clear that this was a really rather successful experiment, we got a good number of replies and feedback which will hopefully help us improve our community communication.

    For anybody who wants to see the raw data (don't worry we went through everything to make sure there was definitely no identifiable data or anything unsavoury) then you can see it on our organization repoopen in new window.

    We will absolutely be using this format again as it seems to be a great way for people to give their feedback and comments, especially for those who may not use our busier social channels. So thank you again for all those who took part and look forward to the next one!

    + + + diff --git a/blog/20230401-Daeraxa-AprUpdate.html b/blog/20230401-Daeraxa-AprUpdate.html new file mode 100644 index 0000000000..616716feb8 --- /dev/null +++ b/blog/20230401-Daeraxa-AprUpdate.html @@ -0,0 +1,223 @@ + + + + + + + + Community Update | + + + + + + +

    Community Update

    DaeraxaApril 1, 2023
    • news
    • log
    • update
    About 6 min

    Another dose of our regular monthly community update!

    Welcome to the April Community Update (don't worry, no fooling going on here)!

    Hi everyone and welcome to the April installment of our monthly update that keeps you informed about what is going on in the background of Pulsar, work in progress, community contributions and more!

    One thing to quickly note in this opening ramble, a big thank you for everyone who responded to our recent community survey, if you want to see the results and analysis then have a look at the previous blog postopen in new window we wrote. If you missed it then don't worry, there will be plenty more examples to have your say as we plan to create more surveys in the future.

    You will notice I've tried to jazz the post up a little with some images this time around, please let us knowopen in new window if you have any comments on this addition.

    Now that has been dealt with, onto the updates!

    i18n (internationalization) Efforts

    [1]

    @meadowsysopen in new window and @confused-techieopen in new window have been making great progress on the efforts to provide native i18n functionality within Pulsar. This means you will be able to select your own locale/language and Pulsar will be able to translate the various menus and items for you. Of course the big caveat here is that currently none of it is translated so if you have the ability to then we will be asking the community to provide translations wherever possible. To do this we are currently planning to use Crowdinopen in new window.

    We will make a bigger announcement on this feature once it is ready.

    Tree-sitter Modernization

    [2]

    Another month and more progress has been made on this front, particularly by @maurício szaboopen in new window and @savetheclocktoweropen in new window, to get our tree-sitter implementation modernized. Some users noticed that opening some languages on Pulsar, like C++, Java, and Ruby, makes some syntax highlighting tokens different from what they were in Atom, and most of the time, highlighting is either wrong or some tokens are simply missing.

    With this rewrite, we're updating Atom’s tree-sitter support to use a newer approach for mapping tree nodes to scope names: using queriesopen in new window. This means more accurate mapping of scopes. We can also use queries to define indentation rules, code folding boundaries, and other features. It further means we won’t be stuck with a "different" implementation of tree-sitter from all other editors, and we can keep up-to-date with the recent developments and advancements on tree-sitter.

    Finally, we also moved away from the "binary" version of tree-sitter (instead using the WASM version). This means that we can migrate the editor to newer Electron versions without trouble, and it's also more future-proof - WASM is a technology that runs on browsers and it's not a Node.JS-only thing. It also makes our compilation process easier, more reliable, and faster (we don't need to compile all grammars and tree-sitter itself on Linux, Windows, and Mac, and for Intel and ARM processors).

    For more background info see some of the previous updates or Maurício's blog postopen in new window on the topic.

    TextMate Grammar Library & Superstring Migration to WASM

    [3]

    Atom and current versions of Pulsar use a library called first-mateopen in new window that uses node-onigurumaopen in new window to parse the legacy TextMate grammars (the original ones before Atom moved to tree-sitter as its primary grammar choice). Like tree-sitter and superstring, this is something that is preventing our migration to modern versions of Electron so @maurício szaboopen in new window and @savetheclocktoweropen in new window have been working on this to instead migrate to a new library we are calling second-mate which uses vscode-onigurumaopen in new window instead which is WASM based. Funny how things eventually come back around to borrowing from VSC instead...

    Work has also been going on to migrate the existing superstringopen in new window library to WASM. This, along with tree-sitter and first-mate mentioned above, is blocking our progress to modern Electron versions. Superstring is the library at the heart of the editor itself so this is quite a big change that will require a good amount of work and testing before it is ready but we are making progress!

    Package Badges

    @confused-techieopen in new window has made some changes to the package backend and website to support badges which can be applied to packages. Currently these can only be added by the Pulsar team but we will hopefully be looking to roll out something to package authors later. Essentially this allows us to add some additional metadata to a package to give information to Pulsar users. This is particularly important for Pulsar as a fork of Atom because we brought with us the vast majority of the Atom packages that were created over the years (see @confused-techieopen in new window's previous blog postsopen in new window on this subject for more info').

    backend-badge.png

    Currently we have rolled out an Outdated badge - this is designed so that we can help Pulsar users with additional info about particular packages which we know are being actively developed but have not been updated by the authors to the Pulsar backend. For example the hydrogen packageopen in new window (shown above) has some problems working with Pulsar, changes have been added to it to make it work but as these changes have not been pushed to the backend by the package authors the only way of getting it is to install them using ppm/pulsar -p using the GitHub/Git Remotesopen in new window functionality. To see more info on the specific reason a badge was added, you can click the badge and it will bring you to a document of our backend admin actions which include the reason for the addition as well as info to help.

    Not all badges are intended to be negative. We plan to offer badges to help the community such as Looking for Maintainers if a package author wishes for some help or to hand over the package maintenance entirely. We have already added a Made for Pulsar! badge to indicate packages which have been published or updated to the Pulsar backend to help people work out which packages are current and being updated as well as help prevent problems with installing very old and unmaintained Atom packages.

    backend-badge-made.png

    You can read the badge specificationopen in new window for more info.

    Badges are not currently available in Pulsar itself but we are working on it!

    Package Service Filtering

    [4]

    An interesting update to our backend that allows for new queries to be made for finding out what packages provide or consume a given service. This can be used in a number of ways, users can easily filter by package functionality rather than just keywords and developers/packages authors can search to see what packages may provide or consume a given service to help create a more interconnected set of packages.

    For example this query https://web.pulsar-edit.dev/packages?service=terminal&serviceType=providedopen in new window will search for all packages that provide the terminal service so if somebody wishes to find packages that provide or consume terminal then it is now easy to do so.

    This functionality is currently only available via the API and website query but we plan to offer some interesting and (hopefully) useful features on the website (and potentially Pulsar itself) to display these services.

    Bundling ppm Within Pulsar

    [5]

    As you may know, ppm is the Pulsar Package Manager — the thing you interact with when you want to install or update packages. Most people will probably only use this by interacting with the packages menu inside Pulsar but ppm is also supplied as a command line applications accessible via either ppm or pulsar -p. One issue with this approach is that we have to account for all of the different OSs and packaging methods which means adding it to $PATH correctly on each OS etc. (This was even an issue with Atom which had a special macOS only command to install the apm command line again).

    With this change we hopefully avoid all of this as it is simply part of the main pulsar executable so we only have to account for the details of one application and not two. This also means we can do some stuff we weren't able to easily do before, like expose the ppm commands within Pulsar itself without having to use a terminal at all. This update has the potential to improve a lot of things so watch this space!

    Chocolatey

    For those unfamiliar, Chocolatelyopen in new window is a package manager for Windows and something we have been wanting to officially support for a while now. Whilst we have not got anything available yet (don't worry we will make an announcement once it is) we have a bunch of community members who have been working on this and helping out so a big thank you to community members @HighHarmonics, @il_mix and @COLAMAroro for their contributions here.


    1. Image from https://openclipart.org/open in new window ↩︎

    2. Image from https://tree-sitter.github.io/tree-sitter/open in new window - Copyright (c) 2018-2021 Max Brunsfeld ↩︎

    3. Image from https://github.com/carlosbaraza/web-assembly-logoopen in new window - CC0 1.0 Universal ↩︎

    4. Image from https://openclipart.org/open in new window ↩︎

    5. Image from https://vectorified.comopen in new window - CC BY-NC 4.0 Licence ↩︎

    + + + diff --git a/blog/20230401-confused-Techie-PON.html b/blog/20230401-confused-Techie-PON.html new file mode 100644 index 0000000000..64464d9b33 --- /dev/null +++ b/blog/20230401-confused-Techie-PON.html @@ -0,0 +1,246 @@ + + + + + + + + Meet PON (Pulsar Object Notation) | + + + + + + +

    Meet PON (Pulsar Object Notation)

    confused-TechieApril 1, 2023
    • dev
    • joke
    About 2 min

    The release of PON!

    One day during discussions amoung the Pulsar dev team, we came to the topic of what file types to support for user's config files. While it was mostly a question of how support would go with allowing anything from JSON, JSON5, TOML, YAML, CSON, and anything else we could think of, in the end we couldn't agree on a solution.

    We kept finding downsides to every possibility, and concerns for support with so many. So we decided it only made sense, to take the true Atom approch, and reinvent the wheel with an in-house solution.

    What we created is PON (Pulsar Object Notation)!

    This new file format takes the best thing of every language and combines them into one.

    Taking all of their strengths, and none of their weaknesses. Pulsar Object Notation has a more expansive feature set than any other object file format than has ever existed!

    • Easy to determine the types of keys
    • Human Readable object structure
    • Incredibly clear object nesting
    • Comments & Multiline comments are supported!

    Best of all PON is easy to learn, and easy to use!

    Compare the following JSON:

    {
    +	"core": {
    +		"projectHome": "/home/dae/pulsar",
    +		"welcome": {
    +			"showOnStartup": true,
    +			"showChangeLog": false
    +		}
    +	}
    +}
    +

    The above JSON is flat out ugly. Plus, it has no support for comments, and it's nearly impossible to see which object a key belongs to without painstakingly counting each and every bracket; really JSON is an outdated, and hard to read format.

    But from the impossible to use JSON above, here is the same thing in PON!

    Dim core As Object
    +Dim projectHome As String
    +Dim welcome As Object
    +Dim showOnStartup As Boolean
    +Dim showChangeLog As Boolean
    +
    +10 # <<core>> [
    +  10.10 ## projectHome:= <</home/dae/pulsar>>
    +  10.20 ## <<welcome>> [
    +    <¿--<<An easy to type comment!>>--?>
    +    10.20.10 ### showOnStartup:= <<unfalse>>
    +    10.20.20 ### showChangeLog:= <<untrue>>
    +  ];
    +];
    +

    Look at that! As you can see, anyone can pick it up without even reading a manual! But if you do need one, make sure to read the full Specification 1.0.0open in new window

    As you might expect, PON already has full support in Pulsar!

    You can install the syntax highlighting for Pulsar via Language PONopen in new window on the Pulsar Package Registry.

    Plus, by using the JavaScript Module PONopen in new window we can convert any JSON data into a proper PON object, and read any PON object form the filesystem into a JavaScript Object.

    With the above module, we are happy to announce that the next release of Pulsar will automatically convert all configuration files it finds on your system (Not just Pulsar ones) into PON. We are hoping to do this system wide to help increase adoption of PON as a data structure.

    So stay tuned for further updates, and when to expect all system files to be converted!

    Happy stargazing!

    Important note: this is a joke. But if you do like PON, the resources above are fully functional and available to use. Happy April Fools everyone.

    + + + diff --git a/blog/20230418-Daeraxa-v1.104.0.html b/blog/20230418-Daeraxa-v1.104.0.html new file mode 100644 index 0000000000..2a5621ee16 --- /dev/null +++ b/blog/20230418-Daeraxa-v1.104.0.html @@ -0,0 +1,223 @@ + + + + + + + + Get Ready for another fantastically essential release, Pulsar 1.104.0 is now available! | + + + + + + +

    Get Ready for another fantastically essential release, Pulsar 1.104.0 is now available!

    DaeraxaApril 15, 2023
    • dev
    • release
    About 3 min

    Check out our newest Regular Release for Pulsar! Available Now!open in new window

    What is new in 1.104.0?

    This release contains many internal changes and upgrades, focusing on preparing the editor for much bigger changes to come.

    One technical change is that we have internally patched WebComponents (document.registerElement), which will potentially fix many of our community packages that rely on this (deprecated) functionality.

    We've also started our first migrations to WASM based packages (which is required for new NodeJS Versions). Any issues found here will be important to address, in order to ensure future updates go smoothly, and to unblock the road to compatibility with newer Electron versions. Please let us know if you find anything broken due to these updates!

    We also have updates such as improved whitespace in a certain PHP snippet, and bleeding edge HTML completions. And with more decaf work, we should be seeing some slightly faster startup speeds.

    Last but not least, we can expect to see this release being available on Chocolatey for Windows; Thank you HighHarmonics, il_mix, and COLAMAroro!

    But as always we want to say a huge thanks to all those that contribute and donate to Pulsar, making it possible for us to continually release these improvements. And we want to give a special thanks to the new faces we are seeing in this update, with some brand new contributors!

    As always we appreciate every single one of you, happy coding, and see you among the stars!

    • The Pulsar Team

    • The settings-view package now lists a package’s snippets more accurately
    • Fixed some issues with some packages with WebComponents v0 (tablr package
      should work now) by internalizing and patching document-register-element
    • Migrated away from node-oniguruma in favor of vscode-oniguruma (WASM
      version). This fixes issues with Electron 21
    • Ensured new WASM packages will work on Apple Silicon
    • Completions for HTML will now be as bleeding edge as possible.

    Pulsar

    second-mate

    autosave

    bracket-matcher

    timecop

    keybinding-resolver

    + + + diff --git a/blog/20230430-Daeraxa-Survey2.html b/blog/20230430-Daeraxa-Survey2.html new file mode 100644 index 0000000000..7a582b6e00 --- /dev/null +++ b/blog/20230430-Daeraxa-Survey2.html @@ -0,0 +1,223 @@ + + + + + + + + Survey: Project Feedback | + + + + + + +

    Survey: Project Feedback

    DaeraxaApril 30, 2023
    • survey
    • community
    • socials
    Less than 1 minute

    A short survey asking for community feedback on a couple of aspects of the project

    Due to the success of the first survey we have decided to use this format again to seek community opinion on a couple of elements of the project.

    In particular we are looking for feedback on some Pulsar UI changes, our documentation and publishing community packages.

    There is no need to sign into any account to respond to this survey and no personal data is being collected (emails, names etc.).

    You can find the Survey (Google Forms) hereopen in new window.

    + + + diff --git a/blog/20230501-Daeraxa-MayUpdate.html b/blog/20230501-Daeraxa-MayUpdate.html new file mode 100644 index 0000000000..59715dc7c2 --- /dev/null +++ b/blog/20230501-Daeraxa-MayUpdate.html @@ -0,0 +1,245 @@ + + + + + + + + Community Update | + + + + + + +

    Community Update

    DaeraxaMay 1, 2023
    • news
    • log
    • update
    About 6 min

    Is it a bird? Is it a plane? No it's the May community update!

    Welcome to the May Community Update

    Hello again and a warm welcome to our May community update! In store for you this time are Pulsar iconography updates, symbols-view improvements, CI upgrades and bunch of upgrades to our package backend and website.

    Just want to first mention that we have a new surveyopen in new window up, we would like some feedback from our community on a few aspects of the project so if you haven't answered this already then please have a look, every response is useful to us and we greatly appreciated.

    Now without further waffling, onto the updates!

    Octicons iconography update

    So this is a change we have in the worksopen in new window thanks to @confused-techieopen in new window, to upgrade Pulsar's iconography by updating the version of Octiconsopen in new window. The version being used currently in Pulsar is really old (v4.4.0 - from 2016!) and there have been many, many updates since then with a whole bunch of new icons and improvements (now on version v18.3.0).

    You can see examples of the new Octicons in the below image:

    One of the initial problems to work around was that Octicons no longer ships with font files, only the svg images. This meant that in order to use them they first had to be created as a font. This also meant modifying the font to make sure that the various codes aligned with the old ones so that they could, where possible, be a drop in replacement for the old version without breaking the naming scheme from upstream Octicons for use as reference material.

    We have a couple of different goals here. The main one is simply to give more options to ourselves and users in terms of icon variation so that we can make better choices for icon usage. We would of course retain the 4.4.0 set (just as 2.1.2 has also been retained).

    One of our others is a plan to hopefully modernise the UI, for example we have a bunch of new icons we can use for extending the set of file icons. You can see a couple of potential implementations in the below picture compared to the original. We actually have a survey up right now to gauge the community response to this so if you haven't already then please consider giving us your opinionopen in new window.

    There is still some work to go here, particularly on making sure that where icons are used we are still using the appropriate ones or selecting a more appropriate icon now it is available in the new set.

    If you want to read more details about what had to be done for the implementation then you can read the documentopen in new window that accompanies the PR that details what was done and how to maintain it in the future.

    Symbols view improvements

    Symbol-based navigation in Pulsar currently depends on ctagsopen in new window. It's versatile and can be configured to support lots of different source code files, but it reads from disk and doesn't work well with files that have been modified a lot since the last save. It also doesn't work at all on brand-new, unsaved files, or with languages that it hasn't been configured for.

    @savetheclocktoweropen in new window has been working on some improvements for this. For example, Tree-sitter parsers are quite good at this sort of code analysis, and can identify the important parts of a source code file with a simple query fileopen in new window.

    Language serversopen in new window are another potential source of symbol information. Some language servers are even capable of supplying project-wide symbol information; this would improve other symbol-view responsibilities, like the “Go to Declaration” command.

    So to make steps towards achieving this we would need to work on symbols-view to be more generic and then update Pulsar and its packages to take advantage of the changes.

    This has the potential to really upgrade the functionality and make navigating files and projects far more intuitive. It isn't ready just yet but don't worry, we will have more information to share on this feature in the coming weeks.

    CI Testing speed upgrade

    @DeedeeGopen in new window has made an updateopen in new window to our continuous integration configuration by caching and restoring the dependencies which ends up saving us a huge amount of time, possibly over an hour each run! (Pulsar has to build and test an awful lot of packages on each run so it is a long process). This should hopefully make life just that bit easier on the development side of things.

    Backend webhook

    @confused-techieopen in new window has been busy adding yet another new featureopen in new window to our package backend, this time it is a new webhook that allows us to publish messages and notifications when package authors publish and update their packages. You can check this out in action in our backend-notificationsopen in new window channel on Discord.

    Now some changes to our package websiteopen in new window. There are a few separate, but related, changes by @confused-techieopen in new window here that involve improving our package website to overhaul some of the issues we have with readme links.

    The first change adds shorthand string author assignment in the package.json file of a package. The npm package specificationopen in new window allows the three author properties to be combined into a single string e.g.

    {
    +  "author": "Barney Rubble <b@rubble.com> (http://barnyrubble.tumblr.com/)"
    +}
    +

    Previously if this was not populated fully with all three options then the author simply wouldn't display. This fixes that problem by allowing the optional fields to be omitted (actually this is taken a step further in our implementation - even name can be optional)

    In a similar vein we also now support shortcut syntaxopen in new window for the repository field of the package.json. For example writing authorName/repoName wasn't working so we have provided support for this.

    We noticed that many package authors would link to other community packages in their readmes in order to show other packages that work with or complement their package. Unfortunately as these point to the old atom.io website it means that every single link simply redirects to the sunsetting Atomopen in new window page. This is really not helpful as it entirely breaks the embedded links, sure there are workaround, people could look at the link and work out what packages it links to but this is a hassle when all you want to do is click a link.

    Therefore in order to solve this a feature has been added that takes these https://atom.io/packages/... links and transforms them to https://web.pulsar-edit.dev/packages/... so that the author's original intent for package linking is not lost. We did have a conversationopen in new window as to whether it was "right" to make these changes but we reasoned that:

    1. these links are all effectively "dead" so currently serve no useful purpose and
    2. they are already being redirected from the original link.

    We feel that this is an entirely benign change even though it does redirect from the author's originally intended link to another.

    Our next change also involves dead Atom links, in this case links to the Atom Flight Manual which has been taken down along with the rest of the atom.io sites. Again we are performing a redirection here but this time to the archived version of the website found on Internet Archiveopen in new window. This keeps the correct link handling and means that the documentation the package author was intending to link to is still available.

    Our last change is that relative links are now preserved against the repository of the package. For example ./LICENSE.md => https://github.com/pulsar-edit/package-frontend/LICENSE.md.

    These were some much needed changes which should hopefully improve the overall experience of using the website.

    Decoupling http handling for the package backend

    Lastly we have yet another @confused-techieopen in new window update in the works, this time improvements to the package backendopen in new window to make developing the backend more accessible to new contributors as well making tasks such as testing easier.

    Currently the handling for HTTP and logic is all mixed together in the backend which means that nearly every function within the backend has a signature like:

    async function getPackages(res, req) {...}
    +

    Where res is the response object as provided by ExpressJS and req is the request object provided by ExpressJS.

    This also means when a function from the backend needs to return, it'll have a signature like:

    // On Success
    +res.status(200).json(data);
    +logger.httpLog(req, res);
    +
    +// On Failure
    +await common.handleError(req, res, err);
    +return;
    +

    Which has a few downsides; when testing a function to the backend a fake Response and Request object must be made that also matches the ones provided by ExpressJS and those objects have to be monitored for the functions behavior, or otherwise the API has to be tested as just that, an API by calling the ExpressJS module itself then checking the api returns for behavior.

    This also means that any new developers have to be familiar with ExpressJS to contribute and each function has to know the exact HTTP Status codes that have to be returned and in some circumstances concern themselves with header fields to set etc.

    The planned changes are to allow each function to return just a serverStatusObject (which is used frequently within the backend) so we change that same signature from above to:

    // On Success
    +return {
    +	ok: true,
    +	content: data,
    +};
    +
    +// On Failure
    +return {
    +	ok: false,
    +	content: err,
    +};
    +

    This has the upside of letting only the code that retrieves API requests deal with responding to them. The rest of the backend can work with just normal functions, without a care in the world about HTTP handling. This change lowers the amount of knowledge needed to contribute, so we hope that more people might be encouraged to get involved. As a bonus, it makes the code much easier to test.


    And that just about wraps things up for this month's updates. As ever if you want to get more involved the community then feel free to join in on our various social channelsopen in new window. Hope to see you again this time next month!

    + + + diff --git a/blog/20230516-Daeraxa-v1.105.0.html b/blog/20230516-Daeraxa-v1.105.0.html new file mode 100644 index 0000000000..f1a3b460cd --- /dev/null +++ b/blog/20230516-Daeraxa-v1.105.0.html @@ -0,0 +1,223 @@ + + + + + + + + Welcome to a rad new release, Pulsar 1.105.0 is available now! | + + + + + + +

    Welcome to a rad new release, Pulsar 1.105.0 is available now!

    DaeraxaMay 15, 2023
    • dev
    • release
    About 3 min

    Welcome to a rad new release, Pulsar 1.105.0 is available now!open in new window

    What is new in 1.105.0?

    This newest release of Pulsar contains many changes, from new features, to security patches, to testing improvements, this release has it all, and we hope it shows!

    A big milestone for this release, is since our initial fork from Atom, we have now created fully green (passing) tests! This hopefully means we can iterate new changes, both fixes and new features, faster than ever before!

    Also on this release we ensured to put a focus on solving the issues reported by our wonderful community such as resolving an issue that would prevent installation of Pulsar on RPM systems, fixing localization issues in settings-view when browsing packages, or addressing memory leak issues within second-mate.

    Even better this release also comes with some brand new features and improvements to existing ones! Such as the addition of new community package activation hooks, adding a new bookmarks service to the ServiceHub, and updating the internal typescript used to support advancements within that language for community packages to take advantage of.

    But this summary can't possibly include everything, make sure to take a look through the changelog below to see everything this release has to offer!

    As always, we want to say a huge thanks to everyone that contributes to Pulsar, from the much appreciated donations, the critical issue reports, or amazing pull requests we receive. We appreciate every single one of you, and couldn't do any of this without you!

    Till next time, happy coding, and see you among the stars!

    • The Pulsar Team

    • Rebranded notifications, using our backend to find new versions of package, and our github repository to find issues on Pulsar. Also fixed the "view issue" and "create issue" buttons that were not working
    • Bumped to latest version of second-mate, fixing a memory usage issue in vscode-oniguruma
    • Removed a cache for native modules - fix bugs where an user rebuilds a native module outside of Pulsar, but Pulsar refuses to load anyway
    • Removed nslog dependency
    • Fixed an error where the GitHub package tried to interact with a diff view after it was closed
    • Fixed RPM installation failure when Atom was installed on the same machine
    • Added a new set of Package activationHooks, ...:uri-opened lets a package activate when any URI is opened within Pulsar, and ...:file-name-opened lets a package activate when any specific filename is opened within Pulsar.

    Pulsar

    notifications

    + + + diff --git a/blog/20230525-Daeraxa-Survey2-Results.html b/blog/20230525-Daeraxa-Survey2-Results.html new file mode 100644 index 0000000000..bf3df334ce --- /dev/null +++ b/blog/20230525-Daeraxa-Survey2-Results.html @@ -0,0 +1,223 @@ + + + + + + + + Survey Results: Project feedback | + + + + + + +

    Survey Results: Project feedback

    DaeraxaMay 25, 2023
    • survey
    • community
    • feedback
    About 5 min

    Here we have the results and comments of our second community survey!

    Thanks to everyone who took part in this survey, we got some great responses, again showing how effective this style of community engagement can be.

    The reason for this survey was to gauge some early feedback on a bunch of changes we are looking at making in various areas of the project. We really need to make sure that it is worth making these changes, the last thing we want is to make changes that nobody actually wants that end up making things worse.

    Honestly there isn't much more to say as there wasn't really an overarching theme for this one like there was with our first surveyopen in new window so let's just get on with it shall we?

    Iconography

    Our first question was asking about upgrading our icons in Pulsar. We are updating our Octiconsopen in new window version bundled with Pulsar to take advantage of changes made between v4.4.0 from 2016 to the current v18.3.0. As part of this we want to explore actually using these new icons in our default UI theme. The questions below are voting on preference between two styles of the new icons against the current theme:

    survey2-icons

    We asked which one was preferred out of these three mock-ups and for any additional comments people would like to make on it.

    survey2-q1

    The outcome of this is fairly clear in that there is a clear preference for the filled icons. This is something we still need to work on (the pictures were only mockups) so it probably won't be in the next release but this gives a us a good indication of the direction we should go with this.

    Comments

    There are a few comments here I've picked out to make comment on:

    Wish the icons would be like VSCode icons extension, which react to something if you name a file a certain word, like (images, root, etc) or language images like put the language images in the file, idk how to explain

    As long as is isn't the outline one I'll be happy. Side note, it would be cool if the file icons showed the kind of file.

    Wondering why executable files (shell scripts) look the same as plain text files

    These all seem to be on the concept of something like file-iconsopen in new window. The Octicons font simply doesn't have support for every file icon (although it does add some extra ones we would like to include such as icons to indicate binary files or specific types of config file) so we won't be able to add this functionality in this update. The good news is that file icons can be added via a number of community packagesopen in new window.

    Both new versions are fine, so if possible in the future, make a setting where you can choose?

    We probably won't ship a theme for both but the outline icons will be included in the font so it would simply be a case of creating a themeopen in new window to use the outlined ones instead of the default. All the icons will be shown in the inbuilt style guide Styleguide: Show so you can easily pick and choose what ones you want. We may however look at creating a default "legacy" theme that uses the original Octicon icons as they are in Pulsar right now.

    Documentation Site Upgrade

    We have plans to overhaul our documentation (both the site/framework and the structure of the documentation). For this we were just after some general feedback on what people like or dislike about the current docs and how it compares to other project documentation.

    Whilst I can't address every comment individually, I've picked out a few to address as they are either a common question or a great point that I think needs to be addressed:

    API Docs

    api chapter needed

    Whenever I've tried to use the documentation it seems to be mostly TODO's. So, I'd say that getting the content back to where it was under Atom is more important than fine tuning layout and structure (especially the API documentation).

    The API, that's the most important part of the Atom project, is not accessible. I would love to have an accessible version soon, and one that is fully searchable, possibly by showing the classes and methods as more relevant and a "full text search" also present

    So the API stuff has been a little more challenging in terms of the documentation migration from the flight manual. It wasn't actually part of the flight manual but was generated from the Atom team's own API tooling which output in HTML. There were so many discussions about potential API changes and migrating to a more used/supported API docs generation tool (JSDocs) that this got left behind in fear of spending a lot of time getting it ready only to have no good way to automatically update it.
    The good news is that the JSDoc work is well on its way so we should be able to get this added before too long.

    Layout

    Having everything as one huge page is a bit much. Maybe make the subchapters individual pages?

    I think everything in Launch Manual should be moved one level up

    As far as I can see this Launch Manual navigation level is not needed and would clean things up and make it more obvious where things are located by removing this level. At this time I'm not certain what else to provide feedback on.

    We agree with these points and this is one of the major areas that is spurring on this rework. We don't want to do anything drastic but we do plan to make navigation a little better and less "monolithic".

    Videos

    Video Tutorials

    I won't rule anything out but definitely check out HTML Tim's YouTube Channelopen in new window. He has a ton of fantastic videos on Pulsar itself, how to get the best out of it for different types of languages and info on community packages.

    We also had a bunch of comments saying they liked the current docs so we also need to take that into account rather than just going crazy and overhauling everything.

    Community package publishing

    Currently, in order to publish a package to the Pulsar package registry it is a requirement to make your package available in a GitHub repository. We were after info to know if this is something that is stopping any of our community from publishing packages.

    survey2-q2

    An interesting almost 50:50 split here, it makes us happy to see so many people are interested in creating packages so we will definitely take that into consideration with our documentation and processes in order to make the publishing and maintenance aspect as good as possible. I can see us asking more questions on the package development experience in the future!

    survey2-q3

    So this was quite enlightening. It is good to know that most people aren't being put off by the current requirement but it is also great to find out that our plans to support other forges or services are in demand. This was very much a question designed to find out if we were a) stopping people creating packages and b) making good use of our resources by looking to expand to other platforms.

    Summary

    So thank you again to everyone who took part in this survey, it is fantastic to get so much community engagement on the project. Not only does it help us make informed decisions but getting comments from people clearly passionate and interested in the project is fantastic. It can be easy to get caught in a echo chamber so it is really important to get input from people who may not be as present on our various social platforms but still want to have their voice heard.

    As always you can find all the raw data (all checked for any personal or identifiable data) from the survey on our organization repoopen in new window.

    I don't know when we will have a new survey or what the topic will be but you can be fairly sure there will be one!

    + + + diff --git a/blog/20230601-Daeraxa-JuneUpdate.html b/blog/20230601-Daeraxa-JuneUpdate.html new file mode 100644 index 0000000000..ece431ffe8 --- /dev/null +++ b/blog/20230601-Daeraxa-JuneUpdate.html @@ -0,0 +1,223 @@ + + + + + + + + Community Update | + + + + + + +

    Community Update

    DaeraxaJune 1, 2023
    • news
    • log
    • update
    About 8 min

    What's this? No... it can't be. But it is! It's the newest instalment of the Pulsar Community Update!

    Welcome to the June Community Update

    Before we start I just want to I just want to say another big thank you for everyone who took the time to provide feedback in our recent survey for which you can find the results of in a recent blog postopen in new window. We got a lot of useful information again so if you haven't already then you can see our analysis of the results and answers to some of the questions in the previous link.

    We have some really exciting news in this month's edition, some automatic detective work on our backend to make life easier for everyone, updates to our GitHub Discussionsopen in new window a community spotlight on one of our most prolific package maintainers and the long await and much hyped arrival of the modernised Tree-sitter implementation!

    Enough waffling, lets get on with it!

    Community package feature detection

    @confused-techieopen in new window has been busy developing a new feature to our Pulsar Package Repository backend to add "Feature Detection". What does this mean? Well in this case feature detection refers to the analysis of community packages as and when they are published to the repository in order to collect information on what features the package can provide. For example it can determine if the package is providing snippetsopen in new window or a language grammar and, most excitingly, what languages and file extensions that grammar is able to support.

    With this we are able to store and use information not explicitly written into the package metadata but actually in the source code of the package itself. This opens a load of possibilities in both Pulsar itself and the package website. For example you could have a filter that explicitly searches only for packages able to provide snippets or all packages that are able to provide a grammar for a particular language to the editor without just returning packages that contain the string you are looking for without the features you want.

    However one of the biggest benefits is being able to use this information in Pulsar. A fairly common task when one first installs Pulsar (or any text editor) is getting it set up for the languages you use. Now, whilst Pulsar does support a large number of common grammars out of the box it can't support everything.
    So in the below example I open up a PONopen in new window (.pon) file and detail the steps needed to get it to display properly.

    • Open my config.pon file for editing
      • In this case Pulsar doesn't have a grammar for PON and .pon files so shows the language as the default Plain Text language
    • Open the package install pane and search for "PON"
    • Search through the list of returned resultsopen in new window until I find language-pon which looks like it might do what I want it to do and click Install.

    This looks like a simple process but just look at how many results came back from searching for "PON". This shows the downside of just looking for packages via simple search terms, you have to trust that:

    1. A language package actually exists for your language

    and

    1. The package is sensibly named and actually has your search keywords in it (for example searching for "Dockerfile" returns thisopen in new window but the package we might actually want to add dockerfile support is named language-docker which is noticably absent).

    This new feature would allow us to avoid such a situation altogether. As we are now able to determine what packages can support a given language or file extension we can instead use a much more helpful process:

    • Open my config.pon file for editing
    • Pulsar doesn't have a package for this but it can use the info it has to search the package database, find a package that supports .pon files and displays a prompt to the user to download and install from the list of packages it has found.

    This approach takes the guesswork out of it completely and will only show you packages that state they have support for that filetype.

    Remember this is only one example, we can extend this same behaviour to snippets and potentially other features in the future which we hope will greatly reduce the amount of guesswork and manual searching that has to be performed.

    Our biggest challenge here is that we have thousands of existing packages that all need to have this analysis run on them so this won't necessarily be a quick process as we battle against GitHub's API limits but we want to make sure we have all this data ready before we go live with this feature. For new packages this action will be performed at time of publishing/update so this is a one time only thing.

    We feel this is a really exciting addition so stay tuned for more info as an when we start implementing this in various places.

    Tree-sitter updates are live!

    [1]

    If you have been following these monthly updates then you know that tree-sitteropen in new window has been a common topic and one of our biggest challenges in our goals to bring Pulsar up to date.

    So thanks to an awful lot of work by @savetheclocktoweropen in new window and @maurício szaboopen in new window this project that we have been working on for months has finally hit the rolling releases in an experimental form: a modernization of the Tree-sitter grammarsopen in new window.

    Atom was the first editor to integrate with Tree-sitteropen in new window — a system designed to deliver fast and accurate parsing of a number of programming languagesopen in new window for better syntax highlighting than the traditional TextMate-style grammars.

    But being the first editor to support something new is a double-edged sword; in this case it revealed gaps in Tree-sitter functionality that were addressed in later versionsopen in new window. However, Atom’s Tree-sitter support never evolved to match, and that’s the situation that Pulsar inherited.

    Now our hand has been forced: security changes in Electron mean that the existing system needs to be converted from the Node bindingsopen in new window to the web-tree-sitter bindingsopen in new window. We have therefore treated this as an opportunity to overhaul the entire Tree-sitter system and bring it to feature parity with other modern editors that are using Tree-sitter.

    To start using the modern Tree-sitter implementation, first download a rolling releaseopen in new window. Then find the Use Modern Tree-Sitter Implementation option in the Core settings pane and enable it. Any languages with built-in Tree-sitter grammars will now use the modern system.

    With the new system enabled, here are a few things you might notice:

    • In general, syntax highlighting should be more accurate, and more consistent from language to language.
    • Automatic indenting of code now uses a more nuanced system that allows for smarter contextual judgements. For instance, if you type return foo && in a JavaScript file and then hit Enter, a grammar could be configured to know to add a “hanging” indent on the next line, because it knows that a valid statement in JS can’t end with &&. Or a closing brace (}) could automatically adjust itself to match the indent level of the line with its corresponding opening brace ({), instead of just naïvely unindenting by one level.
    • Code folding now uses a new system that collapse meaningful units of a document instead of trying to infer those units based on indentation.
    • Commands that rely on Tree-sitter features — like Editor: Select Larger/Smaller Syntax Node — should work just like they did with the old Tree-sitter implementation. They’re a useful way to select meaningful sections of your document — for instance, put your cursor inside of a string, press Alt + Up (or Option + Up), and the entire inside of the string is selected.

    And here are some other things you wouldn’t notice just as a user, but would enjoy the benefits of if you contributed to the Pulsar package ecosystem:

    • The system for mapping Tree-sitter semantics to syntax highlighting is much more robust, which means that grammar authors now have a number of extra tools at their disposal.
    • Likewise, the systems for describing code folding and indentation will be much more user-friendly than the corresponding systems for TextMate-style grammars. Instead of having to write long, confusing regular expressions, grammar authors will often just be able to describe these behaviours in just a few lines of query statementsopen in new window.
    • The results of a Tree-sitter parse are useful beyond the built-in editor duties of highlighting, indenting, and folding. For instance, a Pulsar package could use Tree-sitter to find logical sections of a document, or to associate an identifier with the line where it was defined (in fact, we’re planning to use some of this information to improve the symbols-view package and make it more flexible).

    With such a large overhaul, there are bound to be bugs. If you’re using the new system and things look or feel worse in any way, I can almost guarantee that it’s not intentional, so please tell us about it on Discordopen in new window or file an issueopen in new window. If you’re unsure whether it’s the fault of the new Tree-sitter system, try comparing the behaviour with and without the “Use Modern Tree-Sitter Implementation” option enabled.

    GitHub Discussions reorganisation

    We just had a revamp of our GitHub Discussionsopen in new window which follows a feature that GitHub finally added to allow organisation via sections. This means that we have been able to better separate the various categories into more thematic groups. Hopefully this shows that we do care about and maintain the Discussions area just as much as our other social channels, we don't want people to avoid this area just because it isn't currently as active (chicken or the egg comes to mind here - it won't become more active if people don't use it.). So please, if you want to have a more "long form" discussion or you want a little more of an "asynchronous" type of engagement then Discussions is perfect for that.

    So feel free to comment on this organisation work (positive or negative - we won't be offended) and let us know if there is anything else we could be doing to improve it.

    Community spotlight

    And finally we want to give a huge shout out this month to @asiloisad (aka @bacadraopen in new window) who has been creating and maintaining a ton of packages for Pulsar with constant updates — making them by far our most prolific package author and maintainer.

    To give you an idea of the scale we pulled a few stats from the backend and found that we are looking at 273 updates over 15 packages (bear in mind that this is only packages published or updated on the Pulsar Package Repository and doesn't include those that haven't been updated since migration from Atom).

    Some of the packages I find particularly interesting and useful are:

    • project-filesopen in new window - a package for providing better management and navigation of projects within Pulsar including navigation of the project list, recent projects and even adds autocompletion of path names.
    • navigation-panelopen in new window - this provides an awful lot of stuff that is quite difficult to explain in only a few words but essentially it allows you to add custom symbols to your files

    There are tons more packages, have a look at their GitHub reposopen in new window for more.

    This is exactly the kind of thing we were hoping to achieve by continuing this project. We know the atom shutdown did a lot of damage to people's confidence in the editor and many dismissed it entirely, archived all their packages and moved on when they saw the sunsetting notice. I really hope that we get more people who are just as enthusiastic about publishing packages and just generally about getting involved in the project. So thank you again @asiloisad for all the work you have been putting into this and for putting your faith into this project.


    And that just about wraps things up for this month's updates. As ever if you want to get more involved the community then feel free to join in on our various social channelsopen in new window. Hope to see you again this time next month!


    1. Image from https://tree-sitter.github.io/tree-sitter/open in new window - Copyright (c) 2018-2021 Max Brunsfeld ↩︎

    + + + diff --git a/blog/20230610-Daeraxa-2kStars.html b/blog/20230610-Daeraxa-2kStars.html new file mode 100644 index 0000000000..22f01f1914 --- /dev/null +++ b/blog/20230610-Daeraxa-2kStars.html @@ -0,0 +1,223 @@ + + + + + + + + 2k Stars! | + + + + + + +

    2k Stars!

    DaeraxaJune 10, 2023
    • news
    • github
    • stars
    Less than 1 minute

    We just hit 2000 stars on GitHub!

    This is just a quick blog post to say thank you to everyone in our wonderful community who have allowed us to get this far. I know it is "just" a number but 2000 stars shows us just how many people believe in our project.

    It is hard to believe this project is almost nearing a year old. The pulsar-edit/pulsaropen in new window repository was created on the 12th of July 2022 and in that time we have already done so much and we look forward to doing so much more.

    Here is a chart generated on star-history.comopen in new window showing our stars over time.

    star-chart

    So again, a huge thank you to everyone, particularly our wonderful community and our generous donors who make this project possible.

    + + + diff --git a/blog/20230616-Daeraxa-v1.106.0.html b/blog/20230616-Daeraxa-v1.106.0.html new file mode 100644 index 0000000000..6281cb9eff --- /dev/null +++ b/blog/20230616-Daeraxa-v1.106.0.html @@ -0,0 +1,223 @@ + + + + + + + + Pulsar v1.106.0: A Focus on Grammars | + + + + + + +

    Pulsar v1.106.0: A Focus on Grammars

    DaeraxaJune 15, 2023
    • dev
    • release
    About 4 min

    Welcome to our latest release, Pulsar 1.106.0 is available now!open in new window

    What is new in 1.106.0?

    We have a particularly exciting release for you because this is our first regular release that adds a new feature that we have been hard at work on but more on that later. Of course we still have our usual mix of updates and upgrades such as a whole host of improvements to our Clojure language support and a number of annoying bugs that have been firmly splatted.

    As alluded to in the title our biggest update we have in store is our experimental modern Tree-sitter implementation. This is a really important feature for us as it allows us to move to a modern and actively developed implementation of Tree-sitteropen in new window as well as allowing us to remove one of our obstacles in our quest to get onto modern versions of Electron. To be honest this is a huge topic in its own right, so if you want to read more about it then you can have a look at our previous blog postopen in new window which goes into a lot more detail about this change. For now we have this under an experimental "opt-in" setting so to enable it you will need to go into your Settings and look for Use Modern Tree-Sitter Implementation in your Core settings in order to enable it. In short, if you enable it then you should see more accurate and consistent syntax highlighting, improved automatic indentation and better code folding. As ever we are keen for feedback on this feature so, once enabled, if you notice anything "off" or have any other comments or feedback then please let us know on Discordopen in new window or file an issueopen in new window.

    Onto our Clojure language updates, lots of this actually ties directly into the new Tree-sitter implementation as Clojure is now supported as a Tree-sitter grammar which means a whole host of new features have been added that are specific to this new implementation. We now properly support block comments, quotes and a bunch of other advanced features. Basically there has never been a better time to pick up learning Clojure in Pulsar!

    Don't let these big updates make you assume we aren't also thinking about some smaller scale things, we have a tiny quality of life update to our GitHub package which adds a Log Out option to the package options (Packages > GitHub > Log Out) (previously only available in a somewhat obscure command - github:logout) - just beware that this will remove your token from your system entirely so you will either need to re-enter it or create a new one to log back in.

    And of course to finish up we have some annoying bugs which we have now been squashed, for example an issue that prevented images opening correctly (an issue we apparently inherited from Atom) and, whilst not a bug, a fix to solve a less than ideal situation with our new CSS autocomplete implementation to sort the suggestions in a more expected fashion. We have to thank our community for these as these last couple of items as they were brought to our attention (and in one case fixed) by some of our community members.

    So that just about wraps it up for another release. As ever a huge thank you to our wonderful community and donors who make this entire project possible.

    Till next time, happy coding, and see you among the stars!

    • The Pulsar Team
    A quick note about our missing ARM Linux Binaries Sorry, there are no ARM Linux binaries at time of initial release, due to what we suspect is an issue at our CI provider. Hopefully this will be resolved soon and we can upload some ARM Linux binaries for this release! Thanks for your patience.

    Changelog

    • Fixed bug that happens on some systems when trying to launch Pulsar using the Cinnamon desktop environment
    • Added a modern implementation of Tree-sitter grammars behind an experimental flag. Enable the “Use Modern Tree-Sitter Implementation” in the Core settings to try it out
    • Bugfix: fixed Clojure indentation on tree-sitter
    • Improved the Clojure language support by migrating it to tree-sitter and support block comments, quoting, and other advanced features on modern tree-sitter implementation
    • Fixed a bug that could cause images to not appear the first time opening them
    • autocomplete-css Completions are now sorted in a way that may match what users expect
    • Added a "Log Out" menu item for the github package

    Pulsar

    github

    + + + diff --git a/blog/20230701-Daeraxa-JulyUpdate.html b/blog/20230701-Daeraxa-JulyUpdate.html new file mode 100644 index 0000000000..551a3d5f07 --- /dev/null +++ b/blog/20230701-Daeraxa-JulyUpdate.html @@ -0,0 +1,223 @@ + + + + + + + + Community Update | + + + + + + +

    Community Update

    DaeraxaJuly 1, 2023
    • news
    • log
    • update
    About 6 min

    What has it got in its pocketses? It's the July community update!

    Welcome to the July Community Update

    This month's update is a little different in tone to some of the others. Recently, we’ve had to deal with some problems outside of our control which is why our first few items are a little more "serious" and a bit less optimistic than our usual updates. But don’t despair — all the good news comes in the second half!

    The "bad news" section covers our CI not building certain binaries, uncertainty about our subreddit's future, and some false antivirus positives on a Pulsar dependency, but the "good news" section cleanses the palate with a new method of downloading Pulsar as well as a bunch of improvements to the application!

    So with all that covered let's get on with it!

    ARM builds problem

    Some of you may have noticed that we had a bit of a problem recently in producing binaries for ARM on Linux which, for the most part shouldn't have affected anyone except ARM Linux users, had a knock-on effect with some of the community-maintained packages. Some of these packages (and associated package managers) may have seen a delay receiving an update to 1.106.0.

    This wasn't anything to do with our code or builds but was instead a failure of the CI machine used to produce those builds failing to properly connect to various services.

    This has now been resolved, so if you were waiting for an ARM binary you can now download it on our downloadsopen in new window page as normal. Community maintained packages should now all be up-to-date as well.

    Subreddit closure/reopening and possible Lemmy community

    [1]

    You may or may not be aware that we decided to close our subredditopen in new window as part of the protest against the Reddit API changes. The subreddit remained closed after the original 48 hours for various reasons.

    We recently decided to re-open the subredditopen in new window for a number of reasons, some because we don't want to abandon a portion of our community entirely and partly because of threats from Reddit themselves.

    The reason I bring this up here is because it is already possible that some people who were using Reddit for Pulsar news have now left it and won't have seen the above post. This post also contains a poll to get feedback from the affected community to see what our next steps would be. Should we close the subreddit and abandon it? Should we open a Lemmy community instead? Should we do both?

    So if you are interested in using either Reddit or Lemmy, please have a look at the above post and vote in the poll.

    Antivirus es5-ext issue

    Some users have been reporting that various antivirus applications and virus scanning tools have been identifying Pulsar as a virus due to the es5-ext package.

    This is is nothing to worry about — Pulsar is not a virus, nor do we use dependencies that are.

    After some investigation, we discovered that somewhere in our dependency tree (i.e., a package dependency of a package we use) exists this es5-ext package. Early last year, this package was updated to display a "message of peace" to Russians (in reference to the ongoing conflict). This message displays to anyone within a Russian time zone, and, whilst the "payload" of a simple message is harmless, the act of targeting based on time zone has caused this package to be flagged by various scanners. Unfortunately the package author does not seem to want to remove it.

    So we have been working on a way to get around this. @confused-techieopen in new window, with @savetheclocktoweropen in new window's help, has managed to pin this to an earlier versionopen in new window using our own fork of this package.

    In our experiments, binaries built in this manner are no longer being flagged by any notable antivirus program. Hopefully this gives our users peace of mind.

    Pulsar available on deb-get

    [2]

    We want to put Pulsar on all major package managers, but it can be trickier than it looks. Various package managers have special requirements and restrictions, so it behoves us to move slowly and make sure we understand those constraints.

    That said: Pulsar is now officially supported on deb-getopen in new window, a package manager for Debian (and Debian based distributions such as Ubuntu). Amongst package managers, deb-get is straightforward for us to support; all it does is grab a pre-built .deb binary from our GitHub releases and install it as any other .deb. You can see exactly how straightforward by looking at the script that grabs the latest version of Pulsaropen in new window.

    It's simple from the user's perspective, too. Once you have installed deb-get, all you have to do to install Pulsar is:

    • deb-get install pulsar

    To update Pulsar (and other deb-get packages, all you need to do is:

    • deb-get update
    • deb-get upgrade

    So if you are on a Debian based distro and want a quick and easy way to install and update Pulsar, give it a try!

    Tree-sitter migration of TOML grammar

    [3]

    In our last regular releaseopen in new window, we made a big deal about our modern Tree-sitter migration and the new grammars that come with it.

    This work is still very much ongoing and we have new grammars being migrated all the time. Our latest is the TOML Tree-sitter grammaropen in new window that @savetheclocktoweropen in new window has added. It's got all the benefits of a Tree-sitter grammar: more consistent highlighting, better performance, and support for features that can't be delivered with a TextMate-style grammar.

    You can try this out in our rolling releaseopen in new window or wait until the next regular one. Either way you will need to tick the Use Modern Tree-Sitter Implementation option in your Core settings in order to enable it.

    As ever we are after feedback, so if you use this new TOML Tree-sitter grammar (or any other for that matter) and have any issues or see any inconsistencies compared to the existing grammar then please let us know on Discordopen in new window or file an issueopen in new window.

    less-cache package update

    [4]

    This is still in the works but @confused-techieopen in new window has been making some updates to be added in the near future that bumps the version of less-cache that we are using.

    less-cache is a module that handles turning all Pulsar's (and our community's) packages written in lessopen in new window into valid CSS, as well as ensuring each one is able to import the values from Pulsar that it cares about. For the past three years less-cache has been using less@3.12.2 which, while fine, has been missing out on some of the big new changes in less after version 4 was released. So this PRopen in new window bumps it to less-cache@2.0.0 in order to bump the version of less to 4.1.3. This change gives us some exciting new features, but it also introduces a breaking change to existing less stylesheets:

    • Parens-division now required around math expressions that use division

    See less.js v4.0.0 -> v4.1.3 change logsopen in new window for more info.

    The latter item has more impact: because / is used in newer CSS features like grids, parentheses are needed when doing division to remove ambiguity about the function of / on a particular line. Lots of usages in Pulsar core needed to be updated as a result of this, so it's quite possible that third-party packages are affected as well.

    We're brainstorming ways that we can detect these usages and minimise the impact on these packages, but if you've got a community package that you're maintaining that uses less stylesheets, please take a moment to see if it's affected. At times, an error may be shown informing the user that the stylesheet failed to be compiled; in other cases, it won't be reported and instead can result in a broken UI.

    The upside of this upgrade is that we can take advantage of new less features and keep our implementation up to date.


    And just like that, we are done with yet another month's updates. As ever, if you want to get more involved in the community, please join in on our various social channelsopen in new window. Hope to see you again this time next month!


    1. Image from https://github.com/LemmyNet/lemmy-uiopen in new window - Copyright LemmyNet ↩︎

    2. Image from https://github.com/wimpysworld/deb-getopen in new window - Copyright (c) 2022 Wimpy's World ↩︎

    3. Image from https://github.com/toml-lang/tomlopen in new window - Copyright (c) Tom Preston-Werner ↩︎

    4. Image from https://github.com/less/less.github.ioopen in new window - Copyright (c) 2013 Alexis Sellier, Less.js, contributors. ↩︎

    + + + diff --git a/blog/20230715-DeeDeeG-v1.107.0.html b/blog/20230715-DeeDeeG-v1.107.0.html new file mode 100644 index 0000000000..da0b0ddf23 --- /dev/null +++ b/blog/20230715-DeeDeeG-v1.107.0.html @@ -0,0 +1,223 @@ + + + + + + + + Fresh off the CI press: Pulsar 1.107.0 is available now! | + + + + + + +

    Fresh off the CI press: Pulsar 1.107.0 is available now!

    DaeraxaJuly 15, 2023
    • dev
    • release
    About 4 min

    Fresh off the CI press: Pulsar 1.107.0 is available now!open in new window

    What is new in 1.107.0?

    Another month and another Pulsar release! This month features a bunch of those all important quality-of-life updates. We have a whole host of bugfixes and upgrades for you along with a sprinkling of new features.

    To kick things off we have now resolved an issue which has been hanging around since we began this project. As we no longer had the same method of installing the software as Atom, we created an issue for Windows users where the pulsar and ppm were not being added to PATH. The upshot of this was some rather important Pulsar features were no longer working by default on Windows - namely the ability to launch pulsar by just running pulsar in run or cmd/powershell (you can find more in our launch manualopen in new window on how you can use this to open Pulsar directly to a particular file, line and even column). You can also run pulsar --help to display all the available options.

    Please note that whilst we have this working now for the most part, the -p/--package option as well as ppm still needs some more development work. However until this has been completed you can still access Pulsar package management from the command line on Windows by simply using apm (what ppm is still named underneath) in place of those commands.

    Next we have an update to our less-cache package which bumps the version of lessopen in new window used by Pulsar from 3.12.2 to 4.1.3 which adds some new functionality but also introduces a breaking change regarding using division in math expressions. The good news is that Pulsar will automatically attempt to fix those issues during load. You can read more about this in our recent community update blog postopen in new window on this subject.

    To continue the trend of new developments with the modern Tree-sitter implementationopen in new window, we now have a new grammar for TOML files as well as a couple of fixes to some issues that have been discovered with the new implementation.

    Another nice feature recently added is a new API endpoint - atom.versionSatisfies(). No longer do package maintainers (or any other Pulsar hacker) need to write their own way of checking for Pulsar versions, we now have an inbuilt way of doing it which will hopefully alleviate some of the problems we have seen with some packages using (let's just say) "less-than-ideal" methods of version checking. You can read more about it on the PRopen in new window.

    And lastly, in case you missed it from the community update, we have resolved an issue due to a downstream dependency that was causing Pulsar to get falsely flagged by virus checkers. You can read more about this from the update postopen in new window.

    And that brings us to a close for the 1.107.0 release notes. As ever a huge thank you to our wonderful donors and community members who not only make this project possible but worthwhile.

    Until next time, happy coding and see you amongst the stars!

    • The Pulsar Team

    Changelog

    • Fixed a number of issues with the experimental modern Tree-sitter grammar mode
    • Pulsar can now be added to the PATH on Windows, via the "System" pane within Settings View.
    • Bumped less-cache to v2.0.0 which uses less@4.1.3. This adds many new features of Less, while causing breaking changes to existing Less StyleSheets. Read more about these changes hereopen in new window. Pulsar will attempt to automatically repair any breaking changes in any package style sheets, while emitting deprecations.
    • Fixed a bug that would render files unable to be clicked with sticky headers enabled on One-Dark and One-Light themes.
    • Added a Modern Tree-Sitter TOML Grammar.
    • Added a new API endpoint within Pulsar of atom.versionSatisifes() to allow packages to safely check the version of Pulsar, instead of having to do so themselves.
    • An issue in a downstream dependency has been resolved that improperly flagged Pulsar as malicious.

    Pulsar

    less-cache

    + + + diff --git a/blog/20230716-Daeraxa-v1.107.1.html b/blog/20230716-Daeraxa-v1.107.1.html new file mode 100644 index 0000000000..2f730bfcc7 --- /dev/null +++ b/blog/20230716-Daeraxa-v1.107.1.html @@ -0,0 +1,223 @@ + + + + + + + + Hotfix: Pulsar v1.107.1 | + + + + + + +

    Hotfix: Pulsar v1.107.1

    DaeraxaJuly 16, 2023
    • dev
    • release
    Less than 1 minute

    Hotfix: Pulsar 1.107.1 is available now!open in new window

    What is new in 1.107.1?

    Hotfix for important cosmetic issues in the bundled github package. This package missed updates to be compatible with the breaking changes in Less Style Sheet syntax, and needed to be updated to preserve the intended styling of the GitHub and Git panes.

    Includes this PR: #639open in new window to fix this issue: #638open in new window

    See the v1.107.0open in new window release for all the other changes since v1.106.0.

    + + + diff --git a/blog/20230801-Daeraxa-AugustUpdate.html b/blog/20230801-Daeraxa-AugustUpdate.html new file mode 100644 index 0000000000..0875486bb6 --- /dev/null +++ b/blog/20230801-Daeraxa-AugustUpdate.html @@ -0,0 +1,225 @@ + + + + + + + + Community Update | + + + + + + +

    Community Update

    DaeraxaAugust 1, 2023
    • news
    • log
    • update
    About 5 min

    How many roads must a man walk down? Not a clue, but what I do know is that this is the one and only Pulsar Community Update!

    Welcome to the August Community Update

    As always, a big, warm welcome to this month's update! This time around we have some interesting new features, some of which have been in the works for a while, so it is great to be able to report on their state as they will soon be making their way to a Pulsar near you! These updates include news on a new "pulsar-updater" package designed to help you keep up to date with our latest releases, a big new feature for one of our core packages for working with Markdown documents and bringing some of our dependencies up to date so you no longer have to keep around old toolchains.

    So lets get on with it!

    Update to markdown-preview language identifiers

    @confused-techieopen in new window has been working on a significant update to the way the markdown-preview package handles the highlighting of code inside code fences. The markdown-preview package is a package included with Pulsar that allows you to render Markdown documents directly within Pulsar without the need for any external tools. One of its features is the fact that it is able to perform syntax highlighting of code within "code fences" or "code blocks". That is, if you include a small snippet of javascript or shell script into your document, markdown-preview can provide the same level of syntax highlighting to your snippet in the preview pane as in the main editor.

    This is done by providing a "language identifier" to the codeblock in order to signal to Pulsar what language you want it to highlight. For example, if you wanted to embed some javascript in a document, you would need to start the code fence like this:

    ```javascript
    +

    or

    ```js
    +

    The issue was that this list of identifiers had not been updated for a long time, and people used to other tools and applications with this same functionality (for example, within GitHub and various static site generators) were finding that identifiers they commonly used were not being correctly identified.

    This change adds some significant improvements to this system, not content with only updating the existing list of languages to match GitHub's Linguistopen in new window library but massively extending the system to allow users to tailor the behaviour to suit their own needs. In addition to the "Linguist" (GitHub) mode, it adds in lists for other tools such as "Chroma" (Codeberg, Gitea, Hugo), "Rouge" (GitLab, Jekyll) and "HighlightJS" (Markdown-IT). In many cases, these will be identical and users can leave it on the existing default mode (Linguist). However, for those who rely on Pulsar and the markdown-preview package to correctly display code as it will be used on the platform of their choice, this allows them to not have to modify the identifier between their editor and their production files.

    As if that wasn't enough, it also features a new feature that allows you to freely add and customise your own options in order to include non-default languages and to override your own options by way of a new setting within the package:  Custom Syntax Highlighting Language Identifiers. For example, if I wanted to have PONopen in new window support or wanted to add j as an identifier for "javascript", I could add the following to that setting: pon: source.pon, j: source.js.

    You can try out this feature right now in our rolling releaseopen in new window or you can wait until our next regular releaseopen in new window.

    Moving PPM to our own NPM fork

    One issue we have had crop up again and again that has frustrated contributors and users alike is that Pulsar was using some older versions of some tools, particularly node-gypopen in new window (a tool for compiling native modules for Node.js), which had some very particular requirements for development tooling on the machine. For example, it was very particular about which version of the Visual Studio tools would work and which versions of Python could be used (i.e. not the latest releases).

    This is a problem for Pulsar because node-gyp is used by NPM which is used as part of our PPMopen in new window (Pulsar Package Manager) tool for installing Pulsar packages. Pulsar does not use a system NPM installation but instead has its own version bundled with the application.

    So @DeeDeeGopen in new window has done some work to migrate to our own fork of NPMopen in new window which changes our node-gyp dependency from the old 5.11 to the much newer 9.4.0 which should mean that Pulsar will soon properly support modern compiler toolchains and drop support for old, unsupported ones.

    pulsar-updater package in the works

    A comment that we see come up somewhat regularly from both our team and community members alike is how Pulsar should deal with updates. Atom has an auto-update module that (at least on Windows and macOS) would allow you to update Atom from within the application. Unfortunately, these existing auto-update methods are either incompatible or unfeasible to implement within Pulsar (for example, the requirement that we would need to pay large amounts of money to sign our binaries). We also support (and plan to support) a much larger set of distribution methods than Atom - various Linux, Windows and macOS package managers as well as our "standard" binary releases, which means accounting for all of these different methods.

    So a first step towards a solution here is a new core package developed by @confused-techieopen in new window called pulsar-updateropen in new window which is designed to inform the user not only when a new version has been released but also how to install it based on what install method is detected on the system. For example, it might prompt you to visit the Pulsar website to download a new binary, or it can provide a command for your terminal to run.

    The notifications generated by this package can be dismissed entirely or until the next launch in order to be as minimally intrusive as possible.

    This feature is still something in the works so isn't available just yet, but you can be sure we will provide more details and an announcement once it is out in the wild.

    Pulsar Package Repository feature detection is now live!

    A couple of months ago we announced that a new feature was coming to our package backendopen in new window. The linked post goes into far more detail, but in a nutshell this is a feature that adds "feature detection" to our backend, allowing users and developers to have a much more cohesive experience when trying to install or develop new Pulsar packages that rely on features from other community packages.

    We don't currently have any implementations of this feature in Pulsar itself just yet, but we do have plans for it. For example, we could provide automatic detection of a community grammar for a language not installed on the system. This could easily be extended to all kinds of other features provided by community packages.

    Community spotlight

    As we always say (because it is absolutely true), this project would be nothing if it weren't for our wonderful community members, and we want to make sure we provide proper attribution when people directly contribute to Pulsar, not only through our release notes but on our social channels as well. So in light of that, a big thank you to @ariteopen in new window, @cat-master21open in new window, @2colorsopen in new window and @GuilleWopen in new window who have made contributions to the project which we have recently included.


    And just like that, we are done with yet another month's updates. As ever, if you want to get more involved in the community, please join in on our various social channelsopen in new window. Hope to see you again this time next month!

    + + + diff --git a/blog/20230816-DeeDeeG-v1.108.0.html b/blog/20230816-DeeDeeG-v1.108.0.html new file mode 100644 index 0000000000..eca2cc20fd --- /dev/null +++ b/blog/20230816-DeeDeeG-v1.108.0.html @@ -0,0 +1,223 @@ + + + + + + + + Great Scott! A new Pulsar release. Pulsar 1.108.0 is available now! | + + + + + + +

    Great Scott! A new Pulsar release. Pulsar 1.108.0 is available now!

    DaeraxaAugust 16, 2023
    • dev
    • release
    About 4 min

    Great Scott! A new Pulsar release. Pulsar 1.108.0 is available now!open in new window

    What is new in 1.108.0?

    Welcome to our latest Pulsar release! We have some really exciting new features this month that we can't wait to share.

    To kick things off, we have a brand new core package, pulsar-updater. We recently featured this on our monthly Community updateopen in new window , but in case you missed it (or simply just want to read more about it), pulsar-updater is a new package designed to help notify users about new Pulsar releases and where they can get them. As always, we are open to feedback, especially when we have brand new functionality like this, so please let us know via any of our social channelsopen in new window if you have anything for us.

    Next, we have yet more grammars for our modern Tree-sitter implementation. If you have missed any details on this, then you can read more on our blogopen in new window. The two new grammars in question are for Markdown and YAML. So what benefits does this give us? Well, for example, with Markdown, this provides significantly more accurate highlighting than the existing "Textmate" grammar; it provides HTML syntax highlighting as well as YAML frontmatter support (a common addition to Markdown documents these days, particularly for static site generators), and it allows us to clean up some of the scope names to suit a more conventional approach to naming. All these features should provide a much better Markdown writing experience in Pulsar and allow us to easily keep it up to date with anything new we might want to add in the future.

    On the topic of Markdown we also have a rather large update to our markdown-preview package, which provides a window to display the rendered output of your Markdown documents. The big change here is the syntax highlighting displayed within code blocks specified via a "language identifier". The list of these supported identifiers was rather out of date, and the world had moved on around it, so the decision was made to bring this up to date with the behaviour that people expect from such functionality. We go into far more detail about this change in our Blog postopen in new window so have a read of that if you want to know the full details about this change.

    Last, but definitely not least, we have finally begun the migration from apm to ppm. When we started the Pulsar project, one of the first things we did was rename the "Atom Package Manager" to the "Pulsar Package Manager" for obvious reasons. However, under the hood, you would still find apm binaries and files; this was particularly relevant for Windows users, for which there was no automatic PATH registration. The good news is that we have begun the transition to change them. For the time being, apm will remain alongside ppm until we can be sure we haven't hit any unforeseen issues.

    And with that, we bring this to a close. As ever, a huge thank you to all of our community members and especially our donors, without whom this project just wouldn't be possible.

    Until next time, happy coding, and see you amongst the stars!

    • The Pulsar Team

    Changelog

    • Restored ability for less files in packages to use inline JavaScript inside backticks.
    • Fixed a syntax highlighting issue inside the styleguide package.
    • Fixed an issue with rubygems timing out on ARM Linux workflow.
    • Rewrote Tree-sitter scope predicates to use #is? and #is-not? where applicable.
    • Ensure that project-specific setting overrides don't leak to the user's config file when the settings UI is visited.
    • Added a feature in markdown-preview that adds support for Linguist, Chroma, Rouge, and HighlightJS for language identifiers in fenced code blocks.
    • Fixed the TextMate language-toml grammar to properly support whitespace where-ever it may appear.
    • Added a Tree-Sitter grammar for YAML files.
    • Added a new core package pulsar-updater to help users update Pulsar.
    • Added ppm and ppm.cmd binaries/launchers within ppm. This allows easier integration of correctly named binaries on more systems in more contexts (especially Windows). Existing apm and apm.cmd binaries/launchers are still there for the time being.
    • Added a modern Tree-Sitter grammar for Markdown files.

    Pulsar

    ppm

    + + + diff --git a/blog/20230825-Daeraxa-ChocolateyUpdate.html b/blog/20230825-Daeraxa-ChocolateyUpdate.html new file mode 100644 index 0000000000..06a0cf551f --- /dev/null +++ b/blog/20230825-Daeraxa-ChocolateyUpdate.html @@ -0,0 +1,223 @@ + + + + + + + + Chocolatey packages are up to date again! | + + + + + + +

    Chocolatey packages are up to date again!

    DaeraxaAugust 25, 2023
    • news
    • windows
    • chocolatey
    • package manager
    About 1 min

    An update on the status of our Chocolatey packages.

    As you may know, we decided to officially support the Chocolateyopen in new window package manager on Windows back in April. However, we quickly ran into a few problems regarding the Pulsar binaries exceeding the size allowed. Unfortunately, this meant that Pulsar was "stuck" on version 1.103.0 whilst we tried to find a solution.

    Unfortunately, this went on for rather longer than expected, but thanks to the efforts of a number of people, including @COLAMAroroopen in new window, @spiker985open in new window and @confused-techieopen in new window, a solution was found to the problem, and, as a result, the latest regular release of Pulsar is once again available on Chocolatey. Not only that, but all the intermediate releases have also been provided.

    Whilst all this was going on, we also realised that the process of actually creating a new release for Chocolatey was a little inefficient and overall too manual, so we took the opportunity to also upgrade our release process into a semi-automated one so that all we have to do is press a few buttons and all the rest is done automatically.

    Thank you to all those who have been waiting patiently for this issue to be resolved. With any luck, now that we have our processes for this ironed out, we won't come across anything like this again.

    So now you can safely run a cheeky choco upgrade pulsar at your convenience to get the latest release installed on your system and enjoy all the updates and upgrades we have been putting into Pulsar these last few months.

    + + + diff --git a/blog/20230903-confused-Techie-pulsars-ci.html b/blog/20230903-confused-Techie-pulsars-ci.html new file mode 100644 index 0000000000..b806f44a69 --- /dev/null +++ b/blog/20230903-confused-Techie-pulsars-ci.html @@ -0,0 +1,223 @@ + + + + + + + + Pulsar's CI | + + + + + + +

    Pulsar's CI

    confused-TechieSeptember 7, 2023
    • dev
    • ci
    About 6 min

    How Pulsar's CI has recently changed.

    CI, or Continuous Integration, is a rather broad term used to describe the DevOps process of continuously (oftentimes on every change) building and testing a piece of software. In Pulsar's case this means two big things:

    • Testing Pulsar and all of its core packages
    • Building Pulsar binaries and then running tests using those built binaries.

    To better understand how drastically things have changed, let's quickly take a look how Pulsar's CI has looked up until this month since nearly the beginning of the project.

    How it was

    Whenever someone made a Pull Request to Pulsar, their code changes would be run through a gauntlet of processes:

    • The entirety of the Pulsar codebase would have its tests run on GitHub Actions.
    • Every single core package (those that come with Pulsar) would also be tested via GitHub Actions.
    • A Pulsar binary was built for every single platform we support, this means Windows, Linux, macOS (Intel), macOS (Apple silicon), and ARM Linux.

    The big point of interest today is the process of building the Pulsar binaries.

    On August 17, 2022 @mauricioszaboopen in new window introducedopen in new window and created Pulsar's integration with CirrusCIopen in new window. Which is an overall, fantastic CI platform, that at the time had a very generous free tier. So generous in fact that every single binary built for Pulsar, since we started the project, has been built within this free tier.

    As those familiar with the project know, all of our funding comes from our fantastic community, via OpenCollectiveopen in new window and GitHub Sponsorsopen in new window, and we aim to be very careful about how we spend the money that is donated to us. At all times ensuring that the money is used in ways that can benefit the majority of our users, and that there is no better alternative for its purpose. That's why when Cirrus announcedopen in new window that they were changing their free tier to something a bit more restrictive we had to scramble to find a way to continue our pace.

    How it is, and how it got there

    Now, I don't want to come across as ungrateful to the fantastic service that Cirrus offers, so when this price change was first announced, we had originally intended to figure out if we could afford these price changes reasonably.

    To determine what we could afford, we first had to estimate what we would've been paying the past months of usage. @DeeDeeGopen in new window was able to break down exactly what our costs looked like, using our previous months data.

    Keeping in mind that we trigger the same amount of runs per platform, the difference here is how slow or fast any given platform is during the build process:

    MonthWindows Credits/HoursLinux Credits/HoursmacOS Credits/Hours
    May66 credits / 273 hours72 credits / 396 hours490 credits / 544 hours
    July55 credits / 229 hours39 credits / 213 hours305 credits / 339 hours

    Using this data, where we consider 1 credit = $1 USD, minus the novel 50 free credits, July would've cost $349 USD. We could then realistically estimate that building Pulsar binaries will cost ~$350 USD a month.

    Which when we consider that's a higher price than anything the Pulsar team spends any money on for Pulsar, it's a bit of a tough pill to swallow. But especially because Cirrus isn't the only service out there, no one on the team could justify spending our sponsor's money on such a large expense. At this point, we had to determine a way to lower our usage or move off the platform.

    But to move off Cirrus, we had to consider everything it currently does for us:

    • Builds binaries on every Pull Request
    • Builds our Regular Release Binaires
    • Builds our Rolling Release Binaries (on every commit to master)
    • Acts as a CDN for our Rolling Release Binaries (The download microservice redirects downloads directly to Cirrus)

    At this point, with the above knowledge, we had several different solutions to jugle and consider:

    • We could move to another CI platform, but Cirrus is one of the cheaper ones
    • We could move most of our process to GitHub Actions
    • We could move everything to non-CI services, such as cloud hosted VMs
    • We could store binaires in other cloud storage services, such as GCP or Digital Ocean
    • We could slim down our Cirrus usage as much as possible

    But with all of these ideas, as a Do-ocracy, attemptsopen in new window began being made to switch as much as possible to GitHub Actions.

    While this did solve a few of our needs from above, it didn't solve all of them, and introduced its own issues.

    The biggest one: GitHub Actions doesn't support Apple Silicon or ARM Linux. Even though Apple Silicon support is on GitHub's Roadmapopen in new window, it has been pushed forward over and over since June 15th, 2022. So this support wasn't something we could rely on.

    In finding a solution to our platform support issue, we looked at quite a few possibilities, at a point we were even considering purchasing an Apple Silicon Mac, second hand, and using GitHub Self Hosted Runners to build there. But of course the simplest answer, would be to only build Apple Silicon on Cirrus, but we had to consider what that would actually come out to. Looking at using all 50 free credits for building only Apple Silicon binaries, would mean getting up to 833.333 minutes within Cirrus, but we had to compare those minutes into what we could acheive:

    Typical Apple Silicon task run lengths

    Run TypeLength in MinutesTask runs/month
    Shortest Recorded14.3558
    Average17.94746
    Median17.29248
    Longest Recorded26.631

    So we could estimate to say we could have anywhere from 31-58 macOS runs per month (including re-runs for failures), but that shooting for ~30 macOS runs per month is a safer conservative estimate.

    But, all of this above doesn't account for our ARM Linux runs, that also must stay at Cirrus CI. So we had to consider, within our 50 free credits per month that would get us 8333.333 minutes of Linux builds per month (Which that is not a typo, we are able to get 10x the amount of real-world macOS minutes compared to ARM Linux):

    Typical ARM Linux task run lengths

    Run TypeLength in MinutesTask runs/month
    Shortest Recorded28.367293
    Median30.208275
    Average32.114259
    Longest Recorded45.883181

    So while it seems we could build these platforms on Cirrus, there was temporarily a concern about if we could choose only one, to keep at our current pace, at this point we had to try and find if we should give any specific platform any priority, to which we turned to our install numbers (gathered from the logs of the download microservice):

    Rolling Release Download count by Platform over the past 30 days:

    PlatformDownload CountDownload Percentage
    Apple Silicon86015.59%
    ARM Linux1091.97%
    Windows2,37042.98%
    Intel Mac5509.97%
    Linux1,62429.45%

    With this, we could easily see that if one platform must be prioritized, the numbers say that priority should be given to Apple Silicon, even though, we knew we didn't want to play favourites with platforms.

    It was at this point, we had to break down how many runs we could do for both platforms. If we assume each build is one Apple Silicon macOS run and one ARM Linux run:

    Credit-Cost Build TypeCreditsBuilds/month
    Lowest Recorded1.03248
    Median1.25639
    Average1.26939
    Highest Recorded1.76628

    With these exact numbers of our current trends, we could then assume we have an allotment of 28 builds per month for these platforms. But consdering the possible need of retries, or a build erroring out, we had settled on building Apple Silicon, and ARM Linux once every other day. Which should give us about 15 builds per month minimum. This would give us a healthy buffer zone, or any retries needed, and the ability to do another run for the regular release every month.

    But even though we now knew what we needed to do, there was still a long list of things we had to change to get there, especially now considering our deadline to do so was only a few days away.

    After a flurry of PRs and lots of testing, the Pulsar team was able to accomplish all of the following in just under our 5 day limit:

    • Migrated all other platforms over to GitHub Actions
    • Ensure binaries are easily downloadable via GitHub Actions for testing purposes
    • Configure CRON jobs within Cirrus to build these platforms every two days
    • Minimize the amount of times Cirrus would run, ensuring to only run if changes were present, and disallow any other type of run that may occur
    • Setup a brand new repository pulsar-rolling-releases to act as the CDN for every single rolling release
    • Nearly completely rewrite the download microservice to now use this new repository to retreive the newest rolling release

    All of that work has culminated in a new CI plan for Pulsar, the one that is currently in effect starting September. On every single PR to the Pulsar repo, a binary will be built for Windows, Linux, and macOS. Once that PR is merged, a new rolling release will automatically be built, again just for these three platforms, which will then be publishing as a new release to the repository pulsar-rolling-releasesopen in new window. Once on this repository the download microservice will be able to find and return these versions to any users attempting to download a rolling release of Pulsar. Then once every two days, Cirrus will be triggered to build binaries of Apple Silicon and ARM Linux. These new binaries will also be published to pulsar-rolling-releases. Then whenever we plan to do a new regular release of Pulsar, once a new tag is created for this release, Cirrus will automatically be triggered to build binaires for its two platforms. Luckily for us, all of these changes mean we have not incurred any additional cost for the Pulsar project, even though there is some talk of officially supporting Cirrus on Open Collective at a price we can properly afford.

    Summary

    While this blog post may have been a little data heavy, it's been a very busy time over in Pulsar, and I hope this blog can help explain why some things have changed, and help layout what things look like for the time being. Especially here, a huge thanks goes out to everyone that helped this migration on the Pulsar team, especially a huge thanks to @DeeDeeGopen in new window for their amazing work in not only planning this migration, but enacting it. Additionally, I'd like to give thanks to all the developers at Cirrus, who not only provide an amazing service, especially one that has made Pulsar possible, but provides the platforms needed to support our users, and has been amazing at offering support whenever needed.

    + + + diff --git a/blog/20230904-Daeraxa-SeptemberUpdate.html b/blog/20230904-Daeraxa-SeptemberUpdate.html new file mode 100644 index 0000000000..61686444d4 --- /dev/null +++ b/blog/20230904-Daeraxa-SeptemberUpdate.html @@ -0,0 +1,223 @@ + + + + + + + + Community Update | + + + + + + +

    Community Update

    DaeraxaSeptember 4, 2023
    • news
    • log
    • update
    About 4 min

    I want you to act as a Pulsar team member writing a blog post. I will provide titles of recent changes. You will elaborate on each topic and make each paragraph coherent from my ramblings. You will welcome all readers to the September Pulsar community update.

    Welcome to the September Community Update!

    Welcome to this month's Pulsar community update! In store for you this month we have some massive changes to our CI process, some good news for Windows Chocolatey users, a new option for Pulsar's title bar, some improvements to our ppm unpublish command and work on a brand new utility to help clean up elements of a Pulsar installation.

    I realise that this probably raises more questions than it answers, so read on to find out more!

    Upheaving our CI setup

    Ever since we started providing downloadable binaries shortly after the Pulsar project properly started, we have been using Cirrus CI as our continuous integration platform. This has worked very well for us for quite some time now, but unfortunately for us, they recently decided to change their free tier and our setup puts us well beyond that free tier and into some quite serious money. At this point we had a couple of choices: either we stick with Cirrus CI by using our donors' money, or we move to another platform. We chose the latter option as we simply could not justify nor sustain the costs.

    We did a lot of searching for alternative platforms and ultimately settled on using GitHub actions for nearly everything. This isn't perfect; for example, we cannot use it for our Apple silicon (M1, M2) builds, nor for ARM Linux, for which we are still using Cirrus CI. Over the next week there was a huge amount of effort put in by a number of Pulsar team members, but particularly @confused-techieopen in new window, @DeeDeeGopen in new window and @Meadowsysopen in new window to get everything moved and migrated in order to keep our builds building and our rolling releases rolling.

    So what does this mean for our community? Hopefully, you didn't even notice anything. The biggest effect is that Apple silicon macOS and ARM Linux binaries are produced a little less frequently (i.e.once every other day).

    We have a much bigger and more detailed write-up on what went on to be published soon, as it was a particularly interesting problem (for a community project funded entirely by donations) to work around, so watch this space!

    Chocolately packages are up to date again!

    If you use the Chocolately package manager for Windows, you may have noticed the official packages have been a few versions behind. This has now been solved and the latest versions are available once again with a lot of improvements to the process to avoid this kind of thing ever happening again. A huge thanks to @COLAMAroroopen in new window, @spiker985open in new window and @confused-techieopen in new window for implementing this. We have already put up a whole blog post for this, so if you missed it, you can read it hereopen in new window.

    New title bar configuration option

    @confused-techieopen in new window has recently added a new featureopen in new window to allow some additional configuration of Pulsar's title bar data. This new settings option allows you to decide if you want the current active Pulsar tab to be prepended to the title bar or not. This can potentially aid in readability for some, particularly if you are somebody who likes to work with multiple windows.

    To demonstrate, the next image shows what it currently looks like:

    And what it looks like if you turn the setting off to remove the tab from the title:

    This is currently available in our rolling releaseopen in new window and will be in our next regular release in a couple of weeks.

    Unpublishing packages

    There are some very good reasons to want to unpublish a package or a specific version from the Pulsar Package Repository. You may have made a mistake or simply want to deprecate an old, non-functional version. Either way, we had an issue; it just wasn't working. Thankfully, due to some great collaboration between @asiloisad/@bacadraopen in new window and @confused-techieopen in new window, the problem was found and patchedopen in new window.

    However, shortly after this was fixed, we were contacted by a community member asking why they were unable to re-publish their package after a successful unpublish in order to fix a versioning mistake. Ultimately, we realised that the wording shown during the unpublishing process does not make it clear that once a package has been unpublished, we permanently block the package name from being used ever again. This is intentional in order to prevent any bad actors from hijacking the name of a previous package in order to hide malicious code. This also provided the opportunity for us to come up with a process to work around this in exceptional circumstances, we will now ensure that:

    1. Nobody has downloaded the unpublished package.
    2. There are no significant code changes between the unpublished version and the version the author wishes to re-publish.
    3. The author can prove ownership of the repository.

    So with the above process and an improvement to the wording in the unpublish command coming soon, we hope that this situation can be avoided in the future. A full writeup of what was done has been added to our admin actions logopen in new window that details events just like this for full transparency to the community.

    Pulsar cleanup

    It was brought to our attention by a community member that, upon uninstall, Pulsar was not clearing up all the directories it created during installation. Whilst this is somewhat expected for the configuration directory, there were also examples of other elements being left behind. To this end, @confused-techieopen in new window has been putting together a new package, pulsar-cleanupopen in new window, to try and deal with these leftover elements. As electron-builder is largely in control of the uninstall process, some elements do get left over at the end of the process. This utility will be a stand-alone executable that can be used to fully clean up all these extra artifacts, leaving your system entirely clean of any Pulsar installation.

    Community spotlight

    In our community spotlight this month, we want to say a big thank you to new first time contributor @casswedsonopen in new window for this PRopen in new window to remove a bunch of deprecation warnings in one of our workflows.


    And yet again that brings us to the end of another community update. We hope you enjoyed this content and that you are looking forward to seeing some of these changes coming to a Pulsar near you! As ever, it would be amiss of us to not mention our amazing community and especially our financial donors, who without this project would simply not be possible! So thank you again and see you all again this time next month!

    + + + diff --git a/blog/20230916-Daeraxa-v1.109.0.html b/blog/20230916-Daeraxa-v1.109.0.html new file mode 100644 index 0000000000..f67504a8e1 --- /dev/null +++ b/blog/20230916-Daeraxa-v1.109.0.html @@ -0,0 +1,223 @@ + + + + + + + + Going the whole nine yards: Get Pulsar 1.109.0 now! | + + + + + + +

    Going the whole nine yards: Get Pulsar 1.109.0 now!

    DaeraxaSeptember 16, 2023
    • dev
    • release
    About 5 min

    Going the whole nine yards: Get Pulsar 1.109.0open in new window now!

    What do we have for you in Pulsar 1.109.0?

    Our next release has arrived, and, as ever, we are excited to share all the changes we have been making over the last month since our last release.

    We have a smorgasbord of bug fixes and quality of life improvements this month that we hope will make things just that bit better overall. Many of these changes are "behind the scenes", so you may not see much evidence of them when using Pulsar, but the lack of any obvious changes means that we actually accomplished our goals of not disrupting the user experience.

    Starting things off is one of these "background" changes, but it was a massive amount of work that was put into the project in order to move CI platform. We have a fantastic blog postopen in new window on the topic that really goes into the full details of this change. This release marks the first (regular) release created after this change was made, which is why we feel it is important to mention it here even though you won't see anything different in Pulsar itself.

    To continue the trend of "background" changes, we have finally achieved our goal of removing all of the CoffeeScript code in the core editor and packages (a process dubbed "decaffeination") in favour of standard JavaScript. This was a goal started by the Atom team and has been a long time coming as, whilst it was good at the time, many of the features of CoffeeScript can now be found in vanilla JavaScript and migrating away from it allows a more unified codebase that is easier to maintain and lowers the barrier of entry for new contributors. Again, you won't really see anything within Pulsar itself, but this was a big achievement and we can finally draw a close to this little chapter.

    Onto some changes you will see in Pulsar. Probably the most obvious one is our deprecation of the previous (and defunct) autoUpdate API. This is a follow-on from a change that first came into our 1.108.0 release to add the new core package pulsar-updater. You can read more about that particular change in the previous release notesopen in new window. The most obvious result of this is the removal of the message on the about page telling you about automatic updates. This has instead been replaced with a link to the Readme of the pulsar-updater package reflecting the new situation.

    On the topic of the about package, we have some nice quality of life changes. The first of which is a change to how we link to the changelog/release notes. Previously, this would link to the changelog state as it was at that specific release. We have changed this to instead link to the relevant section on our master branch changelog. This not only allows us to properly support our rolling releases but also gives our regular releases quick access to look at upcoming changes. We also have a nice little update to improve the responsiveness of the pane when displayed in a narrow format; no longer do things get shoved and squished out of place.

    Now onto some bug fixes. The first one here regards a race condition that was found in our autocomplete-plus package, which was causing some weird situations where the suggestion list would open, close, then open again. This has now been fixed (with some handy assistance from the core package keyboard-resolver to help narrow down the problem).

    The next is specific to Windows users, where the Pulsar logo was not being shown (and was being replaced with a default blank icon) when Pulsar was set to be the file handler for selected file types. This has now been solved, and the logo should be displaying correctly.

    To finish things off, we have one change that improves resource usage quite considerably. However, in this case, we aren't talking about memory or CPU, but our cloud compute costs. What we found is that unnecessary requests were being made to our backend services by the settings-view package (which, amongst other things, is responsible for package management) for Pulsar's core packages. As the core packages aren't really designed to be updated between versions (that is why we have our rolling releaseopen in new window) there is no need to poll the backend for information such as the number of downloads or stargazers. The upshot of this change is that we are now saving a not-insignificant amount of money in backend costs. As this whole project is only possible due to our generous donors in the first place, this change means we can make better use of these funds that have been freed up. As always, our costs and expenses are transparent and open on our Open Collectiveopen in new window should you wish to see the effect.

    And that just about draws things to a close for this release. We have had a particularly busy month with some rather significant changes to all kinds of areas of Pulsar, so we hope you enjoy the update. As ever, a huge thank you to our generous donors and community, without whom this project would not be possible.

    Until next time, happy coding, and see you amongst the stars!

    • The Pulsar Team

    • Fixed a race condition that could cause autocomplete-plus to ignore user input.
    • Fixed the about package linking to release notes for Pulsar.
    • Reduced the amount of network requests that settings-view creates.
    • Fixed the icon used when registering Pulsar as a file handler on Windows.
    • Removed the autoUpdate API from Pulsar, instead relying on the pulsar-updater package.
    • Prevented warnings in the developer console from appearing when autocomplete suggestions are shown.
    • Removed all CoffeeScript code from Pulsar and core packages.
    • Migrated the majority of our CI to GitHub Actions.

    Pulsar

    + + + diff --git a/blog/20230917-Daeraxa-LemmyCommunity.html b/blog/20230917-Daeraxa-LemmyCommunity.html new file mode 100644 index 0000000000..58058b57f1 --- /dev/null +++ b/blog/20230917-Daeraxa-LemmyCommunity.html @@ -0,0 +1,223 @@ + + + + + + + + Lemmy community now open! | + + + + + + +

    Lemmy community now open!

    DaeraxaSeptember 17, 2023
    • news
    • windows
    • chocolatey
    • package manager
    Less than 1 minute

    Join !pulsaredit@lemmy.ml!

    Come and join our newly opened Lemmy community!

    Lemmy community summary

    During the Reddit blackout a couple of months ago, we had a lot of interest in the creation of an official Lemmy community in combination with our Subreddit. After checking things again with a vote, we have decided to officially open our Lemmy instance.

    So if you like the Reddit style format but would prefer federation and open source software, then come and join us at pulsaredit@lemmy.mlopen in new window!

    With this, we expand upon our existing presence in the fediverse (@pulsaredit@fosstodon.orgopen in new window) and hope that we can grow the community in these spaces.

    If you are new to Lemmy or the fediverse in general, it is important to understand that you don't need to join the instance that the community is hosted on. In the case of Lemmy, our instance is on https://lemmy.ml/, but you can join pretty much any community you like and still fully interact with the pulsaredit community - this is what federation is all about. If you don't have a Lemmy account, then you can go to https://join-lemmy.org/open in new window to help you find a community that caters to your interests.

    Once you have made your Lemmy account (or already have one), you can join the community by searching for !pulsaredit@lemmy.ml from within your instance's search, clicking the community name, and hitting Subscribe in the top right.

    + + + diff --git a/blog/20230925-savetheclocktower-modern-tree-sitter-part-1.html b/blog/20230925-savetheclocktower-modern-tree-sitter-part-1.html new file mode 100644 index 0000000000..7b2e44423c --- /dev/null +++ b/blog/20230925-savetheclocktower-modern-tree-sitter-part-1.html @@ -0,0 +1,223 @@ + + + + + + + + Modern Tree-sitter, part 1: the new old feature | + + + + + + +

    Modern Tree-sitter, part 1: the new old feature

    savetheclocktowerSeptember 25, 2023
    • dev
    • modernization
    • tree-sitter
    About 9 min

    The last few releases of Pulsar have been bragging about a feature that arguably isn’t even new: our experimental “modern” Tree-sitter implementation. You might’ve read that phrase a few times now without fully understanding what it means, and an explanation is long overdue.

    This is the first of a series of articles about Pulsar’s ongoing project to migrate its Tree-sitter implementation to a more modern version — the culmination of hundreds of hours of development work that started back in February of this year. It first shipped in Pulsar version 1.106 back in June as an opt-in feature, and is being improved on an ongoing basis with each new monthly release.

    This is a big feature, perhaps the biggest since Pulsar was forked from Atom — and yet it’s a feature that, if we’ve done our jobs right, won’t even seem like much of a change to most users. Before we dive into the deep end, I’ll try to explain why this is a topic worthy of multiple blog posts.

    What is Tree-sitter?

    Tree-sitteropen in new window is a code parsing system. It’s the brainchild of Max Brunsfeldopen in new window, current Zedopen in new window contributor and former contributor to Atom.

    It’s a code parsing system that represents your code as a tree of nodes. It’s very fast on first parse — and even faster at re-parsing code after you’ve made changes, because it can reuse the output from the last parse and reprocess only the parts that have changed.

    You can use its output to underpin lots of features that you’d need in a code editor:

    • syntax highlighting
    • code folding
    • contextual awareness (for example: is the cursor currently within a string?)
    • indentation hinting (for example: if I press Return here, should the next line be indented by one level?)
    • buffer navigation (for example: select the entire string that my cursor is in, or move the cursor to the nearest opening HTML tag)
    • symbol navigation (viewing an outline of your current file, or jumping to a symbol with a specific name)

    A Tree-sitter parser is designed to parse code quickly, but not necessarily with 100% accuracy; the goal is to be accurate enough for the purposes listed above.

    What is the new Tree-sitter integration replacing?

    The new Tree-sitter integration — which I’ll be calling modern Tree-sitter throughout this series — won’t replace anything except for the previous Tree-sitter integration, which I’ll be calling legacy Tree-sitter.

    Once we decide modern Tree-sitter is stable, we’ll drop support for legacy Tree-sitter so that Pulsar can update to a newer version of Electron.

    Tree-sitter will continue to exist alongside Atom’s original system for syntax highlighting: TextMate grammarsopen in new window. This grammar system is based on the one invented by TextMateopen in new window many years ago, and it’s still being used by editors like Visual Studio Codeopen in new window and Sublime Textopen in new window.

    If Tree-sitter is already in Pulsar, why write a new implementation?

    Good question! Atom was, after all, the first code editor to ship with support for Tree-sitter. It was introduced in late 2017, and was made the preferred system for syntax highlighting starting with Atom 1.32 nearly a year later.

    There are two major reasons why the legacy implementation needs to be replaced:

    1. Tree-sitter now has powerful features that the legacy implementation doesn’t leverage. As is often the case, being the first to implement it meant that Atom found all of Tree-sitter’s early pain points. It was a stated goal to use TextMate-style scope names in the new Tree-sitter grammars — so as to make migration easier — but Atom had to invent its own system for mapping Tree-sitter output to scope names, and that system didn’t have the flexibility it needed to match TextMate grammars’ syntax highlighting in all cases. This revealed a need for a more robust system of describing tree nodes, and for highlighting ranges that didn’t correspond to the exact ranges of tree nodes.

      Tree-sitter eventually introduced a powerful query languageopen in new window that could make the job of syntax highlighting easier. But by that point, Microsoft had bought GitHub, and Atom seemed not to be a major priority anymore, so the legacy implementation was never updated to adopt this query language.

      That’s a task worth doing, but it will change how Tree-sitter grammars are written, so there’s no way to avoid the fact that backward compatibility will be broken. But this is a perfect time to make the leap, because…

    2. We need to switch to the web-tree-sitter bindings. One of the goals of Pulsar is to be able to run the editor on the latest version of Electron. Unfortunately, newer Electron versions make it difficult for Pulsar to use Node modules that are not context-awareopen in new window. The legacy Tree-sitter implementation uses the node-tree-sitter bindings, and it appears to be a tall task to adapt these bindings so that they can be used in newer Electron versions. Right now, Pulsar’s reliance on node-tree-sitter is preventing us from upgrading Electron to anything past our current version, 12.2.3 — which is nearly two years old.

      So we decided to migrate to the web-tree-sitter bindingsopen in new window. They use WebAssemblyopen in new window and can run safely inside a browser or an Electron application. Using WebAssembly instead of a native C++ module like node-tree-sitter involves a performance penalty, but we’ve found that penalty to be very small in practice. The web-tree-sitter bindings are robust and can do nearly everything that node-tree-sitter can do.

      If, someday, the node-tree-sitter bindings were updated to be easier to use in an Electron context, we’d be able to migrate back without any further loss of backward compatibility. But for now, web-tree-sitter is the way forward, and we’re pleasantly surprised at how well it does the job.

    Nobody likes to break backward compatibility, but needing to switch to web-tree-sitter presents us with an opportunity. Tree-sitter is more stable and more robust than it was in 2017, so we’re able to replace legacy Tree-sitter with something better rather than something that’s merely equivalent.

    Why is Tree-sitter better in general?

    Here are a few reasons why Pulsar is using Tree-sitter at all, and why Pulsar is configured to prefer a Tree-sitter grammar over a TextMate grammar when both are present:

    • Tree-sitter can offer far more accurate and specific syntax highlighting.
    • It can give you better understanding of context. For example: it makes it much easier to write snippets that can behave differently based on the context of the cursor.
    • It makes it much easier for grammar authors to describe features like code folding and indentation hinting — making Pulsar smarter and easier to work with.
    • It allows for smarter code navigation — meaning a more modern and flexible way to view the important symbols in your current file.
    • It offers package authors a richer system for working with source code files. The syntax tree generated by Tree-sitter can be consumed by packages and leveraged in a number of ways.

    The specific ways in which Tree-sitter will make your life easier will vary based on which languages you use most often, but this post series will explore a handful of examples.

    Why is this new implementation better than the old one?

    An under-the-hood change like this isn’t necessarily something you’d notice. But Pulsar users may notice some of the downstream effects:

    • Most notably, modern Tree-sitter is better at understanding and syntax highlighting your code than legacy Tree-sitter.
    • You may notice that Pulsar is better at indenting and dedenting your code as you type, or suggesting ways to fold code blocks that weren’t possible before.
    • You may notice new features being added to existing language support — for example, snippets that do different things based on context — that weren’t possible under the legacy system.

    The benefits are much more direct to grammar authors:

    • It gives authors a more intuitive system for describing syntax highlighting, and one which can finally match a TextMate grammar’s flexibility in how it applies scopes.
    • It gives authors brand new systems for describing code folding and indentation hinting.
    • Modern Tree-sitter grammars are easier to iterate on — they allow someone to make changes to a grammar in progress and see them applied instantly.

    I disabled Tree-sitter grammars at some point, and I don’t feel like I’ve missed anything. Why should I turn them back on?

    TextMate grammars are still the main style of grammar in Visual Studio Code, Sublime Text, and other editors. They can’t do all the things that Tree-sitter parsers can do, and most new editors on the market have chosen to use Tree-sitter instead; but even just VSCode’s example tells us that TextMate grammars are no impediment to having a popular and feature-filled editor.

    So I’ll be clear: we have no plans to deprecate TextMate-style grammars. They still have their place in Pulsar, and the only thing we’d achieve by deprecating them is to disrupt the editor experience of many of our users.

    In the future, it will still be possible (as it is today) to turn off Tree-sitter grammars, either altogether or selectively for certain kinds of files, and fall back to a TextMate grammar for a given language (if it exists).

    But our hope is that you’ll give this new Tree-sitter system a chance, even if you’d disabled Tree-sitter grammars in the past for any reason. We think it’s got all the upsides of the legacy Tree-sitter integration without any of the downsides.

    Can I use this new implementation now?

    Yes, you can, as long as you’re on Pulsar 1.106 or greater. Open your Pulsar settings and focus the “Core” pane. Find the setting named Use Modern Tree-Sitter Implementation and make sure it’s checked, then make sure that the nearby setting named Use Tree-Sitter Parsers is also checked. Then restart Pulsar or reload your window.

    If you routinely use the grammar selector and want to be able to switch between Tree-sitter grammars and TextMate grammars at will, locate the grammar-selector package in the “Packages” pane, then click on its Settings button. Uncheck the setting named Hide Duplicate TextMate Grammars. This will give you the ability to choose between modern Tree-sitter, legacy Tree-sitter, and TextMate grammars.

    Which Tree-sitter grammars come with Pulsar?

    Currently, these grammars are built in:

    • C and C++
    • Clojure
    • CSS
    • EJS and ERB (HTML with embedded JavaScript/Ruby)
    • Go
    • HTML
    • Java
    • JavaScript
    • JSON
    • Markdown
    • Python
    • Ruby
    • Rust
    • Shell
    • TOML
    • TypeScript (and TSX)
    • YAML

    In addition, Pulsar ships with several specialty Tree-sitter parsers that can be injected into other grammars:

    • A parser to detect URLs in text (for identifying and highlighting URLs in strings and comments)
    • A parser to detect TODO-style remarks in comments so that they can be highlighted
    • A parser to highlight regular expressions in various languages
    • A parser for separating YAML front matter from Markdown

    If you use a language that isn’t on the list above and you’re curious about what it would take to give that language a Tree-sitter grammar, you’ll get extra value out of this post series.

    The old grammar highlighted my code in a way that I liked. Now things are colored differently and it’s driving me nuts. Should I turn off Tree-sitter?

    Please don’t! It’d be like amputating your finger to get rid of a hangnail.

    Instead, you can use your user stylesheet to apply a few lines of overrides to your syntax theme and restore the look you’re used to. Open a topic in our discussion forumsopen in new window and someone can tell you exactly how to do it.

    Why should I write a Tree-sitter grammar for Pulsar?

    Because it’s a much friendlier experience than writing your own TextMate grammar, provided that a Tree-sitter parser exists for the language in question.

    Pulsar already has built-in Tree-sitter grammars for most common programming languages. But if you’re a consumer of something more obscure, you might find that someone’s already written a parser for it. The nvim-treesitter project — arguably the largest extant consumer of Tree-sitter — is responsible for the creation of dozens of Tree-sitter parsersopen in new window for niche languages.

    In my experience, turning a Tree-sitter parser into a full-fledged Pulsar grammar takes less than two hours.

    Why is this interesting enough to write about?

    This Tree-sitter overhaul is the biggest feature to be introduced to Pulsar since it was forked from Atom, and it’s a feature that covers a lot of the surface area of the core editing experience.

    Other Tree-sitter–integrated editors like Zedopen in new window, Novaopen in new window, and Lapceopen in new window are, to the best of my knowledge, greenfield projectsopen in new window. They are free to invent entirely new conventions.

    But we’ve got a harder job. Atom embraced most of the concepts inherent to TextMate grammars and built major editor features around them. It wouldn’t be very user-friendly if we introduced a parallel system with a different set of concepts — it would force users to be aware of which kind of language grammar they’re using, and to juggle their mental model accordingly.

    But also: most Pulsar users rely on at least a few community packages that were written for Atom and aren’t actively maintained. We have to be very careful to break backward compatibility as little as possible, and only when it’s absolutely necessary.

    For these reasons, we shouldn’t just introduce brand new systems for code highlighting, contextual awareness, and the rest. Instead, we’ll do whatever we can to make the new Tree-sitter system work within — or identically to — systems that Atom originally shipped with. The Tree-sitter integration can offer enhancements beyond what TextMate grammars do — and it will! — but it’s still got to live in the world that TextMate grammars created.

    So in order to pull this off — to make modern Tree-sitter grammars work within existing systems — we had to create a brand new set of conventions for writing Tree-sitter grammars. In some places, there was prior art from implementations like neovimopen in new window’s; in others we were flying blind and had to invent things from scratch.

    If you’re at all interested in how we did it, stay tuned for the rest of this series.

    + + + diff --git a/blog/20230927-savetheclocktower-modern-tree-sitter-part-2.html b/blog/20230927-savetheclocktower-modern-tree-sitter-part-2.html new file mode 100644 index 0000000000..5d5b1acc52 --- /dev/null +++ b/blog/20230927-savetheclocktower-modern-tree-sitter-part-2.html @@ -0,0 +1,263 @@ + + + + + + + + Modern Tree-sitter, part 2: why scopes matter | + + + + + + +

    Modern Tree-sitter, part 2: why scopes matter

    savetheclocktowerSeptember 27, 2023
    • dev
    • modernization
    • tree-sitter
    About 8 min

    In the last post, I tried to explain why the new Tree-sitter integration was worth writing about in the first place: because we needed to integrate it into a system defined by TextMate grammars, and we had to solve some challenging problems along the way.

    Today I’ll try to illustrate what that system looks like and why it’s important.

    When I first started getting involved with Pulsar back in January, @mauricioszaboopen in new window had just begun the process of migrating us to the web-tree-sitter bindings, and seemed to be dreading the enormity of the task.

    He noticed that most Tree-sitter parsers had their own built-in query filesopen in new window for syntax highlighting. These highlights.scm files act like a sort of stylesheet for a Tree-sitter tree: they’re used when you run tree-sitter highlightopen in new window from the command line, and they’re what GitHub uses when it highlights code in a web browser.

    Couldn’t we just use these files and save ourselves a lot of hassle?, he asked.

    I said no. And I realized that, in saying no, I was volunteering myself to do some hard work.

    How TextMate grammars work

    You may not have ever used TextMateopen in new window, but you’ve probably used an editor that benefited from its existence. Several widely used editors — first Sublime Textopen in new window, then Atom, and now Visual Studio Codeopen in new window — all based their grammar systems on TextMate’s.

    A TextMate grammaropen in new window doesn’t aim to understand your code entirely. It uses regular expressions — pattern-matching — to describe rules that identify the important parts of your code. It knows that some rules apply broadly and others are valid only in certain contexts, so it allows you to nest rules inside other rules.

    Here’s a very simple example of what a TextMate grammar looks like:

    {
    +	"name": "JavaScript",
    +	"scopeName": "source.js",
    +	"fileTypes": ["js", "jsx", "mjs", "cjs"],
    +
    +	"patterns": [
    +		{
    +			"name": "string.quoted.double.js",
    +			"match": "\\\"(.*?)\\\""
    +		}
    +	]
    +}
    +

    It’ll feel overwhelming to consider all of the things a grammar does, so for this example I’ve removed all of its rules except for one: the one that highlights double-quoted strings "like this". You can see that it looks for a particular pattern and, when it matches, assigns the name string.quoted.double.js to that range. That’s called a scope name.

    When you load a JavaScript file in Pulsar — or VSCode, or Sublime Text — this grammar will find all your strings and mark them with scope names. In turn, your editor’s syntax theme will hook into that scope name to make that string a special color. (It will most likely target string, rather than something more specific, but that’s why scope names are divided into segments; targeting string will also match string.quoted.double.js.)

    Let’s make our grammar a bit more complex:

    {
    +	"name": "JavaScript",
    +	"scopeName": "source.js",
    +	"fileTypes": ["js", "jsx", "mjs", "cjs"],
    +
    +	"patterns": [
    +		{
    +			"name": "string.quoted.double.js",
    +			"begin": "\"",
    +			"beginCaptures": {
    +				"0": { "name": "punctuation.definition.string.begin.js" }
    +			},
    +			"end": "\"",
    +			"endCaptures": {
    +				"0": { "name": "punctuation.definition.string.end.js" }
    +			},
    +			"patterns": [
    +				{
    +					"include": "#string_escapes"
    +				}
    +			]
    +		}
    +	]
    +}
    +

    This is practically the exact rule for double-quoted strings from Pulsar’s built-in TextMate grammar for JavaScript. It introduces some more advanced concepts:

    • Instead of a single pattern, we’ve now got begin and end patterns. If it can identify a buffer range that starts with a begin match and ends with an end match, it can scope the entire range that way, and can create a new context in which only certain patterns are applied — in this case, escape sequences.
    • The entire string will still be scoped string.quoted.double.js, but now we’re also applying punctuation scope names to the quote characters themselves.

    Here’s a better way to visualize this:

    string scopes

    If you were to put your cursor anywhere within a buffer and run the Editor: Log Cursor Scope command, you’d see a list of scope names that are active at that buffer position, moving from broadest to narrowest:

    log cursor scopes

    Why is it choosing those specific names? Because of naming conventions established by TextMateopen in new window many years ago. When different language grammars try to abide by the same conventions for naming their scopes, it makes it easier to write syntax themes without constantly checking how they look in every possible language.

    Still, you’d be forgiven if you felt like this was overkill. All we’re doing here is applying some syntax highlighting, right? Do you need this much information just to make a string green?

    How TextMate uses scope names

    TextMate has a good reason for wanting such verbose scope names, and for sprinkling them so liberally throughout your source code: they aren’t used just for syntax highlighting.

    It’s better to think of scope names as intelligent annotations for your source code. The idea isn’t that you’ll want to make single-quoted strings a different color from double-quoted strings — though you could! — but rather that other editor features could benefit from knowing whether you were inside a string, and what kind of string you were inside.

    Because in TextMate, most editor actions — commands, snippets, and macros — can be made contextually aware.

    Let’s use commands as an example. Here are some commands you can write in TextMate:

    • If the cursor is within a string, pressing Ctrl-Shift-’ should convert the string between single-quoted and double-quoted delimiters.
    • If the cursor is in the middle of {}, pressing Return should insert two newlines, put the cursor between the two newlines, and indent the cursor’s line by one level.
    • If the cursor is within a URL, pressing Enter on the number pad should open that URL in a web browser.

    In fact, all of these are real examples that are enabled by default in TextMate. And because they target generic scope names that are shared across a number of grammars, they’ll work in nearly any language.

    Did you notice how our grammar file defined a scopeName of source.js? That’s a root scope name, and it applies to the entire file. And it means that commands can restrict themselves to certain languages just like any other context.

    For instance: to beautify your JavaScript, you might want to run it through prettier. To beautify your HTML, you might want to run it through HTML Tidyopen in new window. And to beautify your JSON, you might want to run it through jq.

    In TextMate, file beautification commands canonically use the hotkey Ctrl-Shift-H. By applying the right scope selector for each of these three commands, you can assign them all to Ctrl-Shift-H, and they won’t get in each other’s way.

    How Pulsar uses scope names

    There are a number of reasons why TextMate isn’t widely used anymore: it’s a macOS-only editor, its releases are frustratingly sporadic, and it’s never had support for crucial features like split editor panes. But TextMate was a major influence on how Atom was built.

    It’s not widely known, but Atom and Pulsar offer nearly as much flexibility with scope names as TextMate does:

    • Any snippet can be defined to run in an arbitrary scope context. For instance, you could write a snippet that will only expand if you’re inside of a JavaScript block comment; if you try to invoke it elsewhere, it’ll act as though that snippet doesn’t exist.

      Built-in packages use this system. For instance, the language-html package defines snippets for most common tag names, but then redefines each one to do nothing if the cursor is inside a context where HTML would be invalid, like a style or script element.

    • Pulsar’s configuration system allows for scope-specific overrides. For instance, a user can set editor.softWrap to false globally, but set it to true for Markdown files.

      Built-in packages use this system. Scopes can be used to set config values that aren’t exposed in the UI — like what to use for comment delimiters in a given language, or which non-letter characters should be considered to be part of a “word” for the purposes of cursor navigation.

    • In Pulsar, commands can be invoked anywhere, whether the user is in a text editor or not; hence there’s no strong system for enforcing that commands be invoked only in certain scope contexts.

      But it’s possible! I’ve got several commands in my init.js whose only purpose is to inspect the scope list of the active text editor’s cursor, then delegate to one of several other commands depending on where the cursor is. It’s not as easy as it could be, but the bones are there.

    But that’s not all. If you use Pulsar, chances are very good that you use a package — built-in or third-party — that inspects scope names for contextual information. Here are just a few of many examples:

    • autocomplete-plus inspects scopes to decide whether it should offer suggestions at the cursor; it declines to do so whenever any of the current scopes matches a user-configurable list. (This is how it decides not to offer suggestions if you’re typing within a comment, for instance.)
    • autocomplete-css decides which suggestions to offer based on the scope name of the closest token.
    • bracket-matcher uses scope names to locate and highlight paired tokens like brackets and matching HTML tags.
    • emmetopen in new window, the eighth-most-downloaded community package, relies on scope descriptors to decide whether it should try to expand an abbreviation into HTML.

    What would break?

    With this in mind, let’s go back to those built-in Tree-sitter query files and consider our string example. In tree-sitter-javascript/queries/highlights.scmopen in new window we can see how strings are treated:

    [
    +  (string)
    +  (template_string)
    +] @string
    +

    This rule treats three different kinds of strings — single-quoted, double-quoted, and backtick-quoted — identically. If we used this rule as written, we’d be applying a scope name of string to all JavaScript strings. Not string.quoted.double.js and the like; just string. And there’s no rule in that file that applies scope names to string delimiters, either.

    If we embraced a system that used names like string and comment instead of string.quoted.double.js and comment.block.documentation.js, the editing experience would be worse in a number of ways ways — some tiny, some large.

    To drive it home for you, let’s think of all the things that would break from this change alone:

    • Any community syntax theme that chose to style double-quoted strings differently from single quoted strings would suddenly behave differently. (Far-fetched? Consider a language like PHP or Ruby in which single-quoted strings have different semantics from double-quoted strings.)
    • Any community syntax theme that chose to style string delimiters in a certain way — with a bolder weight, perhaps — would suddenly behave differently, because the punctuation scopes would no longer be present on the delimiters.
    • Any user-defined snippet that is scoped specifically to string.quoted.double — or even string.quoted — would break.
    • Any user-defined commands that looked for the presence of a punctuation scope to detect whether the cursor is adjacent to a string delimiter would break.

    I think Atom learned this lesson rather well during the original rollout of Tree-sitter grammars. After updating, users were defaulted to the new grammars. Sometimes things looked different, or behaved differently. When users reported these differences, they learned that they could work around those bugs in the short term by unchecking the “Use Tree-Sitter Grammars” setting and restoring the original behavior. I wonder how many did, and how many of them ever re-enabled Tree-sitter grammars in the future.

    A lot of code has been written that presumes the existence of TextMate-style scopes. I think this is fine. The fact that we have a new system doesn’t mean that we should leave scopes behind. For one thing, it’d be a jarring experience for users — we’d be bragging about a new feature that was likely to break customizations that users had been relying upon for years.

    But I also think that TextMate-style scopes function as an elegant middlewareopen in new window. They allow us to change an underlying implementation without the user needing to know or care — except, perhaps, to notice downstream benefits, like faster syntax highlighting and new editor features.

    The challenge

    So that’s why I volunteered to do this. I knew that we couldn’t just reuse some syntax highlighting query files that were written with different assumptions in place. And I knew that I couldn’t reasonably ask someone else to do a bunch of work just because I felt like it was important.

    My goal was to create a set of grammars that had the performance and accuracy upsides of the Tree-sitter experience and the rich scope name annotations that TextMate grammars provided.

    For that to be possible, Tree-sitter grammars should apply scope names as similarly as possible to TextMate grammars. There may be reasons that we choose not to do something the way that the TextMate grammar does it, but we don’t want to be unable to do something the way that the TextMate grammar does it.

    Syntax highlighting was the first, and hardest, task. Next time I’ll talk about how we solved it.

    + + + diff --git a/blog/20231004-Daeraxa-OctoberUpdate.html b/blog/20231004-Daeraxa-OctoberUpdate.html new file mode 100644 index 0000000000..301a84468c --- /dev/null +++ b/blog/20231004-Daeraxa-OctoberUpdate.html @@ -0,0 +1,278 @@ + + + + + + + + Community Update | + + + + + + +

    Community Update

    DaeraxaOctober 4, 2023
    • news
    • log
    • update
    About 6 min

    As the leaves turn brown and the days grow shorter, make sure you draw up a chair and settle in for a nice, warm, pumpkin spice edition of the Pulsar community update!

    Welcome to the October Community Update!

    A warm welcome to this edition of our monthly blog post where we tell stories about what the Pulsar team and community have been up to in the world of Pulsar. This month we have a real mixed bag of things from an initial announcement of our "Pulsar Cooperative" initiative, a large code refactor of PPM, an explanation of some recent macOS signing issues and the appearance of Pulsar on Shields.io and GitHub Desktop! Enough with the prelude and let's get on with the update!

    Introducing Pulsar Cooperative!

    One of our main reasons for trying to continue the Atom editor project (rather than moving to another editor or making one from scratch) was to preserve the huge number of community packages that had been created for Atom over the years. Unfortunately, as development slowed on Atom (and especially when the sunsetopen in new window was announced), some maintainers seem to have either archived their packages or are no longer looking to maintain them.

    The problem is that some of the more popular packages are in this exact situation and we often see members of the community asking about an issue with a package, discovering the fix and implementing the fix locally, but for whatever reason the package maintainer no longer wishes to maintain that package with Pulsar in mind.

    Of course, it is always possible to fork the package and take over maintenance yourself, but not everyone wants to, or may be in a position to, maintain a package. Nor does the core Pulsar team have the ability to maintain all of these community packages as well as the main Pulsar project.

    So we have come up with what we hope will be a viable solution. Pulsar Cooperative. This is a new organization that we have set up to allow joint ownership of packages. The idea is thus:

    • A package repository is archived, not accepting PRs or is not publishing any updates to the PPR (Pulsar Package Repository).
    • Somebody would like to use this package and is able to submit code to fix or improve the package but does not wish to take on sole responsibility for the overall maintenance of the package.
    • So instead, a request is made and the package is forked to the Pulsar Cooperative organization where bug fixes and new features can be added by any community member and so long as those fixes and features do not fundamentally change the package, introduce malicious code, or otherwise break functionality, they will be accepted and a new release published automatically.

    We hope to introduce a place where people can cooperatively work to make the Pulsar ecosystem better without having to worry about violating the strict vision for a package and not having their fix even accepted. It is also a place where people can submit fixes and make their favourite packages functional again without necessarily having to take on full responsibility for the ongoing maintenance.

    This is not a "Pulsar team" project in the sense that members of the main organization are going to be providing bug fixes or updates in response to logged issues (of course they are free too, just like any other community member). This is an entirely community-led endeavour and we hope everyone will be encouraged to get involved!

    This isn't ready just yet, but look out for an update right here on the blog (or any of our other community areas) once we are fully live and ready!

    Modern Tree-sitter blog posts

    [1]

    If you hadn't already seen or read them, @savetheclocktoweropen in new window has been writing a series of blog posts that go into detail about all the work that has been going on in Pulsar to support a modern implementation of Tree-sitteropen in new window. Parts oneopen in new window and twoopen in new window are available right now and keep an eye on the Pulsar blog for more updates!

    Converting PPM's code from callbacks to async

    Community member @Nemokosch/@twocoloursopen in new window  has been spearheading the conversion of our PPM codebaseopen in new window to upgrade old JavaScript callback style to modern ES6 async/await.

    The PPM repo, while modern and accepted when written, is now mostly viewed as being outdated due to the pervasiveness of deeply nested callbacks. In some instances having nesting six levels deep, and as anyone who's been programming in JavaScript prior to ES6 knows, this can be cumbersome, difficult, and frustrating to work with. That is exactly why the effort has been made to convert the entire codebase into a shallow nested, early return patterned, modern ES6 source utilizing async/await properly. In hopes that a greatly simplified codebase can pave the way for easier modifications in the future.

    To show what we are talking about, take this (simplified but real) code as an example:

    From this:

    this.registerPackage(pack, (error, firstTimePublishing) => {
    +	if (error != null) {
    +		return callback(error);
    +	}
    +
    +	this.renamePackage(pack, rename, (error) => {
    +		if (error != null) {
    +			return callback(error);
    +		}
    +
    +		this.versionPackage(version, (error, tag) => {
    +			if (error != null) {
    +				return callback(error);
    +			}
    +
    +			this.pushVersion(tag, pack, (error) => {
    +				if (error != null) {
    +					return callback(error);
    +				}
    +
    +				this.waitForTagToBeAvailable(pack, tag, () => {
    +					if (originalName != null) {
    +						rename = pack.name;
    +						pack.name = originalName;
    +					}
    +					this.publishPackage(pack, tag, { rename }, (error) => {
    +						if (firstTimePublishing && error == null) {
    +							this.logFirstTimePublishMessage(pack);
    +						}
    +						return callback(error);
    +					});
    +				});
    +			});
    +		});
    +	});
    +});
    +

    To this:

    const firstTimePublishing = await this.registerPackage(pack);
    +await this.renamePackage(pack, rename);
    +const tag = await this.versionPackage(version);
    +await this.pushVersion(tag, pack);
    +
    +await this.waitForTagToBeAvailable(pack, tag);
    +if (originalName != null) {
    +	rename = pack.name;
    +	pack.name = originalName;
    +}
    +
    +try {
    +	await this.publishPackage(pack, tag, { rename });
    +} catch (error) {
    +	if (firstTimePublishing) {
    +		this.logFirstTimePublishMessage(pack);
    +	}
    +	throw error;
    +}
    +

    This has been a huge piece of work, which began because they overheard complaints about the PPM codebase being hard to read and understand due to its outdated style. As they had already previously assisted in PPM with the "decaffeination" of the codebase, they were already fairly familiar with it and thought they could help further by "upgrading" the codebase to this modern style.

    There has been a lot of focus on code flow during this conversion in order to make the codebase as easy to read and understand as possible for future contributors. Not only was the structure changed, but some code and modules could be removed entirely as their only purpose was to support the previous callback style.

    From a Pulsar user's perspective you shouldn't notice anything different at all. This is all about maintenance and modernization of the codebase. We want to make Pulsar as hackable and as easy to contribute as possible and these kinds of efforts go a long way to achieving that goal.

    So a big thank you again to [@Nemokosch] for working on this. You can see the progress in this pull requestopen in new window

    macOS binary signing issues

    We discovered an issue recently where it turned out that our macOS binaries weren't being signed by our CI process. After some investigation and changes to the CI environmentopen in new window by @DeeDeeGopen in new window, @confused-techieopen in new window and @Meadowsysopen in new window, the binaries are now correctly signed again.

    This issue would only have affected the 1.109.0 releases; the current and upcoming releases will all be signed as normal.

    Shields.io badges

    Thanks to @confused-techieopen in new window and the team at Shields.ioopen in new window, badges for Pulsar packages are now available! This means that if you make a package for Pulsar and publish it to the Pulsar Package Repository, you can now display download and stargazer stats on your README page.

    Stats can be shown both for downloadsopen in new window and stargazersopen in new window. Here is an example of a downloads badge for x-terminal-reloadedopen in new window:

    Pulsar Downloads

    So go grab your badges now to show off your package's stats!

    Pulsar on GitHub Desktop

    For those of you who use GitHub Desktopopen in new window you will now find (or will soon find) that Pulsar is available as an option for you to select as your editor of choice!

    Thanks to @mdibella-devopen in new window for adding this for the macOS version, @confused-techieopen in new window for Windows and @Daeraxaopen in new window for the Linux version!

    Community spotlight

    This month we want to give special attention to @Nemokosch/@twocoloursopen in new window with their changes to the PPM codebase already discussed on this update. We love to see these kinds of contributions to Pulsar!


    And that wraps things up for this month. We hope you have enjoyed reading about all these changes and updates to Pulsar-related areas. As always, a huge thank you to all of our wonderful community members and a special thanks to all those who donate to the project, which makes this all possible. We hope to see you here again next month!


    1. Image from https://tree-sitter.github.io/tree-sitter/open in new window - Copyright (c) 2018-2021 Max Brunsfeld ↩︎

    + + + diff --git a/blog/20231013-savetheclocktower-modern-tree-sitter-part-3.html b/blog/20231013-savetheclocktower-modern-tree-sitter-part-3.html new file mode 100644 index 0000000000..b441b9f189 --- /dev/null +++ b/blog/20231013-savetheclocktower-modern-tree-sitter-part-3.html @@ -0,0 +1,322 @@ + + + + + + + + Modern Tree-sitter, part 3: syntax highlighting via queries | + + + + + + +

    Modern Tree-sitter, part 3: syntax highlighting via queries

    savetheclocktowerOctober 13, 2023
    • dev
    • modernization
    • tree-sitter
    About 16 min

    Last time I laid out the caseopen in new window for why we chose to embrace TextMate-style scope names, even in newer Tree-sitter grammars. I set a difficult challenge for Pulsar: make it so that a Tree-sitter grammar can do anything a TextMate grammar can do.

    Today I’d like to show you the specific problems that we had to solve in order to pull that off.

    If you’d like to follow along with these code examples in your own Pulsar installation, I’d suggest installing the tree-sitter-tools packageopen in new window. It includes a language grammar for the Tree-sitter query files we’ll be spending most of this article writing, and it lets us visualize the syntax trees that Tree-sitter produces.

    If you just want to get a sense of what a Tree-sitter tree looks like, you can use the Playground on the Tree-sitter web siteopen in new window.

    The problems

    The legacy Tree-sitter integration into Atom used its own system for mapping tree nodes to scope names, but it had two major limitations that prevented it from matching the scope names produced by a TextMate grammar:

    1. It couldn’t query the right nodes: it used a CSS-like syntax that was limited in how expressively it could describe tree nodes.
    2. It couldn’t describe the right ranges: it could only add scopes to ranges that corresponded to individual tree nodes.

    The need to write an Atom-specific bridge between node names and scope names served as evidence that Tree-sitter need its own system for more easily working with syntax trees — one that would prevent every Tree-sitter consumer from having to reinvent the wheel.

    So Tree-sitter added a powerful query systemopen in new window, with a Lisp-like syntax most directly influenced by Schemeopen in new window. This system wasn’t around for the legacy implementation to use — but it’s here for us now, and it’s going to make our job much easier.

    Tree-sitter query syntax does the same thing for a Tree-sitter syntax tree that CSS selector syntax does for HTML: it gives us a terse way to describe a set of nodes in a tree. And just as people eventually realized that CSS selector syntax was useful for more than just styling — see document.querySelectoropen in new window — you’ll soon see that Tree-sitter queries are useful for more than just making strings green.

    Right now, though, let’s just focus on syntax highlighting. And remember those two limitations that I described above, because we’ll have to solve them both before this article is done.

    The first challenge: robust querying

    I’ll remind you of our example from part 2: the scope names applied to a double-quoted string in JavaScript.

    string scopes

    Our first goal is to make it so that our JavaScript Tree-sitter grammar can apply these same scopes to the same buffer ranges. But Tree-sitter works very differently to a TextMate grammar, so it’s not immediately obvious how we can pull this off. Let’s reason through it.

    How captures work

    Tree-sitter itself has demonstratedopen in new window how query files can be used to highlight code. If a parser has a highlights.scm file defined in its repository, the CLI will allow you to run tree-sitter highlight on arbitrary input. It’ll parse the input, figure out which parser should do the job, use that parser’s highlights.scm to map certain nodes to query capture names, and then emit highlighted output in your terminal.

    Last time I showed you this excerpt from tree-sitter-javascript’s highlights.scm file:

    [
    +  (string)
    +  (template_string)
    +] @string
    +

    Once you’re familiar with query syntax, the outcome of this query is clear: all JavaScript strings will be captured and given a name of @string. Somewhere within the tree-sitter highlight code path, those capture names are mapped to various colors, and are applied to the captured buffer ranges. Anything that gets captured as @string will have one color in the output; anything that gets captured as @keyword will have a different color; and so on.

    Let’s imagine that Pulsar has a similar system. In order to keep this article from putting you to sleep, I won’t get into the details of exactly how we do it, but the machinery is in place. Instead of a capture name like @string, we’ll be choosing more verbose names like @string.quoted.double.js, but the principles are the same.

    We also won’t be talking about how Pulsar knows which areas of the buffer to re-highlight as the user makes changes, nor how Pulsar combines the results of multiple parsers (for example, JavaScript embedded in HTML). These topics may be visited in future articles, but today we’re just talking about how to use Tree-sitter queries to identify arbitrary ranges and give them the scope names that we want.

    Highlights

    What does a string look like in a Tree-sitter tree? Let’s create a new document with nothing but the following contents:

    "like this";
    +

    Using the tree-sitter-tools packageopen in new window, I can open an inspector pane and look at the raw tree for this string:

    string tree

    So we can see that a string node consists of three parts: a delimiter, a string_content node, and another delimiter. This structure maps elegantly to the things that we want to scope.

    Most parsers build abstract syntax treesopen in new window. In the process of making the tree, they discard lots of information that isn’t important to the parser’s goal. But Tree-sitter builds concrete syntax trees! Every character in our JavaScript file will end up being represented by at least one node, and every node remembers the exact buffer range it covers. If it didn’t, we wouldn’t be able to use it for syntax highlighting.

    In Tree-sitter parsers, nodes that matter to semantics (like string_content) tend to have names, whereas other nodes (like the delimiters) are “anonymous” nodes. Anonymous nodes can still be queried against like named nodes, so it feels like it’ll be pretty easy to apply the scopes that we want.

    So let’s open our grammar’s highlights.scm file and give it a try. (In Pulsar’s “dev mode” — which you can trigger with the --dev flag on the command line — grammar authors can even make changes to their query files and see them take effect instantly when they save!)

    (string) @string.quoted.double.js
    +

    That’s not bad, but it’s too specific. In tree-sitter-javascript, the string node applies for both single-quoted and double-quoted strings, with the difference being reflected only in the anonymous nodes. (Template strings, as we saw above, have their own node type.)

    How do we distinguish single-quoted strings from double-quoted strings? Here’s one thing we could do:

    ((string) @string.quoted.single.js
    +  (#match? @string.quoted.single.js "^'"))
    +((string) @string.quoted.double.js
    +  (#match? @string.quoted.single.js "^\""))
    +

    The built-in #match? predicate allows us to reject possible matches when their contents don’t match a given regular expression. Here we’re telling the query engine to distinguish between string nodes whose text starts with ' and those whose text starts with ".

    We’ll be using the #match? predicate a lot. Unlike some of the other predicates we’ll see shortly, it’s implemented by the web-tree-sitter bindings, so Tree-sitter on its own is able to reject would-be captures that don’t pass it. By the time we see the list of captures, those that have failed a #match? test have already been filtered out.

    In this case, though, there’s an even easier way to tell these strings apart:

    (string "'") @string.quoted.single.js
    +(string "\"") @string.quoted.double.js
    +

    As I mentioned, we can query for the presence of anonymous nodes. So the first line will match any string that contains at least one anonymous node child named ', and the second line will match any string that contains at least one anonymous node child named ".

    Since the capture name is on the outside of the closing parenthesis, the capture name applies to the whole string.

    We don’t have to be more specific; if that anonymous node exists at all, then it’s used as a delimiter on both sides of the string. And this query won’t match any potential false positives — like a double-quoted string that happens to have a ' somewhere inside of it — because the parser is too smart to get tripped up by that sort of thing.

    This is one reason why it’s very easy to make a new Tree-sitter grammar if someone has done the work of writing a Tree-sitter parser for the given language. If we were writing a TextMate grammar, we’d have to care about a lot more of these edge cases, but a Tree-sitter parser will have handled them for us already.

    Scope tests

    We can already tell that the expressiveness of Tree-sitter’s query system will go a long way toward solving the first of the two problems we described above. Last time around, Atom developers had to invent a system for querying the tree, but we get a much more powerful system for free.

    Tools like anonymous nodes and #match? predicates can get us quite far on their own, but they can’t solve all of our problems. We still have to scope the quotation marks themselves, and we may think we know how to do it:

    (string "'" @punctuation.definition.string.begin.js)
    +

    By putting the capture name immediately after the "'", we can target that anonymous node and give it a name. But remember that there are two delimiters! We want to give one scope name to the beginning delimiter and a different scope name to the ending delimiter. As we’ve written it, this rule would match both delimiters.

    Surprisingly, we can’t solve this problem without some external help. Tree-sitter queries have a concept of “anchors”open in new window that can enforce positioning of children — for instance, targeting only the first node child of a parent — but they can be used only for named nodes, not anonymous nodes. We need a way to introduce our own filtering criteria into Tree-sitter queries.

    Luckily, Tree-sitter gives us the tools to write our own predicates. Instead of trying to make it aware of our application-specific concerns, we can use the generic predicates #is? and #is-not? to mark certain query captures with data, then use that data to filter the results however we like.

    The downside is that Tree-sitter can’t approve or reject captures with these predicates on its own like it can with #match?. Instead, we’ll have to “process” these captures after the fact and filter them manually. But it’s worth the effort because it lets us use whatever logic we want — even Pulsar-specific logic that means nothing to Tree-sitter.

    Let’s illustrate.

    (string
    +  "'" @punctuation.definition.string.begin.js
    +  (#is? test.first "true"))
    +

    The two parameters after #is? are arbitrary values of my own invention. Tree-sitter simply treats them as a key and value and applies some metadata to this capture. Any nodes that get captured by this query will contain some data under assertedProperties:

    capture.assertedProperties["test.first"]; //-> "true"
    +

    In fact, I can omit that second argument; for boolean tests like this one, the presence of the property is all we need:

    (string
    +  "'" @punctuation.definition.string.begin.js
    +  (#is? test.first))
    +(string
    +  "'" @punctuation.definition.string.end.js
    +  (#is? test.last))
    +

    Using (#is? test.first) is like having Tree-sitter attach a Post-It note to a capture object with the text “remember to assert test.first later” written on it. Tree-sitter doesn’t know or care what that means, but it assumes we will.

    And in this case, test.first corresponds to a function we’ve written that looks like this:

    function first(node) {
    +	if (!node.parent) return true;
    +	return node?.parent?.firstChild?.id === node.id;
    +}
    +

    This function first ensures that root nodes (which have no parent) will always pass the test. Then it compares our node to its parent’s first child. If they’re equal, the test passes. If they’re not, then the captured node isn’t the first child of its parent, and we can ignore it.

    The logic for our last function is identical, except that it compares our node to node.parent.lastChild.

    Even better: the existence of #is-not? means that we get negation practically for free. Suppose I did this:

    (string
    +  "'" @punctuation.definition.string.end.js
    +  (#is-not? test.first))
    +

    Then the metadata would exist in a different place…

    "test.first" in capture.refutedProperties; //-> true
    +

    …and I’d know to ignore this capture unless my first function fails for this node.

    So now we’ve got a way to filter capture names by any criteria we can think of. If we can test for it in JavaScript, it can be used as a predicate in Tree-sitter queries.

    We call these custom predicates scope tests, and we’ve grouped them under a test. namespace for reasons that may make more sense later. Scope tests are a crucial tool for solving the first of those two problems we described earlier: they let us query for tree nodes in arbitrary ways that the legacy system simply couldn’t.

    And because scope tests are just JavaScript, we’re able to use some oddball criteria for accepting or rejecting captures. Consider these examples:

    ((program) @source.js.embedded
    +  (#is? test.injection))
    +
    +(variable_declarator
    +  name: (identifier) @constant.other.foo
    +  (#match? @constant.other.foo "^[_A-Z]+$")
    +  (#is? test.config "language-foo.highlightAllCapsIdentifiersAsConstants"))
    +

    Both of these are scope tests that grammar query files can use. The first one applies a scope only if we’re in an injection layer — for instance, if this is JavaScript inside of a SCRIPT tag in an HTML file. The second one applies a scope only if the user has enabled a certain configuration option. Neither one has anything to do with Tree-sitter itself, but we can use them in Tree-sitter query files all the same.

    The logic for applying tests and winnowing a list of raw captures exists in Pulsar in a class called ScopeResolver. That first function defined above is present at ScopeResolver.TESTS.firstopen in new window, and there’s logic in ScopeResolver that matches up a test.first property to that function.

    There are other scope tests that we’ve found to be quite useful — tests which any grammar author can use in their own query files:

    • Whether a node has a certain kind of node as an ancestor
    • Whether a node has a certain kind of node as a descendant
    • Whether a node is the first/last non-whitespace content on a row
    • Whether a node has arbitrary metadata that has been attached with Tree-sitter’s #set! predicate

    But for our current goal — applying punctuation scopes to string delimiters — implementing test.first and test.last is enough to get us the outcome we want.

    You may remember from earlier that the legacy Tree-sitter integration used a CSS-like syntax to describe nodes. It supported combinators like > and even pseudoclasses like :nth-child, but not much else. Tree-sitter’s own query system can do much more than that — and it’s extensible, so we can add our own logic wherever we need it.

    So we’ve done it! We now have the ability to scope a JavaScript string identically between our two different grammar systems. And we’ve moved past the legacy system’s first drawback: a brittle query system. Our first challenge has been vanquished.

    The second challenge: scoping arbitrary ranges

    To solve the second drawback — inability to scope the correct ranges — let’s create another challenge.

    TextMate grammars will scope comments differently based on whether they’re line comments or block comments. There’s also a convention to annotate comment.line scopes with their delimiter type:

    // this is a comment
    +

    A typical TextMate grammar for JavaScript would scope this comment as comment.line.double-slash.js, and would further scope the // as punctuation.definition.comment.js.

    comment scopes

    Can we do that in a Tree-sitter grammar with the tools we’ve already got? Let’s inspect what our example comment looks like in a Tree-sitter tree:

    comment tree

    Hmm. No anonymous nodes or anything. Just one node called comment.

    It’s a strong Tree-sitter convention that various sorts of code comments are all represented by nodes called comment. So this query would do the right thing in our example…

    (comment) @comment.line.double-slash.js
    +

    …but would be incorrect in other scenarios because it would match block comments as well as line comments.

    But we can use #match? to distinguish between the two kinds of comments:

    ((comment) @comment.line.double-slash.js
    +  (#match? @comment.line.double-slash.js "^\/\/"))
    +
    +((comment) @comment.block.js
    +  (#match? @comment.block.js "^\/*"))
    +

    In the latter case, we don’t have to test for the presence of */ at the end. If the contents of the comment begin with /*, that’s all the information we need; we know it must end with */, or else the parser wouldn’t have classified it as a comment.

    But what about our punctuation.definition.comment.js scope? Sadly, tree-sitter-javascript (and most other parsers) don’t make it easy to target the comment delimiters themselves. Comment delimiters, unlike string delimiters, usually aren’t available as anonymous nodes.

    Hence the legacy Tree-sitter system has never been able to annotate that // with the punctuation scope it needed. We’ll need to solve this ourselves.

    Scope adjustments

    In this case, it’d be more convenient if there were a node for the // we want to scope. But we control the internals of the editor, and we can tell it to apply scope names to whatever buffer ranges we want. When the tree doesn’t do all of our work for us, it just means we have to try a bit harder.

    In this case, the comment node tells us where the comment starts in the buffer. And once it passes the #match? predicate, we know that it starts with //, so it must end two characters later. Not the hardest problem to solve!

    The trickiest part will be figuring out how to describe these buffer positions in a query.

    Adjusting by pattern

    What if we did this?

    ((comment) @comment.line.double-slash.js
    +  (#match? @comment.line.double-slash.js "^//"))
    +
    +((comment) @punctuation.definition.comment.js
    +  (#match? @punctuation.definition.comment.js "^//")
    +  (#set! adjust.endAfterFirstMatchOf "^//"))
    +

    Here we’re capturing the same thing twice under different names. In the second case, we’re using #set! — a predicate very similar to #is? and #is-not? — to attach a qualifier: instead of stopping at the end of the line comment, stop right after the // near the beginning.

    Just like #is? predicates are represented on captures under assertedProperties and #is-not? predicates are represented under refutedProperties, #set! predicates are represented in their own bucket simply called properties.

    Note the semantic difference between a predicate that ends in ! and one that ends in ?. In this case we’re not setting up a test for the capture to pass or fail; we’re attaching a side effect to the capture. Imagine a Post-It note attached to the capture that says “remember to adjust the range for this capture to end at X.”

    I mentioned earlier that all nodes remember their corresponding buffer range — starting at row W and column X, ending at row Y and column Z. By default, this is the range against which a scope is applied. But in this case, the adjust.endAfterFirstMatchOf predicate reminds us to execute a regular expression match on the node’s contents and move the ending position to the end of that match, instead of the node’s natural ending point.

    So in the absence of any node boundary at the position we want, we’ve found a rather simple alternative way to express that position. We still have to write our own codeopen in new window to make it happen, but that won’t be a problem. Since we already have to loop through our captures to apply scope tests, we might as well use that opportunity to tweak the range of a capture if we need to.

    And how would we handle the delimiters of a block comment?

    ((comment) @comment.block.js
    +	(#match? @comment.block.js "^/\\*"))
    +
    +((comment) @punctuation.definition.comment.begin.js
    +  (#match? @punctuation.definition.comment.begin.js "^/\\*")
    +  (#set! adjust.endAfterFirstMatchOf "^/\\*"))
    +
    +((comment) @punctuation.definition.comment.end.js
    +  (#match? @punctuation.definition.comment.end.js "\\*/$")
    +  (#set! adjust.startBeforeFirstMatchOf "\\*/$"))
    +
    +

    We can scope the opening delimiter the same way. To scope the ending delimiter, we move the head of the range to the position at the beginning of a regex match. (And for situations where you want to move both the head and the tail at once, there’s adjust.startAndEndAroundFirstMatchOf).

    Adjusting by node position descriptor

    Let’s look at another example.

    function SomeComponent(props) {
    +	return <SomeOtherComponent {...props} />;
    +}
    +

    Let’s say we want to scope the /> at the end of the self-closing tag. Tree-sitter represents that as two separate anonymous nodes — / and >.

    So we’ve got a different problem here: there’s no single node that includes both boundaries of the range we want to scope. How can we make the scope span two adjacent nodes?

    We could probably use a pattern-based solution here like we did above. But we could also leverage a useful feature of how nodes are represented in the tree.

    You might be familiar with how DOM nodes in the browseropen in new window are traversible via properties like parentNode, nextSibling, and so on. If you’ve got a reference to a particular DOM node, you can use those properties to jump from that node to any other node in the tree, as long as you you know how the two nodes are related.

    A similar system exists for Tree-sitter nodes, and it gives us a simple way to describe relationships between nodes:

    ((jsx_self_closing_element
    +  ; The "/>" in `<Foo />`, extended to cover both anonymous nodes at once.
    +  "/") @punctuation.definition.tag.end.js
    +  (#set! adjust.startAt lastChild.previousSibling.startPosition)
    +  (#set! adjust.endAt lastChild.endPosition))
    +

    Here we’re capturing the entire self-closing element, then moving the ends of the range to the specific boundaries that we need: starting at the beginning of second-to-last node child, and ending at the end of the last node child. The / and > will always be the last two children of the node we captured, so this is a simple and repeatable way to describe the boundaries we want.

    This syntax for describing a position relative to a given node is something we call a node position descriptor. If you’ve ever used a function like Lodash’s _.getopen in new window, you might feel at home with this syntax — it resolves a chain of property lookups all at once, and fails gracefully if any of them aren’t present. It’ll come in handy later for other purposes.

    So in order to pass our “JavaScript line comment” challenge, we’ve had to invent another feature called scope adjustments. With the infrastructure we’ve already got, scope adjustments are an easy thing to add: a scope adjustment is just a function that accepts a capture object and returns a range. We process them in the post-capture phase immediately before we apply scope tests.

    Scope adjustments are our answer to the legacy system’s second limitation. They allow us to embrace the syntax tree when it works in our favor, but still break free of it whenever we need to.

    What does this get us?

    Some of you, having gotten this far, might wonder to yourselves: if it takes this much effort just to get feature parity with a TextMate grammar, why not just stick with TextMate grammars?

    One answer is simple: despite how complex this may seem, and how many implementation details I’ve hidden from you, the effort it’s taken to integrate Tree-sitter grammars into Pulsar is much, much less than the effort it originally took to integrate TextMate grammars into Atom.

    But the message here can’t just be “we implemented a cool new thing that you won’t notice at all!” The whole point is that this is better than — not just equivalent to — what we had before, and not just for the few of us who write grammars.

    So let me give you an example of something that was very hard with a TextMate grammar, but is now quite easy.

    When a scope name might be useful for semantic reasons, but not for syntax highlighting reasons, TextMate grammars add scope names that use the meta namespace. You might see some meta scope names when you run the “Log Cursor Scope” command, but most of them don’t have an effect on how your code looks. Yet that information is visible to snippets, settings, and commands, and it can be very useful.

    In the language-javascript package, Pulsar defines a fun snippet that expands as follows, with the string functionName highlighted:

    function functionName() {}
    +

    That’s great, but there’s more than one syntax for defining functions. Imagine if you could use fun inside of a class body and have it expand to the correct syntax for defining an instance method:

    class Foo {
    +	functionName() {}
    +}
    +

    Or inside of an object literal — much like the class body syntax, but with a trailing comma so as to prevent syntax errors:

    const Foo = {
    +	functionName() {},
    +};
    +

    TextMate grammars weren’t built with this sort of thing in mind. With much effort, you could probably pull it off, but only by making the grammar so complex that it might as well be a parser. But Tree-sitter is already a parser. It understands your code well enough to make these things easy.

    With our rich syntax tree, we can now apply meta scope names rather liberally, marking sections of the buffer with useful metadata that can be used by commands and snippets. We can scope the inside of a class body and the inside of an object literal:

    ; The interior of a class body.
    +((class_body) @meta.block.class.js
    +  ; Start after `{`…
    +  (#set! adjust.startAt firstChild.endPosition)
    +  ; …and end before `}`.
    +  (#set! adjust.endAt lastChild.startPosition))
    +
    +; The inside of an object literal.
    +((object) @meta.object.js
    +  ; Start after `{`…
    +  (#set! adjust.startAt firstChild.endPosition)
    +  ; …and end before `}`.
    +  (#set! adjust.endAt lastChild.startPosition))
    +

    By defining these two scope names, we’ve exposed these concepts to all the systems that consume scope names, including snippets.

    The language-javascript package already defines fun one way, but we can redefine it for more specific scopes:

    '.source.js .meta.block.class.js':
    +  'Function':
    +    'prefix': 'fun'
    +    'body': '${1:functionName}($2) {\n\t$0\n}'
    +
    +'.source.js .meta.object.js':
    +  'Function':
    +    'prefix': 'fun'
    +    'body': '${1:functionName}($2) {\n\t$0\n},'
    +

    Now we’ve made the fun snippet much more useful. When we type fun and press Tab, the snippets package will pick the version that matches the context of the cursor most closely.

    This will work identically whether you use tab triggers or choose your snippets from an autocomplete menu.

    This change isn’t just theoretical; it’s been implemented in the language-javascript grammar package, and it shipped with Pulsar 1.109.

    Next time

    Syntax highlighting isn’t the only way that Tree-sitter’s query system can make our lives easier. In the next installment we’ll tackle two tasks that the legacy Tree-sitter integration never addressed: indentation and code folding.

    + + + diff --git a/blog/20231016-Daeraxa-v1.110.0.html b/blog/20231016-Daeraxa-v1.110.0.html new file mode 100644 index 0000000000..f1f6e11e31 --- /dev/null +++ b/blog/20231016-Daeraxa-v1.110.0.html @@ -0,0 +1,223 @@ + + + + + + + + Armed with a big ol' can of Raid: Pulsar 1.110.0 is available now! | + + + + + + +

    Armed with a big ol' can of Raid: Pulsar 1.110.0 is available now!

    DaeraxaOctober 16, 2023
    • dev
    • release
    About 4 min

    Armed with a big ol' can of Raid: Pulsar 1.110.0open in new window is available now!

    What do we have for you in Pulsar 1.110.0?

    Here we are with another Pulsar release, and this month we have quite a number of fixes and improvements. This time around, the focus has really been on bug fixes in order to improve the overall experience.

    Starting with changes to PPM (Pulsar Package Manager), it has been updated to use a newer version of node-gyp (a tool for compiling native modules for node.js), which will allow use of newer C/C++ compiler toolchains as well as newer versions of Python, namely 3.11, which introduced an issue for PPM requiring downgrading to 3.10. For Windows users, it also now supports Visual Studio 2022! We previously covered (part of) this topic in one of our community update blog postsopen in new window in a bit more detail, so be sure to have a read of that too.

    On the topic of PPM, the decaffeination (conversion of CoffeeScript to JavaScript) has now been completed thanks to community members @nemokosch/@twocoloursopen in new window & @GuilleWopen in new window. In the last releaseopen in new window, we announced this had been completed in the core editor and packages; now this has been extended to PPM!

    Onto Pulsar itself: we have a few new features that have been added. The first is a new autocomplete API that works on ranges rather than the previous prefix system, which will improve LSP support. (And on the topic of autocomplete, if anyone had been editing EJS files and noticed errors popping up, these have now been greatly reduced in this update).

    Onto the fixes. This first one solves an issue where, if you attempted to start Pulsar with an invalid configuration file, then it would silently fail but still present a running process. Error checking has now been added so that the error can actually be exposed to the user.

    Next, we have a problem introduced with our snippets package update, which now includes variables indicated by a $, which is also used by PHP, so some escaping of these characters needed to be added.

    And lastly, we had a problem with macOS binary signing in version '1.109.0'; this was already covered in the last community updateopen in new window, and now this fix applies to our regular releases.

    And that just about covers things for this release. We hope you have enjoyed reading this and continue to enjoy using Pulsar. As ever, a huge thank you to our generous donors and community, without whom this project would not be possible.

    Until next time, happy coding, and see you amongst the stars!

    • The Pulsar Team

    • Made the modification of editor.preferredLineLength configurable within wrap-guide when changing wrap-guide.columns
    • Fixed Snippets from language-php that would lose the $ character
    • Fixed a condition where an invalid config may crash Pulsar before fully starting up, but not registering that it's crashed
    • Reduced error notifications that may appear from autocomplete-html when handling EJS files
    • Fixed macOS binary signing after moving over to GitHub Actions for CI
    • Updated PPM to a newer node-gyp, allowing newer versions of C/C++ compiler toolchains and Python to be used (also dropped support for Python 2.x!)
    • Fully decaffed the entire PPM codebase
    • Added a new autocomplete API that does not uses prefixes and instead declares the range it'll replace (better LSP support)

    Pulsar

    ppm

    + + + diff --git a/blog/20231031-savetheclocktower-modern-tree-sitter-part-4.html b/blog/20231031-savetheclocktower-modern-tree-sitter-part-4.html new file mode 100644 index 0000000000..33b1848be3 --- /dev/null +++ b/blog/20231031-savetheclocktower-modern-tree-sitter-part-4.html @@ -0,0 +1,308 @@ + + + + + + + + Modern Tree-sitter, part 4: indentation and code folding | + + + + + + +

    Modern Tree-sitter, part 4: indentation and code folding

    savetheclocktowerOctober 31, 2023
    • dev
    • modernization
    • tree-sitter
    About 13 min

    Last time we looked at Tree-sitter’s query system and showed how it can be used to make a syntax highlighting engine in Pulsar. But syntax highlighting is simply the most visible of the various tasks that a language package performs.

    Today we’ll look at two other systems — indentation hinting and code folding — and I’ll explain how queries can be used to support each one.

    Indentation

    Our programmer predecessors might scoff at how much our editors coddle us, but I’ll say it anyway: I hate it when my editor doesn’t know when to indent. In particular, C-style languages like JavaScript have predictable and simple rules about indentation, so when an editor guesses wrong, it’s like a burst of static in the middle of a song.

    If I type

    if {
    +

    and press Return, all major code editors will indent the next line for me by one level. There’s no reason for an editor to force me to press Tab myself — it’s obvious from context.

    And once I’m done with the conditional and type } on its own line, my editor should recognize that this line isn’t part of the indented block, and should decrease the indentation by one level automatically.

    How does Pulsar do this now? And how can we swap in our own system for doing it with Tree-sitter?

    How TextMate grammars do it

    Pulsar, like Atom before it, uses an indentation hinting system based on the system from TextMate grammars. It works a bit like this:

    • Use the previous line’s content to decide whether a new line is indented. When a user presses Return, a TextMate grammar will test the line the cursor was just on against a regular expression called increaseIndentPattern. If there’s a match, it concludes that the next line should start with an extra level of indentation.
    • Use a line’s own content to decide when it should be dedented. Since only certain kinds of content — like } or end — signify the end of a block, a TextMate grammar will test the current line against a regular expression called decreaseIndentPattern over and over as the user types. If that pattern ever matches, the current line will be dedented one level.

    For JavaScript, imagine that increaseIndentPattern can be described as “an opening brace without a matching closing brace,” and decreaseIndentPattern can be described as “a closing brace that is not preceded by an opening brace.” Each pattern would probably need to match more than just those situations, but that’s the most important pattern to recognize by far.

    Let’s look at that screencast again to see these rules in action:

    The first rule comes into play when we press Return at the end of line 2. A newline is inserted, and the cursor is correctly indented by one additional level.

    The second rule reacts after we type a } on line 4: now the line’s content matches decreaseIndentPattern, and the line is dedented automatically.

    How legacy Tree-sitter grammars do it

    The same way. No, seriously.

    It was obviously meant to be temporary, but legacy Tree-sitter grammars rely on the same increaseIndentPattern and decreaseIndentPattern that TextMate grammars use. We should come up with a Tree-sitter–based system instead.

    TextMate’s deep embrace of regular expressions is a double-edged sword: it makes simple things easy, but it makes complex things nearly impossible. Instead of testing lines against a single regular expression — which can quickly get unwieldy as it expands to handle all possible indentation hinting scenarios — we can use Tree-sitter’s query system to identify code features that would affect indentation.

    @mauricioszaboopen in new window had noticed Neovim’s prior art hereopen in new window, and had started to implement a similar system, so it was easy to pick up where he left off.

    Working within the system

    We could’ve just adopted Neovim’s system wholesale. But I wanted a system that kept the same decision points as the TextMate system for conceptual simplicity. So we’ve built a system that will abide by the contract described above…

    1. To figure out whether to indent a line, look at the content of the line above it.
    2. To figure out whether to dedent a line, look at the content of that line itself.

    …except using its own logic:

    1. When a user presses Return, instead of checking the previous line against increaseIndentPattern, we’ll instead run a query capture against the previous line and look at the results to figure out whether to indent the new line.
    2. When a user types on the current line, instead of checking it against decreaseIndentPattern, we’ll run a query capture against that line with each keystroke and look for results that imply we should dedent that line relative to the line above.

    Remember that Tree-sitter produces concrete syntax trees; every node represents an actual buffer range. The things that are typically stripped from abstract syntax trees, like punctuation, are still present in a Tree-sitter tree, and can be queried. That’s good news for us: looking at punctuation is a great way to predict how lines should be indented.

    So let’s start here. Imagine this content exists in a file called indents.scm:

    "{" @indent
    +"}" @dedent
    +

    This is maybe the simplest possible system for describing indentation in a C-style language. For some languages, like CSS, this gets us 99% of the way to a perfect indentation system. But how does it work?

    If you’re following along with tree-sitter-toolsopen in new window, you can visualize it:

    1. First, make sure you’ve enabled modern Tree-sitteropen in new window.
    2. Open a new document, change the grammar to CSS, and type some sample CSS.
    3. Run the Tree Sitter Tools: Open Inspector For Editor command.
    4. Toggle the “Anonymous nodes” option to Show.
    5. Paste that code block into the appropriate text box and click on Run Query.

    tree-sitter-tools indentation example

    You can see the query matches illustrated in the text editor in real time, and you can match up the colors to the @indent and @dedent captures. You can even type new content (as in the examples below) and see the captures update in real time!

    Let’s say the user is writing a CSS file, and the cursor is represented by the | character:

    body {|
    +

    If the user were to press Return, we’d run a query capture on a specific range of the buffer: from the start of row 1 to wherever the cursor was. The opening brace on row 1 would produce a single capture called @indent. Based on that information, we’d know that row 2 should be indented by one level.

    But what if the file looked like this instead?

    body { font-family: 'Helvetica', sans-serif; }|
    +

    If the user were to press Return, we’d run the same query capture, and it would match twice: one @indent capture and one @dedent capture. Those captures would cancel each other out. We’d know that the opening brace we saw had already been balanced by a later closing brace, and we’d know that row 2 should not increase its indentation level.

    Now let’s look at one more example:

    body {
    +  font-family: 'Helvetica', sans-serif;
    +  |
    +

    After typing one property-value pair and pressing Return, we’re on row 3. Should this line be dedented? It depends on what we’re about to type! If we’re about to type }, then the answer is yes — but if we’re typing anything else, like another property-value pair, then the answer is no. That’s why Pulsar won’t decide whether to dedent until we start typing. If we were to type }, our Tree-sitter grammar would run a query capture on the current line, spot the @dedent match, and respond by dedenting the current line one level from the line above.

    For more complex C-style languages like JavaScript, here’s a better starting point for indents.scm:

    ; Any of these characters should trigger indent…
    +[ "{" "(" "[" ] @indent
    +
    +; …and any of these should trigger dedent.
    +[ "}" ")" "]" ] @dedent
    +

    There are major simplicity benefits to targeting these anonymous nodes instead of more abstract nodes. Most folks’ indentation styles tend to align with delimiter usage, so the more tightly we can bind to the delimiters themselves, the more accurate the hinting will be. And anonymous nodes are safer to rely upon as the user types and the syntax tree is in flux. Sometimes we have to “filter out” false positives — for instance, curly braces in JavaScript that signify template string interpolations instead of statement blocks — but that’s a small price to pay.

    I’m hiding some of the complexity from you, but less than you’d think. This is a much friendlier way to describe indentation hinting than making a grammar author maintain an ever-more-complex set of regular expressions. It allows the author to describe each kind of indentation hint as its own rule.

    Getting creative

    And it allows us to do some more complex things that weren’t possible before.

    TextMate’s system will let us indent or dedent one level at a time. But consider a switch statement:

    switch (foo) {
    +	case "bar":
    +		// one thing
    +		break;
    +	case "baz":
    +		// another thing
    +		break;
    +	default:
    +	// a third thing
    +}
    +

    Between the second-to-last line and the last line, there’s a two-level change in indentation. How can we express that?

    Here’s where our syntax tree comes in handy. Instead of describing our desired indentation level relative to the previous line, we can describe it relative to a line of our choosing:

    ; The closing brace of a switch statement's body should match the indentation
    +; of the line where the switch statement starts.
    +(switch_statement
    +  body: (switch_body "}" @match)
    +  (#set! indent.matchIndentOf parent.startPosition))
    +
    +; 'case' and 'default' need to be indented one level more than their containing
    +; `switch`.
    +(["case" "default"] @match
    +  (#set! indent.matchIndentOf parent.parent.startPosition)
    +  (#set! indent.offsetIndent 1))
    +
    +["case" "default"] @indent
    +

    Here we’re using a new capture called @match. It can do exactly what we just described by using a node position descriptor (an idea we introduced in the last installmentopen in new window) to refer to an earlier line.

    In the first rule, we’re matching the closing } of a switch statement. We’re using a #set! predicate to describe the starting position of its switch_body parent. The switch_body starts on row 1, so @match understands this to mean “adjust the current line to match the indentation of row 1.” This will happen automatically when the user types the closing brace.

    In the second rule, we’re using similar logic. If we were typing the above switch statement, we’d find ourselves over-indented as we started typing on line 5. We’d want our editor to dedent that line once it saw that we were typing a new branch of the switch statement. So we can write another @match rule — still targeting the indentation level of the starting row of switch_body — but with an extra rule to offset the indent by one level. In other words, we want to be indented one level more than the indent level of row 1.

    The third rule is simpler: it’s how we ensure that the editor indents by one level after case and default statements.

    You might’ve had your hackles raised by this example. After all, there’s another school of thought on how to indent switch statements:

    switch (foo) {
    +case "bar":
    +	// one thing
    +	break;
    +case "baz":
    +	// another thing
    +	break;
    +default:
    +	// a third thing
    +}
    +

    This faction thinks that case and default statements should be indented to the same level as the original switch statement. How can we please both camps?

    One way might be to write two different versions of our second rule, and decide which one to use based on configuration:

    
    +; Some say 'case' and 'default' need to be indented one level more than their
    +; containing `switch`.
    +(["case" "default"] @match
    +  (#is? test.config "language-javascript.doubleIndentSwitchStatements")
    +  (#set! indent.matchIndentOf parent.parent.startPosition)
    +  (#set! indent.offsetIndent 1))
    +
    +; Others say 'case' and 'default' should match their containing `switch`.
    +(["case" "default"] @match
    +  (#is-not? test.config "language-javascript.doubleIndentSwitchStatements")
    +  (#set! indent.matchIndentOf parent.parent.startPosition))
    +

    Here we’re using a test.config scope test. I told you about scope tests last time, but I haven’t yet mentioned that they don’t just apply to syntax highlighting queries; they apply to indentation queries, too!

    The test.config scope test gives us a way to approve or reject a capture based on the user’s chosen configuration. If they’ve enabled the doubleIndentSwitchStatements config option, we can indent their code one way; if they’ve disabled it, we can indent their code another way.

    This particular example isn’t yet implemented, but it could be. This would be another advantage that the new Tree-sitter system has over TextMate-style indentation hinting: more room for configurability.

    Here’s another edge case of indentation: a braceless if statement.

    How did we pull this off? Haven’t we been targeting nodes like { and }?

    Yes, but we can also write rules to handle one-line conditionals like these:

    (if_statement
    +  condition: (parenthesized_expression ")" @indent
    +  (#is? test.lastTextOnRow true)))
    +

    Here we’ve written a query that captures the closing ) of a braceless if statement and uses that as the indentation hint. We’re also using a scope test to ensure the capture is ignored when it isn’t the last text on the row; that’s how we can avoid a false positive in this scenario:

    if (notificationsAreDisabled) return;
    +

    A braceless if or else applies only to the next statement. The real feat is knowing to dedent immediately after that statement:

    (if_statement
    +  condition: (_) @indent
    +  consequence: [
    +    (expression_statement)
    +    (return_statement)
    +    (continue_statement)
    +    (break_statement)
    +    (throw_statement)
    +    (debugger_statement)
    +  ] @dedent.next)
    +

    An if clause with braces will have a node named statement_block in its consequence slot. An if clause without braces will have its consequence slot filled with one of these six kinds of nodes instead.

    The @dedent.next capture is only rarely needed, but this is a textbook case: it signals when we should dedent the next line without waiting to see the content of the line. Because we know that the next line should always be dedented in this scenario.

    How well does this work? Amazingly well:

    Tree-sitter isn’t confused by the line comment! It won’t dedent until after the user types an actual statement.

    Does this matter a great deal? Is it worth creating detailed rules to cover a breadth of unusual indentation scenarios? Probably not. But this is just one of a handful of low-hanging fruit that the new system has made possible. Even the slightly verbose query above is much easier to write (and for other people to reason about) than an inscrutable regular expression.

    Code folding

    I’m not someone who uses code folding very much, but I want it to be there when I need it. Collapsing entire code branches helps me see the big picture more easily.

    How TextMate grammars do it

    Much like indentation, code folding markers in TextMate grammars are determined with regular expressions. Any line that matches foldingStartMarker is treated as the start of a fold, and any line that matches foldingEndMarker is treated as the end of a fold. This offers similar tradeoffs to the indentation patterns described above: simple for simple cases, but nearly impossible for complex cases.

    How legacy Tree-sitter grammars do it

    Legacy Tree-sitter grammars introduced a system for defining folds within the grammar definition file itselfopen in new window — one that hooks into named nodes from the tree. It’s a good start, but we can make something more expressive and flexible with queries.

    Using queries to define fold ranges

    Again, credit goes to the nvim-treesitter developers and to @mauricioszaboopen in new window for envisioning how Tree-sitter queries can describe folds more simply. Here’s how simple it can be in a language like CSS:

    (block) @fold
    +

    That’s the entirety of the contents of the folds.scm file inside the language-css package. This works because a starting position and an ending position are all you need to describe a fold, and that’s what Tree-sitter gives us. Every node in a tree reports its buffer positions, so every node can be the target of a fold.

    tree sitter simple fold example

    Let’s go a bit deeper and figure out what this does.

    When it opens a file, Pulsar needs to figure out where possible fold ranges are so that it can show a small chevron in the gutter on each line where a fold can start. So it’ll run a query capture against each line, testing it to see if any @fold captures result.

    Any results will have their buffer ranges inspected. If the range starts and ends on the same line, the candidate fold is ignored. (This saves grammar authors from having to manually exclude things like one-line conditionals.) But if the range starts on one line and ends on another, the fold is valid, and Pulsar knows to place a chevron in the gutter where the fold would start.

    The beginning of a code fold is the very last column on its starting line. In most cases, the range in question will have delimiters on each end — the backticks of a template string, the curly braces of an if or else condition, et cetera. That’s why, by default, the end of a code fold is the starting position of its very last node child. This works as intended in the vast majority of cases, as in our CSS example above:

    tree-sitter-tools node hierarchy illustration

    But because this isn’t always true — especially for languages like Python that don’t use delimiters for blocks — we provide ways to tweak a fold’s ending position.

    For instance, let’s look at a JavaScript block comment:

    JavaScript block comment

    Since comment nodes don’t have children, we should set a custom ending position for the fold with fold.endAt so that it doesn’t try to look up a child node that doesn’t exist. Then we can use fold.offsetEnd to move the ending point of the fold two columns to the left so that the fold ends before the comment’s ending delimiter:

    ((comment) @fold
    +  (#set! fold.endAt endPosition)
    +  (#set! fold.offsetEnd -2))
    +

    Now we can fold up the block comment the way we want:

    Folding in JavaScript is still pretty simple, but not as simple as CSS. We’ve got to account for some edge cases. For example, when an if statement is followed by an else, we should adjust the fold so that it ends on the line before the else, so that each fold can be toggled independently without interfering with one another:

    ; Target `if` consequence blocks that are followed by `else`s.
    +((if_statement
    +  consequence: (statement_block) @fold
    +  alternative: (else_clause)
    +  (#set! fold.adjustToEndOfPreviousRow true)
    +))
    +
    +; Other `if` consequence blocks will get caught here.
    +(statement_block) @fold
    +

    You can see how this works in the screencast below — the else block’s closing delimiter folds up to be on the same line as the starting delimiter, but the if block’s fold stops before the newline.

    “End the fold at the end of the previous line” is a common enough case to have its own shorthand predicate. We’ve put this special-case query above the simpler one because Pulsar will use the first capture that matches for a given line.

    Why is this so complicated?

    I’ll say it again: this tour through the machinery of Pulsar is aimed at Tree-sitter aficionados and at those who might want to write their own language packages for Pulsar. If that doesn’t describe you, don’t let yourself get overwhelmed by this information dump — just make note of the new features that this system makes possible.

    There are pieces of the indentation and folding systems that I didn’t even try to explain in this post. But all this complexity has a purpose, and users reap the benefits in small increments — for instance, every time they don’t have to go back and reformat code they paste into an editor.

    These systems only work with the assistance of language grammars, so we owe it to the authors of those grammars to hide as much of that complexity as we can. If we can make these systems seem simple on the surface, they’ll work better, and users will be happier.

    Next time

    Cue up your DVD of Inception! Next time we’re delving into language injections — the feature that lets you write CSS inside of HTML inside of JavaScript inside of HTML inside of PHP.

    + + + diff --git a/blog/20231109-Daeraxa-NovemberUpdate.html b/blog/20231109-Daeraxa-NovemberUpdate.html new file mode 100644 index 0000000000..9c9fe224a0 --- /dev/null +++ b/blog/20231109-Daeraxa-NovemberUpdate.html @@ -0,0 +1,223 @@ + + + + + + + + Community Update | + + + + + + +

    Community Update

    DaeraxaNovember 9, 2023
    • news
    • log
    • update
    About 4 min

    What month is most important for a Pulsar? A supernova-ember! ...What isn't a bad joke is this, the Pulsar community update!

    Welcome to the November Community Update!

    Another warm welcome to the Pulsar community update, where we cover new developments and events in the world of Pulsar. This month we have a couple of really significant changes to how Pulsar works internally by creating a couple of new APIs that can be used throughout the application, a new package to help you run code directly within Pulsar and our usual community spotlight to say thank you to those community members contributing to Pulsar's development!

    New UI API

    Pulsar has a number of different methods of rendering UI components, which tend to differ between the various core packages that make up the main Pulsar editor. This is particularly evident when dealing with Markdown rendering, where different packages may be using different versions of the same dependencies, which all have to be taken into account when updating them. To this end, @confused-techieopen in new window has been implementing a new UI API that will simplify all of these disparate methods into a single new API.

    The focus will be on Markdown to start with, simply because this is where we are having to spend a lot of effort re-implementing features. For example, our 'settings-view' package needs to display package READMEs and relies on a library called markedopen in new window. However, this isn't the only place where READMEs will be displayed. Not only do we have our own Pulasr Package Registryopen in new window website powered by package-frontendopen in new window using the markdown-itopen in new window parser, but we also have to consider how it is displayed on GitHub, which is currently the only method of publishing packages to the PPR.

    By removing all these independent implementations across various core packages, we should be able to reap the following benefits:

    • With only a single version of each dependency, the overall install size can be reduced.
    • Likewise, there is less overhead associated with keeping things up-to-date, and it will be easier to update when needed.
    • Improvements added to the community package READMEs can be used everywhere, not just on the Pulsar Package Website.
    • We could introduce global Pulsar settings for greater control over how markdown is parsed, which can apply to every package that uses this API. For example, enabling emojis, adding copy buttons to code blocks, disabling syntax highlighting and modifying links or images.
    • Automatic redirection of "dead" atom.io URLs to the pulsar-edit.dev or web archive equivalent.

    Of course this new API will also be available for use by community packages for developers to take advantage of if they wish, so that Markdown parsing doesn't need to be independently implemented any more.

    This new feature is availble in our newest rolling releases and will soon be in our regular release too!

    Exposing a fuzzy-native API

    In a similar vein to the above, we also have a fuzzy-nativeopen in new window package used to provide fuzzy string matching for Pulsar. This package is used as a dependency for a large number of Pulsar packages, including core packages such as fuzzy-finder, command-palette and autocomplete-plus. The goals here are very similar to those for the new UI API mentioned above; instead of each package needing to specify (and bundle) fuzzy-native as a dependency, they can instead simply reference this new global API, saving the need to bundle it as a dependency and having to maintain each package that uses it.

    You can view the progress of this work over on its pull requestopen in new window by [@mauricio szabo].

    A new code runner package

    @confused-techieopen in new window has been working on a new package for running scripts and code directly within Pulsar. The inspiration comes from some existing packages that still work and can be installed, but few of the most popular ones are still actively maintained. Having an integrated code runner package is a common request from new Pulsar users wondering how they can run code directly in the editor in much the same way as is possible within other editors such as VSCode. We are currently discussing the best way to bring this kind of functionality to users; should we publish this as a package, include it as a new core package, or something in-between? Whatever the outcome, you can look forward to more news on this package soon!

    If you want to check it out in its current, in-development state, then have a look at the pulsar-runneropen in new window repository.

    Further installments in the modern Tree-sitter blog post series

    [1]

    Continuing on from last month's entryopen in new window we have posted two new blog posts by @savetheclocktoweropen in new window on the topic of Pulsar's modern Tree-sitter implementation. So if you haven't seen those posts, then you can continue onto parts threeopen in new window and fouropen in new window on the topics of syntax highlighting and indentation & code folding respectively.

    Community spotlight

    A big thanks this month to @danfuzzopen in new window for the identificationopen in new window and fixopen in new window for a problem with shell script syntax highlighting.

    Another big thanks goes out to @kiskozaopen in new window for finding a problemopen in new window and providing a fixopen in new window for an issue with the pulsar-updater package where it wasn't correctly caching the "Dismiss this version" notification to prevent it from showing again.


    And once again we wrap things up for this month. As always, a big thank you to all of our community members and a special thanks to those who donate to the project and make this possible. We hope to see you here next month for our December edition!


    1. Image from https://tree-sitter.github.io/tree-sitter/open in new window - Copyright (c) 2018-2021 Max Brunsfeld ↩︎

    + + + diff --git a/blog/20231110-savetheclocktower-modern-tree-sitter-part-5.html b/blog/20231110-savetheclocktower-modern-tree-sitter-part-5.html new file mode 100644 index 0000000000..d9486c8bb7 --- /dev/null +++ b/blog/20231110-savetheclocktower-modern-tree-sitter-part-5.html @@ -0,0 +1,358 @@ + + + + + + + + Modern Tree-sitter, part 5: injections | + + + + + + +

    Modern Tree-sitter, part 5: injections

    savetheclocktowerNovember 10, 2023
    • dev
    • modernization
    • tree-sitter
    About 16 min

    One annoying thing that software developers do is insist on writing in more than one language at once. Web developers are espeically obnoxious about this — routinely, for instance, putting CSS inside their HTML, or HTML inside their JavaScript, or CSS inside their HTML inside their JavaScript.

    Code editors like Pulsar need to roll with this, so today we’ll talk about how the modern Tree-sitter system handles what we call injections.

    The TextMate grammar system understands injections. In any context, a TextMate grammar can include a subset of its own rules… or an entirely separate grammar.

    But Tree-sitter needs something a bit more elaborate. If I’ve got CSS inside a style tag in my HTML file, now I’ve got two different parsers, each responsible for a different range of code. If I make some changes inside that style block, both parsers need to react to it.

    Injections cover a wide range of use cases — from the examples above to fenced code blocks in Markdown files to special-purpose injections that recognize things like URLs. Injections allow us to do some powerful and useful things that would be hard to do otherwise — including some things that TextMate injections can’t do at all.

    A mental model for injections

    Let’s pretend we have a simple HTML file that looks like this:

    <!DOCTYPE html>
    +<html lang="en" dir="ltr">
    +	<head>
    +		<meta charset="utf-8" />
    +		<title>Sample</title>
    +
    +		<style>
    +			body {
    +				padding: 0;
    +			}
    +		</style>
    +
    +		<script type="text/javascript">
    +			window.dataLayer = window.dataLayer || [];
    +			function gtag() {
    +				dataLayer.push(arguments);
    +			}
    +			gtag("js", new Date());
    +			gtag("config", "G-ABCDEFGHIJ");
    +		</script>
    +	</head>
    +	<body></body>
    +</html>
    +

    We haven’t gotten very close to the machinery so far in this series, but I’ve been content to have you model this as a single document with a single Tree-sitter HTML parser responsible for all syntax highlighting. That works fine until we get to the contents of the style and script elements.

    To the Tree-sitter HTML parser, a style element looks like this:

    visualization of a style element in tree-sitter

    You can see that it performs the usual parsing on the start and end tags, but punts on parsing the CSS itself — instead marking it as raw_text. This is what it should do! It’s not a CSS parser, after all. It treats the inline script element similarly, marking its contents as raw_text because it doesn’t understand JavaScript.

    To apply syntax highlighting to these areas, we need to bring in parsers that understand these languages.

    So our mental model needs to evolve. Instead of one buffer with one parser, we have one buffer with three parsers. We need a name for “a region of the buffer that uses a specific grammar to be understood,” so let’s call it a language layer, because that’s what Pulsar calls it under the hood.

    Language layers

    Imagine a simpler HTML file that doesn’t have any inline style or script tags:

    <!DOCTYPE html>
    +<html lang="en" dir="ltr">
    +	<head>
    +		<meta charset="utf-8" />
    +		<title>Sample</title>
    +	</head>
    +	<body></body>
    +</html>
    +

    Here we’re looking at a buffer with a single language layer at the root. When I type a new keystroke in this buffer, only one Tree-sitter parser has to do any re-parsing work, and only one layer needs to be consulted when re-applying syntax highlighting.

    Once I add a style block…

    <!DOCTYPE html>
    +<html lang="en" dir="ltr">
    +	<head>
    +		<meta charset="utf-8" />
    +		<title>Sample</title>
    +
    +		<style>
    +			body {
    +				padding: 0;
    +			}
    +		</style>
    +	</head>
    +	<body></body>
    +</html>
    +

    …I trigger the creation of a second language layer. This new layer is a child of the root HTML layer — because the HTML layer is its reason for being, and the CSS layer might go away in the future if the HTML layer changes. The new language layer uses a Tree-sitter CSS parser.

    When I add back the script block…

    <!DOCTYPE html>
    +<html lang="en" dir="ltr">
    +	<head>
    +		<meta charset="utf-8" />
    +		<title>Sample</title>
    +
    +		<style>
    +			body {
    +				padding: 0;
    +			}
    +		</style>
    +
    +		<script type="text/javascript">
    +			window.dataLayer = window.dataLayer || [];
    +			function gtag() {
    +				dataLayer.push(arguments);
    +			}
    +			gtag("js", new Date());
    +			gtag("config", "G-ABCDEFGHIJ");
    +		</script>
    +	</head>
    +	<body></body>
    +</html>
    +

    …I trigger the creation of another language layer: a JavaScript layer that is also a child of the root HTML layer. Now there are three layers that might need to be consulted for syntax highlighting and other tasks.

    tree-sitter injection illustration

    And it doesn’t stop here! Certain constructs inside of the JavaScript, like regular expressions or tagged template literals, might carry their own injections, in which case new language layers would be created as children of the JavaScript layer. The result is a tree of language layers which cooperate to apply syntax highlighting to our buffer.

    This might feel impossibly complex, but it isn’t. It’s just a different approach to what was already being done with TextMate grammars. In a minute I’ll explain how Pulsar manages this complexity.

    How Tree-sitter envisions injections

    The Tree-sitter CLI tool performs its own code highlighting, so it needs its own solution for injectionsopen in new window. It envisions a query file called injections.scm that maps certain tree nodes to certain languages. For instance, here’s the HTML parser’s injections.scm:

    ((script_element
    +  (raw_text) @injection.content)
    + (#set! injection.language "javascript"))
    +
    +((style_element
    +  (raw_text) @injection.content)
    + (#set! injection.language "css"))
    +

    These are simple examples, but the website documentation covers more advanced scenarios. For instance, the name of the language might not be hard-coded in the query file; it might be something that you’ll need to determine from the tree itself, like by inspecting a heredoc stringopen in new window’s tag:

    list_items.each do |item|
    +  puts <<~HTML
    +    <li>#{item.name}</li>
    +  HTML
    +end
    +

    This is a thorough system, and we’ve already shown how query files can solve problems like syntax highlighting and injection hinting. So it can solve our language injection problem, right?

    How Pulsar implements injections

    The stuff I just described makes a lot of sense, and it’s possible we’ll do it someday. But here’s how Pulsar does it now:

    atom.grammars.addInjectionPoint("text.html.basic", {
    +	type: "script_element",
    +	language() {
    +		return "javascript";
    +	},
    +	content(node) {
    +		return node.child(1);
    +	},
    +});
    +
    +atom.grammars.addInjectionPoint("text.html.basic", {
    +	type: "style_element",
    +	language() {
    +		return "css";
    +	},
    +	content(node) {
    +		return node.child(1);
    +	},
    +});
    +

    This code block is the equivalent of the above query file for HTML injections. It’s got a similar amount of flexibility for selecting a target (e.g., “the second child of a style_element node”) and the ability to determine the language name either dynamically or statically. But we’ve done everything else via queries; why do we do injections this way instead?

    1. The addInjectionPoint API was added by the legacy Tree-sitter implementation. For reasons of continuity, it makes a lot of sense to keep using this API rather than switch to something that’s functionally the same.

    2. In fact, there’s one thing that the addInjectionPoint API does that a hypothetical injections.scm file can’t: it can be used to add injections to Language X even by someone who doesn’t control the language-x bundle. This makes it far more useful to Pulsar! It means that someone can write their own parser that injects itself into another language, whether in the form of a community package or a few lines in a user’s init.js.

    To me, it doesn’t make sense to deprecate the addInjectionPoint approach when it can do things that a query-based approach can’t. Still, lots of Tree-sitter parsers include that query file, so I imagine that Pulsar will eventually support it in addition to the addInjectionPoint API.

    How does it know which language to use?

    You might’ve noticed that both the Tree-sitter query file example and our addInjectionPoint example refer to the to-be-injected language rather casually — javascript and css. Internally, grammars tend to refer to one another via their root scope — as in the text.html.basic case above. So why not just use the root scope?

    Two reasons:

    1. In our example, the HTML grammar shouldn’t necessarily hard-code references to the injectable grammars it wants. It makes more abstract sense to describe the language it wants as javascript instead of source.js — because more than one grammar could theoretically identify as a JavaScript grammar, and because a grammar might want to respond to more than one name. (js and javascript, c++ and cpp, and so forth.)
    2. There are multiple use cases for associating a shorthand token in a buffer file to a language name. I mentioned above how heredoc stringsopen in new window often hint at the language of the string content via the tag. (And as I write this document, I glance at a dozen other examples: fenced code blocks in Markdown.) So it’s useful for us to be able to determine the to-be-injected language dynamically by inspecting the content of the buffer. We need to meet those use cases where they are.

    So when an injection wants javascript, we need to be able to match it to our source.js grammar. That happens via a property in the grammar definition fileopen in new window; the grammar itself describes its “short” name.

    Architecture

    No matter which approach we use for describing injections, the job of processing injections is roughly the same. All of Pulsar’s Tree-sitter support is written with the understanding that there could be an arbitrary number of grammars that need to be consulted when an edit happens. So the job of parsing a document is divided up into some number of LanguageLayer classes arranged hierarchically.

    To visualize what we described above, you can once again use tree-sitter-toolsopen in new window. Open your favorite HTML document, then run the Tree Sitter Tools: Open Inspector For Editor command. You’ll be able to see all of a document’s trees in a drop-down list:

    a list of language layers in a buffer

    The first item in the list will always be the “root” tree. Other items, if present, represent injections. And because injected languages can have their own injections, this list can grow to arbitrary length.

    When the user edits the buffer, even with a single keystroke, we re-parse the document from the root down as follows:

    1. The root layer re-parses.
    2. When that’s done, Pulsar looks for possible injections by querying for nodes that have been specified in calls to addInjectionPoint.
    3. If those nodes match the criteria of addInjectionPoint (does the injection describe a language whose name we can match to a grammar? does the node specified by the content callback exist?), then we try to match them up to the layers that already exist. Layers that can’t be matched to current injection nodes are disposed of, and nodes that can’t be matched to existing layers get new layers created for them.
    4. The process starts over for each layer at the next level of depth in the tree until all injections are current and all parsers have re-parsed.

    Any keystroke can create a brand new injection or invalidate one that used to exist. If I put the cursor inside of <style> and insert an x, changing it to <styxle>, then the CSS injection would no longer exist, and its corresponding LanguageLayer instance would need to be destroyed. If I then undo my change, the parse tree restores the style_element node, and a new language layer is created.

    Does this feel overwhelming? That’s fair. After all, I’m writing this blog post in a buffer with 32 different language layers across the various code examples, and you’d think that would add up to one hell of a performance penalty on each keystroke. But it doesn’t.

    Here are a few reasons why:

    1. We don’t revisit every single language layer on every single keystroke because we can determine when a given buffer change cannot possibly affect a given injection. For instance, if I’m editing a section of my HTML file outside of the style block, then Pulsar knows it doesn’t have to re-parse the CSS inside of that style block yet. It knows that the layer’s parse tree, though technically stale, is not invalid, and will defer re-parsing until an edit happens within its extent of the buffer. (This is true even if those edits cause the style block to shift its position in the document!) As a result, lots of buffer changes can short-circuit the exhaustive process I just described.

    2. Syntax highlighting in particular is designed for performance, even in injection scenarios. After a buffer’s initial highlighting pass, a given section of code will retain its highlighting indefinitely, even if its position in the buffer shifts as the result of other edits.

      Syntax re-highlighting only happens when a buffer range is specifically invalidated. When an edit happens, Tree-sitter tells us how that edit affects the syntax tree, which in turn tells us which parts of the buffer need to be re-highlighted — and, just as importantly, which parts don’t need to be re-highlighted.

    3. Tree-sitter is faster than you think it is. The smaller the edit, the more the parser can reuse its old work, and the faster the re-parse happens.

    4. Hardly anything in this process happens synchronously, so buffer operations will feel fast even in the rare case where Tree-sitter needs time to catch up.

    Challenges

    The systems we described in the last two installments — syntax highlightingopen in new window, code folding, and indentation hintingopen in new window — are much easier to explain when we don’t have to think about injections. How do we make them work in a multi-language buffer?

    Annoyingly, the answer is different for each system. For instance:

    • To support code folding in an environment with multiple injected languages, we’d want to ask each layer for its code folds, then combine the results.
    • If the user presses Return and we want to know whether to indent the next line, we should ask one specific layer — the one most qualified to answer that question at the given cursor position.

    So sometimes we need to aggregate across layers, but other times we need to pick a winner.

    Picking a winner is the obvious approach for indentation when you think it through. If I hit Return when the cursor is in a script block, then I’m writing JavaScript, and the JavaScript layer should be the one making indentation decisions. More generally, this means that if more than one layer is active at a given buffer position, we should pick the deepest layer and ask it to decide. (Sometimes this means the deepest layer that fulfills a certain criterion — in this case, the deepest layer that actually defines an indentation query.)

    But aggregating is the obvious approach for other scenarios. Tree-sitter grammars get to support Pulsar’s Editor: Select Smaller Syntax Node and Editor: Select Larger Syntax Node commands (you don’t know you need them in your life until you give them a try!) and those commands should work properly across injection boundaries. So when either command is invoked with the cursor at a given position, we should figure out which nodes contain that point regardless of which parse tree owns them. Then we can arrange those nodes from smallest to largest.

    You can see the results here. As I expand the selection by invoking Select Larger Syntax Node over and over, the selection starts with nodes in the CSS injection, jumps to nodes in the parent JavaScript injection, then jumps again to nodes in the root HTML injection.

    Strange injection tricks

    Mixing languages in a single buffer is messy, so injections need some unusual features in order to deal with that messiness. These features can be used in surprising and powerful ways.

    “Redaction” in injections

    One thing that makes Tree-sitter injections more powerful than their TextMate equivalents is their ability to ignore content that isn’t relevant to their jobs. The injection engine “redacts” all content except what it wants a given layer to see.

    Redacting children

    Suppose you had an html tagged template literal:

    let markup = html` <p>Copyright 2020–2023 John Doe</p> `;
    +

    Since the tag hints at the language name, Pulsar will give you HTML syntax highlighting for free inside the literal. But that literal is still JavaScript, so what happens if we do this?

    let now = new Date();
    +let markup = html` <p>Copyright 2020–${now.getFullYear()} John Doe</p> `;
    +

    That ${now.getFullYear()} part isn’t actually HTML. This example won’t confuse an HTML parser, so it’s not a big deal; but there does exist valid content inside of a template interpolation that definitely would flummox the injection:

    let evil = html` <p>this might not ${"</p>"} get parsed correctly</p> `;
    +

    Ideally, the HTML injection wouldn’t see that interpolation at all. So what if we could hide it?

    We can. In fact, we do! Here’s what that template string looks like in tree-sitter-tools:

    tree-sitter-tools visualization of a JavaScript template string

    Our injection is defined such that we specify the template_string node as the content. That means Pulsar will use the buffer range of that node, but will subtract the ranges of any of the node’s children!

    We can visualize this with the “Show injection ranges” option in tree-sitter-tools:

    disjoint content ranges in a Tree-sitter injection

    You can see that the HTML injection layer has two disjoint content ranges on either side of the interpolation. The Tree-sitter HTML parser won’t even know the interpolation is there.

    This behavior makes sense as a default, but it can be opted out of with includeChildren: true in addInjectionPoint if it gets in your way.

    Redacting via content callback

    A grammar author has another tool to control what gets redacted: the content callback. It’s not limited to returning a single node! It can return an array of nodes, each with its own range; there’s no obligation for those ranges to be adjacent.

    Our first HTML injection example earlier applied its own subtle redaction. We specified a type of script_element, but a content callback that returns that element’s second child (the raw_text node). So the type property tells Pulsar which node to query for (create an injection for each script element) but content selects the node(s) that will be meaningful to the parser (omit the <script> and </script> because those aren’t JavaScript).

    This flexibility means that it’s possible for all your injections of a certain type to share one language layer. Instead of creating one layer for each of N different buffer ranges, you could create one layer with N disjoint content ranges. The trade-off is that an injection that covers more of the buffer will need to be re-parsed more often in response to buffer changes, but that trade-off might make sense in certain scenarios.

    Macros in Rust/C/C++

    C, C++, and Rust allow you to define macros via a preprocessor. Macros are weird for a parser: they can’t be parsed as though they’re valid source code, because they might be fragments of code that aren’t syntactically valid until after preprocessing.

    Hence they’re a situation where a language might want to inject itself into itself. Consider this code example:

    #define circleArea(r) (3.1415*(r)*(r))
    +

    The #define keyword and the circleArea(r) preprocessor function signature have to be well-formed, but everything that follows is an anything-goes nightmare for a syntax highlighter. The preprocessor won’t try to parse it or make it make sense; it’ll just make the appropriate substitution throughout the source file and enforce validity later.

    For the same reason, the tree-sitter-c parser doesn’t attempt to do any parsing of the preprocessor argument — the (3.1415*(r)*(r)) in our above example. But that argument will often be valid C, so there’s no reason why we shouldn’t take a stab at it:

    atom.grammars.addInjectionPoint(`source.c`, {
    +	type: "preproc_function_def",
    +	language(node) {
    +		return "c";
    +	},
    +	content(node) {
    +		return node.lastNamedChild; // the `preproc_arg` node
    +	},
    +});
    +

    This is a low-stakes gambit for us. If the content of the macro is syntactically strange, the parser might get a bit flummoxed, and the resulting highlighting might look a bit weird. But that’s OK! It won’t affect the highlighting of anything outside of the macro content.

    Injecting highlighting for URLs and TODOs

    Two built-in packages called language-todo and language-hyperlink define specialized TextMate grammars. Their purpose is to provide rules that match TODO: remarks (in comments) and URLs (in comments and strings), and to inject those rules into certain contexts regardless of grammar. This is a lovely feature of TextMate that the Atom developers got for free when implementing TextMate-style grammars back in the day.

    The effect is that Pulsar can help you locate TODOs in comments by coloring them differently from the rest of the comment. It can also draw underlines under URLs and even follow a URL when you place your cursor inside of it and invoke the Link: Open command.

    illustration of highlighting of TODOs and URLs in a line comment

    This works because a TextMate grammar can “push” its injections into any scope inside any other grammar, whether that other grammar asks for it or not. For instance, the language-hyperlink grammar injects itself into strings, so any language that defines a string.* scope will have those rules injected into it.

    The legacy Tree-sitter system never had an equivalent feature. I missed it terribly, so I decided to create equivalent Tree-sitter parsers and grammars for these rules. These parsers, when given arbitrary text, can create nodes for things that look like URLs or TODO comments. Once those parsers existed, I could inject them into whichever grammars I wanted:

    for (let type of ["template_string", "string_fragment", "comment"]) {
    +	atom.grammars.addInjectionPoint("source.js", {
    +		type,
    +		language: () => {
    +			return "hyperlink";
    +		},
    +		content: (node) => node,
    +		languageScope: null,
    +	});
    +}
    +

    There’s one new thing here: the languageScope option. Typically, you’ll want a grammar’s base scope name to be present inside of an injection; for instance, you’d want a source.js scope name to exist inside of an HTML script block. But that behavior doesn’t make sense for our use case. We want to add a scope name around a URL when it’s present, but otherwise we want to operate stealthily. Passing null to the languageScope option lets us bypass the default behavior.

    There’s one other thing to address, though. Most comments won’t have URLs in them. Most strings won’t have URLs in them. If I use this code as-is, I’ll be creating one new injection for every string, every line comment, and every block comment in my JavaScript file, whether a URL is present or not. (This unnecessary work, believe it or not, doesn’t create any sluggishness during routine editing, but we should still try to avoid it.)

    What should I do? One option would be to do what I described above: create one large injection for the entire document and have it be in charge of all comments and strings in the document. That was my first experiment, but I decided against it because the trade-off wasn’t worth it: incremental re-parses were slower because every buffer change meant that my URL parser had to re-scan the whole buffer.

    I’m willing to chalk part of that up to my lack of experience writing Tree-sitter parsers! I’d bet there are things I can do to make those parses less costly. But in the meantime, I applied a Stupid Human Trick™ to get the best of both worlds:

    const HYPERLINK_PATTERN = /\bhttps?:/;
    +
    +for (let type of ["template_string", "string_fragment", "comment"]) {
    +	atom.grammars.addInjectionPoint("source.js", {
    +		type,
    +		language: (node) => {
    +			return HYPERLINK_PATTERN.test(node.text) ? "hyperlink" : undefined;
    +		},
    +		content: (node) => node,
    +		languageScope: null,
    +	});
    +}
    +

    I can assure you that this feels incredibly silly to do, but it works: we’re pre-screening the content of the node and ignoring those that definitely don’t contain a URL. Returning undefined from the language callback prevents a layer from being needlessly created. We employ a similar strategy for the TODO highlighting.

    There’s another thing that feels awkward about this: it’s not as automatic as the previous TextMate solution. Instead of being able to “push” these injections into other grammars, we’re asking those grammars to “pull” our injections into themselves.

    In an ideal world, I’d be able to create a generalized injection that applied to all files as easily as in the TextMate grammars. But to create a Tree-sitter injection I’ve got to describe the name of the node I want to inject into. And there aren’t many safe assumptions you can make about Tree-sitter node naming conventions.

    The saving grace here is what I mentioned above: the injection API lets you inject things into someone else’s language grammar. So if your favorite community language package doesn’t highlight TODOs and URLs, you can fix that with about six lines of JavaScript in your init.js.

    Markdown and front matter

    Markdown is how I write most of my prose, including this blog post. And for years it’s been quite popular inside static site generators like Jekyllopen in new window and its successors, but with a wrinkle: those tools support the addition of YAML metadata via a “front matter” blockopen in new window at the start of a Markdown file.

    There are two major Markdown parsers for Tree-sitter, both of which are written by third parties rather than by the Tree-sitter organization. One of themopen in new window is being actively developed, and boasts built-in support for front matter, but has a number of bugs that are show-stoppers for Pulsar at the moment. The other oneopen in new window is older, doesn’t support front matter, and doesn’t seem to be actively maintained… but is otherwise bulletproof. I’d love to use the newer one for Pulsar, but I can’t justify it until it’s more stable.

    So how do we get around the older parser’s lack of support for front matter? By writing our own Tree-sitter parser and using injections:

    1. Write a front matter parseropen in new window whose only purpose is to divide a Markdown document into two nodes: (a) front matter and (b) Markdown text.
    2. Inject the YAML grammar into the front matter node.
    3. Inject the Markdown grammar into the Markdown text node.

    In an ideal world, the parser in step 1 would be just an ordinary Tree-sitter parser for Markdown, and we’d need only the single injection for the YAML block. But this’ll tide us over just fine. Documents that don’t have front matter still get parsed by tree-sitter-frontmatter and will simply omit the front_matter node.

    Next time

    I could keep talking about injections, but I can’t afford to test your patience while we still have other topics to visit. Next time we’ll look at what Tree-sitter calls code navigation systemsopen in new window: how to use Tree-sitter to identify functions, classes, and other important parts of your code.

    + + + diff --git a/blog/20231116-Daeraxa-v1.111.0.html b/blog/20231116-Daeraxa-v1.111.0.html new file mode 100644 index 0000000000..2b1457304f --- /dev/null +++ b/blog/20231116-Daeraxa-v1.111.0.html @@ -0,0 +1,223 @@ + + + + + + + + If you're API and you know it, clap your hands! | + + + + + + +

    If you're API and you know it, clap your hands!

    DaeraxaNovember 16, 2023
    • dev
    • release
    About 4 min

    Pulsar 1.111.0open in new window is available now!

    What do we have for you in Pulsar 1.111.0?

    Welcome to a new Pulsar regular release. This time we have a big new addition to Pulsar's API along with our usual set of bug fixes (with some fantastic community contributions).

    One of our major changes is the new UI API we have added to the 'atom' global class. You can read about this in detail in our recent blog postopen in new window but essentially, this allows us to unify the way we render things in Pulsar. For this release, we have a new 'markdown' object that means packages (both core and community) no longer have to worry about performing Markdown rendering and instead can offload it to Pulsar itself. This allows us to create a unified way to render Markdown by using a single set of dependencies. We also took the opportunity to move from the previous markedopen in new window library to the markdown-itopen in new window parser we are already using on the Pulsar Package Registryopen in new window. As an added bonus, we also get to save some space on the installation size!

    On that last topic, we have found a way to reduce Pulsar's installed size by ~35.5 MB by deduping some dependencies and otherwise performing some fine tuning on them.

    Next, we have a fix for a really tricky bug that has been around since the Atom days, which we logged on our own repo almost a year ago. The problem is that it has been devilishly difficult to find a perfect set of reproduction steps. Thankfully, we have now managed to do this (with a great deal of help from one of our community members, @asiloisad/@bacadraopen in new window) and have a fix. This was a problem that would occasionally show up when a hidden input element used in the text editors would be focused when out of view, causing the screen to be "misaligned" or otherwise "shifted".

    And on the theme of community, we have two issue reports and subsequent bug fixes by community members. The first is by @danfuzzopen in new window to fix a problem in our bash Tree-sitter grammar where ANSI C quoted strings were not being properly highlighted as actual strings. The second is by @kiskozaopen in new window who discovered a problem with our (relatively) recently introduced pulsar-updater package, which notifies you if a new release of Pulsar is available. The bug in question was a problem with the "Dismiss this version" button, which was not caching correctly and would therefore "forget" that somebody had requested to not be notified for that version again.

    We hope you enjoyed reading about this update as much as we hope you continue to enjoy using Pulsar. As ever, a huge thank you to our generous donors and community, without whom this project would not be possible.

    Until next time, happy coding, and see you amongst the stars!

    • The Pulsar Team

    • Added a new "UI" API to atom, accessible via atom.ui. This exposes a markdown object, allowing community packages to offload Markdown handling to the core editor.
    • Fine-tuned/deduped dependencies to remove ~35.5 MB from Pulsar's installed size.
    • Fixed an issue that sometimes caused text to shift or disappear after an editor pane regains focus.
    • Fixed scoping/highlighting of single-quoted ('...') and C-style ($'...') strings in shell scripts.
    • Fixed an issue with the "Dismiss this Version" button (in the pulsar-updater package).
    • Fixed an issue with how Linux Pulsar binaries were built, to ensure compatibility with non-bleeding edge glibc versions. (Compatibility with even older glibc versions is still being looked into, for the folks on older or RHEL-compatible distros.)

    Pulsar

    github

    whats-my-line

    + + + diff --git a/blog/20231212-Daeraxa-DecemberUpdate.html b/blog/20231212-Daeraxa-DecemberUpdate.html new file mode 100644 index 0000000000..3198aac167 --- /dev/null +++ b/blog/20231212-Daeraxa-DecemberUpdate.html @@ -0,0 +1,223 @@ + + + + + + + + Community Update | + + + + + + +

    Community Update

    DaeraxaDecember 11, 2023
    • news
    • log
    • update
    About 3 min

    If you have been good this year, you might just find something in your stocking this Christmas. Until then, you can find the Pulsar community update right here!

    Welcome to the December Community Update!

    Another warm welcome to the Pulsar community update, where we cover new developments and events in the world of Pulsar. This month we have a couple of really significant changes to how Pulsar works internally by creating a couple of new APIs that can be used throughout the application, a new package to help you run code directly within Pulsar and our usual community spotlight to say thank you to those community members contributing to Pulsar's development!

    Update on Electron updates

    It has been a while since we have shared an update on the progress of getting Pulsar up to date with its version of Electron. This has been a long-term goal of the project, but not an easy one due to significant changes in the Electron framework. @maurício szaboopen in new window has had a long-running fork of Pulsar that is working on the latest versions of Electron and by all accounts, it works just fine. One recent win has been to get one of Pulsar's core packages, the github package, working again.

    Tree-sitter

    However, probably our main obstacle is tree-sitter, the library Pulsar uses for a number of languages to perform syntax highlighting, code folding and a number of other useful features that is not only out of date compared to modern tree-sitter but blocking the move to the new version of Electron. The good news is that Pulsar has been shipping with a brand new implementation of tree-sitter for some time now (but disabled by default) and there have been steady updates and improvements to it. If you want to read more about tree-sitter and its implementation, then we have a fantastic series of blog postsopen in new window by @savetheclocktoweropen in new window detailing it.

    What we plan to do next is enable this modern version of tree-sitter by default, the implementation is pretty mature at this point and it is as good as, if not better than, the original implementation in many languages. Once enabled by default we can iron out any issues we haven't seen as yet with a view to completely removing the old implementation to clear the way to the new Electron version.

    Making it available

    We haven't decided on the exact approach yet, but we hope to soon be able to publish "preview" binaries and make them available for download via our website and GitHub. That way, early adopters and people who want to help out with the future of Pulsar can do so easily. The more people we have using it and reporting issues, the faster we can have this migrated into the main version.

    Of course, you are always welcome to build it from source using the latest-electronopen in new window feature branch.

    Package compatibility

    One downside to moving to a new version of Electron is that some community packages do use some of these same deprecated features of Electron that have given us so much trouble with some of the core packages. On the surface, this means that we will potentially introduce some breaking changes in Pulsar, causing some of these packages to no longer work - the Hydrogenopen in new window package being a perfect example of this.

    However, part of the goal of the Pulsar project was to make sure that the huge number of community packages out there were retained and it is in no way our goal to deliberately break them. To that end, any package authors who find their packages affected by the updated Electron versions are free to reach out to us via any of our social channelsopen in new window. Likewise, if any users are affected by any of these changes and find themselves with an affected package that is no longer maintained by the original author, the same avenues of help and support will be available should you wish to fork and publish the package under your own account.

    We also have a plan we mentioned recently, the Pulsar Cooperativeopen in new window initiative, in order to have a community-maintained space for package development without the overhead of full-on ownership.

    PPR website issues

    Some people may have noticed we sporadically have some issues with the Pulsar Package Registry website displaying packages correctly. This is rare and tends to resolve itself fairly quickly once the faulty instance is dropped. @confused-techieopen in new window has introduced some additional error logging to try and nail this issue down. The error page has also been updated to include some links for people to report the issues as and when they see them in order to help us capture and resolve this issue.

    Community Spotlight

    Default file icons

    Community member @tthaumaturgistopen in new window has produced a whole bunch of amazing new icons that can be used to show that a given file will be opened within Pulsar as well as identifying the type of file at a glance.

    They have produced icons for both Windows and macOS, @confused-techieopen in new window is currently working on implementing this within Pulsar for Windows. A huge thank you to them for this wonderful contribution!


    And that brings this, the final community update of the year, to a close. As always, a big thank you to all of our community members and a special thanks to those who donate to the project and make this possible. We hope to see you again in 2024!

    + + + diff --git a/blog/20231216-Daeraxa-v1.112.0.html b/blog/20231216-Daeraxa-v1.112.0.html new file mode 100644 index 0000000000..0088783998 --- /dev/null +++ b/blog/20231216-Daeraxa-v1.112.0.html @@ -0,0 +1,223 @@ + + + + + + + + Christmas has come early | + + + + + + +

    Christmas has come early

    DaeraxaDecember 16, 2023
    • dev
    • release
    About 4 min

    Pulsar 1.112.0open in new window is available now!

    Welcome to our 12th regular release! It has been exactly a year since we put out our first tagged releaseopen in new window and development continues. This month we have some new soft-wrapping options, some long overdue updates to PPM, improvements to our "GitHub" package, a new fuzzyMatcher API and our usual slew of bug fixes.

    Let's start with a feature added by community member @Trigan2025open in new window. There are now new options for the "soft wrapping" feature that allows Pulsar to automatically show or hide the soft wrap guide line based on your soft wrap settings. You can find this new option within wrap-guide package settings.

    We have a number of new PPM changes, including better and more secure network handling and converting PPM's code to async. You can read about this second change in much more detail in a recent blog postopen in new window we made. We have also taken the opportunity to do some out-of-season spring cleaning to tidy up the repo and get rid of old, unused dependencies, as well as some general maintenance.

    On to our GitHub package, we found that we had a rather common issue with people not being able to log into their GitHub account via the package. Essentially, it was possible to set scopes in such a way that, although permissions were technically granted, Pulsar was unable to read the scopes and refused to log in. To solve this, we have updated the package to provide feedback and improved the scope checking logic. We also updated the link to the Personal Access Token page to include by default the scopes that Pulsar requires.

    Continuing with the theme of last month's new "UI" API, we have another new API that all packages can now take advantage of. This time it is ui.fuzzyMatcher which will allow packages to use Pulsar's fuzzy-finder module without needing to bundle it into their own packages.

    And to finish off with a bug fix, an issue was found where Pulsar wasn't correctly inheriting the directory from which the pulsar binary was being run, leading to some slightly odd behaviour.

    It is hard to believe that it has been an entire year since we created our first tagged release of Pulsar and we never would have managed to get to this milestone without the amazing support from our donors and our community, so as ever, a massive thank you to everyone who has allowed us to get this far!

    Until next time (and next year!), happy coding, and see you amongst the stars!

    • The Pulsar Team

    • Fixed github package not giving feedback when a token with the wrong scopes was entered, tweak scope-checking logic to match expectations, and log incorrect scopes.
    • Various cleanups, maintenance and upkeep of the PPM repo.
    • Added options for a user to control when to automatically show or hide the wrap-guide; "Always", "When soft wrap is enabled", and "When soft wrap at preferred line length is enabled".
    • Updated network handling in PPM to something newer and more secure.
    • Updated most of PPM's code to use async/await and promises internally.
    • Created atom.ui.fuzzyMatcher API, moving the Pulsar fuzzy-finder module into the core of the editor for community packages to utilize.
    • Fixed an issue that prevented Pulsar from inheriting the directory from which the pulsar binary was run.

    Pulsar

    PPM

    github

    + + + diff --git a/blog/20231219-DeeDeeG-v1.112.1.html b/blog/20231219-DeeDeeG-v1.112.1.html new file mode 100644 index 0000000000..5b497a12f1 --- /dev/null +++ b/blog/20231219-DeeDeeG-v1.112.1.html @@ -0,0 +1,223 @@ + + + + + + + + Hotfix: Pulsar v1.112.1 | + + + + + + +

    Hotfix: Pulsar v1.112.1

    DeeDeeGDecember 19, 2023
    • dev
    • release
    Less than 1 minute

    Hotfix: Pulsar 1.112.1 is available now!open in new window

    What is new in 1.112.1?

    Hotfix for important functionality within PPM. During recent refactoring of the PPM package, a bug was accidentally introduced that made it impossible for any package maintainer to publish a new package (or to publish new versions of their existing packages). An update was needed within PPM to restore this functionality to expected working order.

    Includes these PRs: https://github.com/pulsar-edit/ppm/pull/116 and https://github.com/pulsar-edit/ppm/pull/118 to fix the issue.

    See the v1.112.0open in new window release for all the other changes since v1.111.0.

    + + + diff --git a/blog/20240112-Daeraxa-JanuaryUpdate.html b/blog/20240112-Daeraxa-JanuaryUpdate.html new file mode 100644 index 0000000000..dd7c8323fc --- /dev/null +++ b/blog/20240112-Daeraxa-JanuaryUpdate.html @@ -0,0 +1,223 @@ + + + + + + + + Community Update | + + + + + + +

    Community Update

    DaeraxaJanuary 12, 2024
    • news
    • log
    • update
    About 3 min

    Happy new year! Welcome to the first Pulsar community update of 2024!

    Welcome to the January Community Update!

    First of all, we hope everyone had a fantastic new year and here is to a new one! While the Pulsar team has been largely enjoying the holidays we still have plenty of things to update you with!

    New tree-sitter becoming default

    [1]

    This was mentioned in the December blog post, but we have now landed this changeopen in new window in the newest rolling releases and will be coming to our regular releases in the next version. This has been available as an option for a while now but was disabled by default. You can read more info in the previous blog postopen in new window and about the implementation in detail in @savetheclocktoweropen in new window's blog post seriesopen in new window.

    Annoying website bug found and zapped!

    We mentioned in the last blog postopen in new window that the Pulsar Package Registry website has sporadically encountered some kind of problem causing the website to not display any data. After @confused-techieopen in new window implemented some changes to improve our error logging, we have actually managed to find something! To top it off, @savetheclocktoweropen in new window was able to find the exact problem and solve it. If you wish to read more about the exact issue, you can read more on the pull requestopen in new window.

    New PPR owners/:ownerName API endpoint

    Staying with the PPR, @confused-techieopen in new window has implemented a new endpoint for the APIopen in new window. Building off the work by @savetheclocktoweropen in new window (to make the backend aware of package ownersopen in new window) that allows you to filter packages by author, this can also be used via URL in the PPR frontend website (with UI controls still to come). For example, if you want to list all packages published by @bacadraopen in new window (one of the Pulsar community's most prolific package authors), you can add ?owner=bacadra to the end of the URL. e.g.https://web.pulsar-edit.dev/packages?owner=bacadra.

    Long-term projects

    This community blog post was designed to highlight ongoing work and successes that are going on both within the Pulsar team and the community at large for "Pulsar-adjacent" items. That is, along with new features being developed for Pulsar, we also highlight things going on that might otherwise go unnoticed but are deserving of attention. Sometimes we may announce things, but due to other priorities, it may take a while until we get around to implementing or announcing any news. This doesn't mean we have forgotten about them, not by a long shot. So just to recap some of the things we are looking to progress in 2024 that have been previously mentioned on this blog and in our social channels:

    Community Spotlight

    As it is the start of the new year, this spotlight should be particularly special. We have received an absolute ton of new and repeat donations, for which we couldn't be more thankful. You really do keep this project alive. We also want to thank each and every one of our community members, no matter what you do: those who make themselves known in our social channels, publish packages, contribute code, donate to the project, and those who are just content using Pulsar or reading our blog posts. This spotlight goes out to our entire community, and here is to an even better 2024!


    So with that, we start another year. Thanks again to the entire community, no matter how you choose to get involved, and here is to another year of Pulsar!


    1. Image from https://tree-sitter.github.io/tree-sitter/open in new window - Copyright (c) 2018-2021 Max Brunsfeld ↩︎

    + + + diff --git a/blog/20240115-Daeraxa-v1.113.0.html b/blog/20240115-Daeraxa-v1.113.0.html new file mode 100644 index 0000000000..9c39987653 --- /dev/null +++ b/blog/20240115-Daeraxa-v1.113.0.html @@ -0,0 +1,223 @@ + + + + + + + + Unlucky for some, but not us. Our 13th release, Pulsar 1.113.0, is available now! | + + + + + + +

    Unlucky for some, but not us. Our 13th release, Pulsar 1.113.0, is available now!

    DaeraxaJanuary 15, 2024
    • dev
    • release
    About 3 min

    Pulsar 1.113.0open in new window is available now!

    Welcome to our first release of 2024! This is our 13th main release; let's just hope we aren't cursed by the number. This month we are enabling a feature by default that has been in the works for a long time and is a major step in moving Pulsar to current versions of Electron. We also have a significant update to our symbols-view package and a number of bug fixes as per usual.

    First up is something we have been mentioning in the last few community update blog posts and otherwise hinting about for a while. Back in Pulsar 1.106.0, released in July last year, we added a new setting called Use Modern Tree-Sitter Implementation that would enable a brand new implementation of Tree-sitteropen in new window within Pulsar. If you want to know more about the history of this change, then have a look at our detailed blog postopen in new window on the topic as well as @savetheclocktoweropen in new window's fantastic blog post seriesopen in new window. A huge thank you to everyone who contributed to this feature by testing it out and submitting bug reports and pull requests, these contributions have led to this moment where we can finally activate it by default. The reason we have done this is in order to eventually move to modern versions of Electron, for which the old tree-sitter implementation is simply not compatible. We have had this as an option while we worked out the most egregious kinks in the system, but we are in a position where we feel it is strong enough to enable by default. If you encounter any significant issues with the new system, there is a new option Use Legacy Tree-sitter Implementation that will revert to the old system. It would be great if any problems with the new system could be brought to our attention via any of our social channelsopen in new window so we can look to fix any issues that have yet to be encountered by us.

    We have a rather significant update to our symbols-view package. This package is used to display symbols within Pulsar (e.g. function definitions) and allows you to navigate your code via those symbols. This package has had a rather major overhaul and now follows the standard "provider/consumer" model as many other Pulsar packages do. In particular, this allows for packages to provide symbols to symbols-view, such as Tree-sitter grammars, via the new symbol-provider-tree-sitter package. The upshot of this, combined with the new Tree-sitter implementation, is that there will be a much richer and more accurate display of symbols in your project. More information can be found in the pull requestopen in new window for this change.

    A new Tree-sitter PHP grammar has been added to Pulsar as part of ongoing Tree-sitter grammar upgrades and improvements, which includes some contributions from community member @claytonrcarteropen in new window who had a parser for PHPDoc allowing us to highlight documentation comments in PHP in a similar vein to JSDoc injection JS/TS files.

    On to some bug fixes. We had an issue reported about the github package that showed a problem with the rendering of the diff view display. This seems to have been due to a code path in our TextEditor component that would sometimes try to perform measurements on things that weren't necessarily visible. The fix here, simple as it seems, was to defer those measurements until we could be sure the editor was visible.

    Next, we have a fix to the find-and-replace package caused by an odd interaction between the package and the Preserve Case During Replace option, causing an error to appear when using empty strings as input (as an empty string cannot be capitalized).

    And last, we have a change to the default behavior of rendering emojis in Markdown as part of the new UI API. This issue was discovered due to an unwelcome (but admittedly rather apt) 😡 emoji in the middle of an error message popup. While the erroropen in new window was rather hilarious, we did decide that it needed to be banished by default for the sake of one of our team members' sanity (and to reduce unnecessary obfuscation of errors, of course).

    And that is all we have for you right now. We hope you enjoy this new release and here is to another 12 months of Pulsar!

    Until next time, happy coding, and see you amongst the stars!

    • The Pulsar team

    • Enabled Modern Tree-sitter Grammars by default
    • Added a modern Tree-sitter grammar for PHP.
    • Fix a measurement issue that was causing visual glitches in the github package's diff views.
    • Enabled the core symbols-view package to accept symbols from a number of sources, including Tree-sitter grammars and IDE packages.
    • Switch default to false for converting ASCII emoticons to emoji when rendering Markdown.
    • Fix certain find-and-replace scenarios when the "Preserve Case During Replace" setting is enabled.
    • Fix an issue in symbols-view when returning from visiting a symbol declaration.

    Pulsar

    find-and-replace

    symbols-view

    + + + diff --git a/blog/20240122-savetheclocktower-modern-tree-sitter-part-6.html b/blog/20240122-savetheclocktower-modern-tree-sitter-part-6.html new file mode 100644 index 0000000000..6bf0cd503b --- /dev/null +++ b/blog/20240122-savetheclocktower-modern-tree-sitter-part-6.html @@ -0,0 +1,223 @@ + + + + + + + + Modern Tree-sitter, part 6: reinventing symbols-view | + + + + + + +

    Modern Tree-sitter, part 6: reinventing symbols-view

    savetheclocktowerJanuary 22, 2024
    • dev
    • modernization
    • tree-sitter
    About 12 min

    We’ve been telling a series of stories about all the different ways that Tree-sitter can improve the editing experience in Pulsar. Today’s story about symbols-view starts a bit slowly, but it’s got a great ending: the addition of a major new feature to Pulsar 1.113.

    Background

    Back in March, @mauricioszaboopen in new window gave me an assignment:

    Currently, “definitions” are implemented using CTags in symbols-view. What do you think about transforming this into a “service”, like pulsar.definitions or editor.definitions? That way, the tokenizer can “push” definitions into this service, and symbols-view can query for the definitions on the current file.

    I’ll explain what he’s talking about.

    You might already use the symbols-view packageopen in new window to navigate to important parts of your source code files. For instance, if you want to jump to the definition of render in a given file, you can

    • press Ctrl+R (or Cmd+R on macOS),
    • start typing render, and
    • press Return to accept the first result in the list (or use arrow keys or the mouse to navigate to a different result).

    Choosing the render symbol in your symbols list will move the editor to the line where render is defined.

    This is a time-saving feature. But how does it work? How does Pulsar know which items to put in the list? How does it know where your render method is defined? You might be surprised: it uses an ancient program called ctags — specifically a fork called Exuberant Ctagsopen in new window.

    (What Maurício calls “definitions” is what symbols-view calls “symbols,” and what ctags calls, well, “tags.” For simplicity I’ll use the term “symbol” just to align with what Pulsar calls it.)

    ctags works well enough that you might never notice its drawbacks, but it’s got plenty of drawbacks. It reads files from disk, so it can return inaccurate results if you use it on a file that has been modified since its last save. For the same reason, it doesn’t work at all on new files that haven’t yet been saved. And it needs special configurationopen in new window for each language it supports — meaning that, even after you’ve written a Pulsar grammar for your newly-invented Language X, you won’t get any symbol-based navigation unless you modify the symbols-view package itself and tell ctags how to find your language’s symbols.

    I know you’re probably tired of hearing me say “Tree-sitter would be great for this task!” — but code navigation systemsopen in new window really are in its wheelhouse. The trees we’re already using to highlight code and do other useful tasks can be queried to supply symbols much more easily than via ctags. And many parsers even come bundled with a query file that does the work of identifying the symbols we’re interested in.

    You might have noticed how GitHub can nowadays give you an outline-like view of a source code file, listing lines where methods are defined. That’s all happening through Tree-sitter. If GitHub can use it for symbol navigation, so can we.

    Refactoring symbols-view

    But to make that happen, we need to change how symbols-view works. All it knows about is ctags! Could we rip all that out and replace it with a Tree-sitter solution? Yes, but in the process we’d be abandoning support for any languages that don’t yet have Tree-sitter parsers.

    A better approach would be to know about both strategies and pick the best one on the fly. So let’s figure out exactly what Maurício’s request — “transforming [symbols] into a ‘service’” — means.

    A crash course in services

    In Pulsar, services are how packages talk to one another. Suppose I’ve authored package-b and it depends on another package called package-a that someone else has written. I could reach into atom.packages and grab the reference to package-a, but this feels weird for a number of reasons. For one, it incorrectly assumes that package-a has already been activated. It may get activated after package-b — or else it may never get activated because the user has disabled or uninstalled it.

    But even if package-b were able to find and consume package package-a this way, it’d create a tight coupling between the two. That coupling would break if package-a renamed itself, or if it changed implementation details that package-b was relying on.

    So instead of communicating directly, they can invent a service called foo and use it to communicate. One package defines itself as a provider of service foo, and the other defines itself as a consumer of service foo.

    During startup, Pulsar will activate each package, notice the match, and arrange an introduction as soon as both packages have been activated. The provider will end up returning an object that the consumer can use however it likes; this object is typically some sort of interface with methods that the consumer can call.

    Services thus act as contracts between packages. And they can be versioned, too. If it wants, package-a can provide several different versions of the service at once; this leaves the author free to make changes without breaking packages that consume the older version.

    A built-in example

    This flexibility makes new things possible. Consider a package like autocomplete-plus, the bundled package that provides an autocompletion menu in Pulsar. It doesn’t try to implement the various tactics that can be used to suggest completion candidates; all it does is make the user interface for an autocomplete menu. It then defines an autocomplete.provider service so that other packages can provide completion suggestions. Packages like autocomplete-html, autocomplete-css, and others know how to suggest context-specific completions at the cursor, so they feed that data to autocomplete-plus.

    Service Diagram 1

    We like this approach because it gives users an incredible amount of control. For example, if you don’t like the HTML autocompletion suggestions, you can change autocomplete-html’s configuration, or even disable it entirely. Or you could write your own alternative to autocomplete-html. Or you could even write your own alternative to autocomplete-plus! By registering as a consumer of autocomplete.provider, your replacement package would be able to communicate with packages like autocomplete-html just as easily as autocomplete-plus can.

    Service Diagram 2

    This is the model we need for symbols-view. We now have a second approach for generating symbols that can compete favorably with the ctags strategy. So let’s reinvent symbols-view in the style of autocomplete-plus and make it a consumer of a new service we’ll invent named symbol.provider.

    Service Diagram 3

    The built-in ctags provider can be spun off into a package called symbol-provider-ctags, and our new Tree-sitter–based approach can be in a package called symbol-provider-tree-sitter. These packages can provide the symbol.provider service for symbols-view to consume.

    How will it work?

    I’ve talked about why Pulsar chose not to leverage the built-in highlights.scm query files that exist for most Tree-sitter parsers: we needed richer information than they could provide. Luckily, that’s not true for other kinds of files! Many parsers also provide tags.scm query files, and they’re easy for us to consume as-is.

    When a user presses Ctrl+R / Cmd+R, we can run a query against the current buffer. Any node that is captured as @name in a tags.scm file can be represented as a symbol. Often the node will be contained in a larger capture called (for example) @definition.function; we can detect that and infer that the text captured by @name refers to a function.

    The information we get is not only richer than what ctags can provide, but also more accurate, since we’re querying against the current buffer text. Even if the file hasn’t been saved recently. Even if it hasn’t been saved at all!

    Now, we can only do this when the file in question is using a Tree-sitter grammar, so it’s not a universal solution. But we can prefer a Tree-sitter symbol provider where it’s available, and fall back to our ctags provider where it isn’t.

    Project symbols

    Another thing that symbols-view has long supported — theoretically — is project-based symbol navigation, allowing you to search for and jump to symbols in other files.

    Project Symbols example

    It’s been able to do this because ctags can read project-wide symbol metadata — a genuine upside it has over some other approaches. But this feature only works if the user has generated a special file called a “tags file” for their project. Pulsar itself can’t generate this file on its own because it doesn’t know which files it should crawl to find symbols (imagine if it tried to crawl your entire node_modules folder!), so the ctags strategy requires the user to regenerate that file on a regular basis.

    For now, our Tree-sitter symbol provider can only suggest symbols in the current file. If you activate Toggle Project Symbols via Ctrl+Shift+R / Cmd+Shift+R, it won’t even volunteer for the job. Using Tree-sitter to list the symbols in an open buffer is very fast precisely because the buffer is open; we’ve already paid the startup cost of the initial parse. But there’s no way Tree-sitter could parse all of a project’s files in a similar amount of time. If we want project-wide symbol search we’ll have to look elsewhere.

    Go to declaration

    “Who cares,” you may think. And I’ll admit I don’t attempt a project-wide symbol search very often. But there’s a related feature I’m pretty sure you’ll like.

    symbols-view defines a Go To Declaration command. It’ll search the project for a symbol matching the word under the cursor. If there’s one result, it’ll get opened automatically; if there’s more than one, it offers up the options in a list for you to choose. And when you’re done, there’s a corresponding Return From Declaration command that takes you back to the place you just were.

    Dive into a definition with Ctrl+Alt+Down / Cmd+Alt+Down, then return to the surface with Ctrl+Alt+Up / Cmd+Alt+Up:

    Here I’ve demonstrated it on a TypeScript type, but it’ll work on functions and classes and other types of things, too.

    Did you know this feature existed? I didn’t. It’s been available to you this whole time if you’ve had a tags file to supply project-wide symbols, the way nobody does. But with a refactored symbols-view, another candidate for supplying these symbols enters the arena: a language server.

    Language servers

    I hesitate to mention language serversopen in new window merely in passing, because they’re a deep enough topic to require their own multi-part blog post series. But let me give it a shot.

    There are a handful of Pulsar packages named like ide-x, where x is the name of a language. Several of them were even originally developed by the Atom team. For now I’ll call them IDE backend packages.

    What these packages have in common is that they all run something called a language server under the hood. A language server is designed to be a brain for a few dozen common features you’d want from your code editor: autocompletion, code linting, refactor support, and the like. A single language server typically knows how to do these tasks for one specific language or framework.

    Language servers are exciting because they make it easier for weirdos like us to use editors other than the market leader. Instead of having to write all those features from scratch for, say, TypeScript, an upstart code editor could instead communicate with typescript-language-serveropen in new window and write some glue code to wire up the language server’s features to the features of the editor.

    The good news is that the language server specification includes several actions that are relevant to symbols-view: textDocument/documentSymbolopen in new window for same-file symbols, workspace/symbolopen in new window for project-wide symbols, and even textDocument/definitionopen in new window for finding where a symbol is defined. Some IDE backend packages already have “brains” capable of doing these tasks!

    But here’s the bad news: since the symbol.provider service has only just been invented, those IDE backend packages need updates before they can be used for symbol navigation.

    I’ve started to do a bit of that work. Inspired by ide-typescript — but mainly starting fresh — I’ve been working on a package currently called pulsar-ide-typescript-alphaopen in new window that aims to be its drop-in replacement. It should be able to do everything that ide-typescript can already do, but it will also be able to offer project-wide symbol search and go-to-declaration functionality.

    And it might take a few version bumps on dependencies, but most other IDE backend packages can also be updated to take advantage of these features.

    Anyway, back to symbols-view

    Unlike autocomplete-plus, which aggregates suggestions from multiple providers and shows all of them to the user, symbols-view is mainly interested in choosing the best provider for the job. There’s little point in aggregating across a language server and Tree-sitter and ctags, since they’re largely going to be offering the same list of symbols with varying degrees of richness, and you’d be pretty annoyed if Pulsar offered you three different list entries for the same function. Inside symbols-view they’re called “exclusive” providers because only one of them will be picked for the job.

    But I wanted to leave the door open for some creative and unexpected usages, so symbols-view also has a concept of “supplemental” providers. A provider that marks itself as supplemental is saying it’d like to contribute symbols that would probably not already be in an exclusive provider’s list. You may be wondering what kinds of symbols would fit the bill, so let me give you an example…

    Did you know you can bookmark lines in a buffer? Try it out: right-click on any line of your editor and select Toggle Bookmark. The built-in bookmarks package keeps track of them and will also let you navigate between them via F2 and Shift+F2.

    Anyway, to illustrate the idea of a supplemental provider, I wrote one: symbol-provider-bookmarksopen in new window will turn each of your bookmarks into a symbol, then display them in the symbols-view UI alongside your main provider’s symbols, using the text of the bookmarked line as the symbol name.

    symbol-provider-bookmarks example

    This one’s not bundled with Pulsar, so grab it from the package registryopen in new window if it sounds interesting.

    Shipping now

    I’ve had most of this article written for months, but I decided to wait to publish it until we could show this stuff off. That time is now.

    Pulsar 1.113 makes two major changes that will vastly improve the quality of the symbol searching you might already be accustomed to:

    1. The new version of symbols-view is now in place. It will offer you ctags-based symbols in grammars that don’t use Tree-sitter, but it will prefer Tree-sitter–supplied symbols in most grammars. If you truly don’t like change, you can disable the symbol-provider-tree-sitter package and just rely on symbol-provider-ctags, or else you can configure symbols-view to prefer some providers over others.

      But I’m betting you’ll want to keep using the symbols provided by Tree-sitter, because…

    2. As you may have heard, modern Tree-sitter grammars are now the default! The system that we shipped in experimental fashion back in Pulsar 1.106 is now ready for prime time. For now, you can opt back into legacy Tree-sitter with the new core.useLegacyTreeSitter setting — but not for long, because the legacy system will be dropped when we’re finally able to migrate to a newer version of Electron.

    Because common languages like JavaScript, Python, Ruby, and many others have full-featured modern Tree-sitter grammars, they will also be using our new Tree-sitter symbol provider for Ctrl+R / Cmd+R. That means the symbol results should be better across the board — more accurate and more comprehensive. (If it seems worse, please file a bugopen in new window.)

    How does this actually improve the symbols-view experience? Let’s see what our original example looks like with a Tree-sitter symbol provider:

    The richness of the metadata we get from these sources has allowed us to enhance the symbols-view UI, too! You’ll be shown the “kind” of thing that a symbol is — class, function, constant, et cetera. In many cases, these kinds will be illustrated with icons. Visit the package settings page for symbols-view to explore the possibilities.

    And there are even a few killer new features. Open a symbols list on a JSON file and marvel at the entries you see:

    symbols-view JSON example

    The entire key path is now the name of the symbol! The same sorts of query and predicate tricks we’ve seen in previous installments in this series can be used for awesome features like this. The symbol-provider-tree-sitter READMEopen in new window has more details.

    And it’s early days for pulsar-ide-typescript-alphaopen in new window, but I’ve been using it for a few months as a symbol provider (and a go-to-declaration provider!) on TypeScript and JavaScript projects. Feel free to give it a shot yourself. (And if you’re interested in bringing one of the other ide-x packages into the year 2024, please do broach the topic on GitHub Discussionsopen in new window, Discordopen in new window, or one of our other communitiesopen in new window.)

    Conclusion

    After overhauling Pulsar’s syntax highlighting, indentation, code folding, and language injections, we’ve found yet another way that Tree-sitter can improve our existing editor experience. But in this case, there’s an even better improvement just around the corner: IDE backend packages and language servers. I’ll be sure to go into more detail on the Pulsar IDE experience in future posts.

    Integrating Tree-sitter has been a difficult project. I started working on it in earnest in February of 2023; it shipped in June behind an experimental flag; and it’s finally the default grammar type in January of 2024.

    Our Tree-sitter series is nearing an end, but there’s one more thing to cover: the challenges. Could it have been easier? Can Tree-sitter overcome its pain points and drawbacks? We’ll talk about it next time.

    + + + diff --git a/blog/20240124-mauricioszabo-the-quest-for-electron-lts.html b/blog/20240124-mauricioszabo-the-quest-for-electron-lts.html new file mode 100644 index 0000000000..19a3f839cb --- /dev/null +++ b/blog/20240124-mauricioszabo-the-quest-for-electron-lts.html @@ -0,0 +1,223 @@ + + + + + + + + The quest for Electron LTS | + + + + + + +

    The quest for Electron LTS

    mauricioszaboJanuary 24, 2024
    • dev
    • modernization
    • electron
    About 5 min

    In the beginning, Atom appeared. It created an API to make packages, but together with this API, it also allowed authors to use web APIs together with node.js packages, modules (including "native modules" - more on that later) and, finally, a special API that was used to communicate between the "main module" and the "browser part".

    That last part, eventually, split from Atom and became Electron. And for a while, the Atom development was tied to the Electron one, meaning that an update on Atom usually meant an update on Electron, and vice-versa.

    Unfortunately, that wasn't the case for a long time...

    A little bit of history

    The latest version of Atom was pulled because of some certificate issues; that means the real latest stable version of Atom was 1.60.0.

    This version of Atom used Electron 9.3.5. For comparison, at the time, Electron was at version 22, but the "master" branch of Atom (and atom-community) was at version 11.5.0.

    The first thing we did was to find the greatest version that would give us some kind of benefit, but also that we could make work. Luckily for us, the only breaking change on Electron 12 was that some crash analytics changed, and considering we would not use that anymore (because we decided to remove all telemetry) we decided to bump 12.2.3 - the version we're at today.

    This might not seem that much of a change, but Electron 12 gave us native macOS ARM binaries, and allowed us to use Playwright to test the editor - enabling what we called "visual tests" on our CI (the actual unit and integration tests from Atom, at the time, were not running reliably in our CI, so these visual tests allowed us to have some feedback if we broke anything).

    The quest for Electron 13

    The next step was to bump to Electron 13. This...

    ...crashed the editor...

    ...really bad.

    Trying to open Pulsar in Electron 13 gave us the error "The renderer process crashed". There was no stacktrace, and the documentation linked on how to debug this erroropen in new window didn't help at all. There were some hidden tutorials to enable CrashReport, then dumping the report somewhere, use a version with debug symbols or download them from GitHub. But honestly, none of this actually pointed to the actual problem, so we did the worst thing that we could think of: we patched require to log what was being required, and hoped for the best.

    What was crashing was a library called superstring.

    Superstring, and Memory Manipulation

    Superstring is a library that handles text manipulation. That basically translates to editing text - so, basically, the most important library that we have.

    This library is also a "native library" - meaning that it's written on a lower-level language (C++, in this case) and that it integrates with the Node.JS code via some bindings to the runtime. These bindings basically change all the time on Node.JS, but that's not the worst part.

    The worst part is that they change even more agressively on Electron. Because in C, and C++, you manipulate memory manually, and because we're now in a "browser environment" (Electron) some changes are being pushed to avoid corrupting memory, accessing places that we're not supposed to access, and to make these libraries "play nice" in multiple contexts - meaning, one instance of the "browser" not corrupting the memory of another instance (in our case, this translates to one editor window not corrupting another). Superstring was not ready to be used in the state it was, because it manually copied memory from places.

    Fortunately, we found a fix - there was an experimental, and incomplete, version of Superstring that compiled to Web Assembly - that means a new format that every browser should run without any problem, and that's how we ported Superstring to run newer Electron.... for a while.

    More things break

    Our syntax highlighter tree-sitter also didn't work on newer Electron version - on Electron 14, it stopped working completely. So, for the time being, we disabled it, and kept trying to port things, fix stuff, etc. Fortunately, all other libraries were ready to be used in newer Electron contexts, and we just had to fix some "synchronous" calls to be "asynchronous" and that worked.

    I wasn't that worried about disabling tree-sitter because, at the time, we were already trying to make a modern tree-sitter implementation, so I knew that sometime in the future, we could re-enable tree-sitter and that it would work (spoiler alert: it DID!).

    Then I got greedy, and decided to make Pulsar work in the newest Electron at the time - Electron 23.

    Things work fine! (juuuust kidding!)

    Well, if you have been following the tone of this post... of course you know what happens next, right?

    Electron 21 enabled something called V8 memory cage. I honestly don't fully understand that that means, but what ended up happening is that another native library was crashing the editor - and the name of the library is Oniguruma.

    If you're not aware of what this is, it's a regular expression library. If you also don't know what that is, basically is the library used in TextMate grammars to do syntax highlighting.

    So, summarizing: Atom (and by definition, Pulsar) had two ways of highlighting code (coloring the keywords, functions, classes, methods, etc): one is called tree-sitter, that breaks in Electron 14, and another callled TextMate, that breaks in Electron 21...

    ...great.

    A code editor without syntax highlighting is probably not a good idea at all; we could not disable it and basically offer no syntax highlighting library. So, I started a quest to migrate TextMate grammars to something different - and what better place to start than VSCode?

    It turns out that VSCode uses a WASM version of Oniguruma - that they called vscode-onigurumaopen in new window - and it's MIT-licensed too, which is compatible with Pulsar's license. And luckily for us, Pulsar's highlighter (again, inherited from Atom) is on its own separate library, well-tested, called first-mateopen in new window. So, I did a wordplay and called the new library second-mate (and up to this day, I still reget not calling it check-mate to be honest) that uses this new WASM version. After fixing some infinite loops and other edge-cases that were not fully tested on the original project, I plugged second-mate in the editor, and as unreal that this might sound, it worked!

    Well, kinda - @savetheclocktoweropen in new window found that our tests were constantly failing, and found that the reason is that the WASM version doesn't garbage collect the regexes, so it was basically re-creating them over and over again. It is an edge-case that was never going to happen in normal usage, but still it was worth a fix, and then our tests were back to normal... after, of course, we fixed some other issue with macOS on ARM processors.

    The quest is (almost) over

    I know it seems hard to believe, but after all this work, we were able to bump to Electron 24, then 25, then 26, then 27, and finally, 28 (which did break more stuff but luckily that was easy to fix). I'm even writing this post using an experimental build on Electron 28!

    Also, thanks to the amazing work of @bongnvopen in new window, we are back to a native version of Superstring (that means we won't take a performance hit from the WASM version - it was, unfortunately, really slow for big files, like the ones I had to work with more than 10,000 lines). This new version still lacks some APIs, but fortunately we don't need them - by patching the specific core packages that used these undocumented, weird APIs we were able to avoid having to implement them.

    The last piece of code that we needed to fix was the github package, but as soon as that was fixed, it was only a matter of completely removing the old implementation of tree-sitter, activating the new one by default, and test, experiment, and do more work.

    There are still things that need to be done - native packages change a lot in the newer Electron, which translates to some packages simply refusing to load (and the risk of crashing or locking the editor) - for example, no terminal package works, because they all depend on node-pty and that is not supported (luckily, again, for us, we have a version of x-terminal-reloadedopen in new window that works); it also breaks Hydrogenopen in new window, a very popular package that uses ZeroMQ to communicate with Jupyter notebooks (that currently simply doesn't work on the newer Electron branches).

    But for now, we're studying a way to offer the newer Electron branch as an additional download option in our page, so that authors can check if their packages work on this new version. And hopefully, we can get an up-to-date editor, at last!

    + + + diff --git a/blog/20240201-Daeraxa-FebruaryUpdate.html b/blog/20240201-Daeraxa-FebruaryUpdate.html new file mode 100644 index 0000000000..d4f55175fa --- /dev/null +++ b/blog/20240201-Daeraxa-FebruaryUpdate.html @@ -0,0 +1,223 @@ + + + + + + + + Community Update | + + + + + + +

    Community Update

    DaeraxaFebruary 1, 2024
    • news
    • log
    • update
    About 4 min

    Here it comes sashaying into your feeds, it's the Fab-ruary community update!

    Welcome to the February Community Update!

    Last month was our biggest update to Pulsar we have had in quite a while, so in this blog we will be addressing some of the issues people have seen and what you can expect in terms of fixes and updates. Outside of that, we have some big changes to the Pulsar Package Registry backend that give (and document) a bunch of new filters and endpoints to the API, as well as a reminder for @maurício szaboopen in new window's blog post detailing our biggest hurdle: the road to modern versions of Electron.

    Update on the new Tree-sitter implementation

    [1]

    With the release of Pulsar 1.113 we finally went live with our new Tree-sitter implementation, which had been in the works for a long time. We had tested this as much as we were able to with the resources we had, and we made sure that any issues raised by people enabling the new feature were dealt with. However, with it being enabled by default, we suddenly had significantly more people using this feature, and thus issues we hadn't yet seen were being found.

    @savetheclocktoweropen in new window was quick in starting a number of "Mega-issues"open in new window on GitHub for those languages where issues had been found in order to keep things in one place and make sure we didn't get swamped with duplicate issues.

    The vast majority of these issues have now already been addressed (or are in process), so if you are seeing any oddities or problems, then you can download one of our rolling releasesopen in new window which will already have those merged changes included. Otherwise, they will be present in the release version of Pulsar 1.114.

    If any of these changes are truly breaking for you and you are not in a position to upgrade to a rolling release, you can find instructions on how to do this for that particular language in the relevant Mega-issueopen in new window. Make sure to subscribe to that issue so that you are notified when it has been fixed and the configuration can be removed. The benefits of the new system are substantial, and we would hate for you to miss out on them.

    Thank you to all those who have submitted issues or let us know about problems via our community areasopen in new window, your help has been much appreciated. If you encounter any new problems, please check the Mega-issues and check if your issue has already been addressed. If not, then feel free to add a new post to that issue.

    New blog post about our quest for Electron stable

    [2]

    If you haven't seen it already, @maurício szaboopen in new window has written a great new blog post on our next biggest goal for Pulsar: getting it onto a current and stable version of Electron. There are a number of challenges (the recent Tree-sitter update included) that need to be met in order to reach this, and the post explains it all.

    Read it now on the Pulsar blogopen in new window!

    New PPR filter options

    Thanks to @confused-techieopen in new window following on from some work via @savetheclocktoweropen in new window, we now have a whole bunch of new ways to use the PPR API to filter packages. For example, you can now search for packages that provide a grammar for a particular file extension, consumes/provides a service, or just search by the package owner.

    Examples of this would be as follows:

    Currently, these filters are only available via the API directly, so they do not feature within Pulsar itself or in the PPR website UI, but we are looking to add this functionality in the future to make finding the exact package you want much easier.

    Now if only there was a way to find what all these different endpoints might be and how you could construct even fancier queries... well, if only there was a...

    Major update to our OpenAPI documentation for the PPR

    [3]

    Our API documentation (found at https://api.pulsar-edit.dev/swagger-ui/open in new window) is having a major update courtesy of work by @confused-techieopen in new window. This update adds the significant number of endpoints and schemas that have been added since it was last updated.

    Even better, to make sure we don't get into this same situation again with outdated API information, it will now automatically update when changes are made to the backend schema.

    Community Spotlight

    Thank you to @claytonrcarteropen in new window for the PR to Pulsar to fix a breaking changeopen in new window with our new Tree-sitter implementation as well as a second PR for adding a new -f/--force flagopen in new window to the ppm link command in order to aid in replacing linked packages without needing to manually remove original.

    And a big collective thank you to all those users who have helped us by submitting issues and feedback about the new Tree-sitter implementation in general. We know this might have been unintentionally disruptive to some people, so thank you so much for sticking with us while we fix these issues!


    And that brings us to the end of this community update. We have a lot of exciting plans for Pulsar this year, so make sure to keep tuning into the blog for new posts from the Pulsar team!


    1. Image from https://tree-sitter.github.io/tree-sitter/open in new window - Copyright (c) 2018-2021 Max Brunsfeld ↩︎

    2. Image from https://www.electronjs.org/open in new window ↩︎

    3. Image from https://www.openapis.org/open in new window ↩︎

    + + + diff --git a/blog/20240215-Daeraxa-v1.114.0.html b/blog/20240215-Daeraxa-v1.114.0.html new file mode 100644 index 0000000000..7e76bb2ba7 --- /dev/null +++ b/blog/20240215-Daeraxa-v1.114.0.html @@ -0,0 +1,223 @@ + + + + + + + + A Valentine's release bursting with love, Pulsar 1.114.0 is available now! | + + + + + + +

    A Valentine's release bursting with love, Pulsar 1.114.0 is available now!

    DaeraxaFebruary 16, 2024
    • dev
    • release
    About 4 min

    Pulsar 1.114.0open in new window is available now!

    A Valentine's release bursting with love, Pulsar 1.114.0 is available now!

    Welcome to a brand new Pulsar release! I think it is safe to say that this month has been one of our more eventful due to the switchover we made to the new Tree-sitter implementation. This release features a lot of updates and fixes for this new implementation, thanks to all the feedback we got from the community. We also have a number of other bug fixes and new features to introduce, such as restoring compatibility with older Linux distributions, and a new flag for an old favorite ppm command.

    As mentioned above, we have quite a few changes relating to our new Tree-sitter implementation that we made default in Pulsar 1.113.0. First of all, we have some fixes and updates to make sure the new implementation isn't a regression from the legacy implementation. One such update is restoring support for the semanticoloropen in new window package, which was supported by the previous implementation. Otherwise, if you had any particular issue with syntax highlighting, then we hope it has now been resolved, as a lot of changes and fixes have been made in response to community feedback. If not, then please visit our ongoing "Mega-issues"open in new window and see if your issue has been raised, and if not, please raise it there.

    Back in Pulsar 1.111.0, we created a new UI api to make an easy and universal way for packages to interact with Pulsar. This implementation did contain a couple of small oversights, which have now been resolved. One issue with the markdown implementation was the display of non-functional heading anchors when rendering a package README; the other was a small rendering bug that was causing a misalignment of content after a linebreak. We had also inadvertently introduced a new problem with fuzzymatcher that could cause the command palette to hang if it failed to highlight a match.

    One issue raised a while ago was that, starting with Pulsar 1.109.0, users of older LTS Linux distributions, such as AlmaLinux 8 and RockyLinux 9, were no longer able to run Pulsar. This was due to the CI runners we use being updated to newer OSs, and therefore Pulsar was being compiled against a newer version of glibc. We have changed our workflows to compile Pulsar in a Debian 10 Docker container instead, which should resolve this problem.

    It was noticed a while ago that the github package wasn't quite working correctly on ARM Linux systems. We have bumped the version of dugite (a library for using git in NodeJS and Electron) to resolve the problem.

    Lastly, we have two new features in ppm. The first is a new flag --force that can be specified on ppm link, which will forcibly replace a conflicting package without needing to manually remove it. The other is an update to ppm's syntax theme template so that, when generating a new syntax theme, it now includes some style variables that the editor has long supported, but which were not in the template, so theme authors might have been unaware of them. (As a bonus: there are two new variables supported by default in the editor, and added to the template as well! Theme authors, feel free to take advantage of these!)

    And that is it for yet another Pulsar release. We hope you enjoy this release and look forward to the next one!

    Until next time, happy coding, and see you amongst the stars!

    • The Pulsar team

    • Prevented an exception raised in the command palette in certain unusual filtering scenarios.
    • Refrain from rendering anchor icons when showing a package's README file in settings-view.
    • Build Linux binaries on Debian 10, for older glibc and compatibility with older Linux distros.
    • Fixed a rendering error in atom.ui.markdown.render when disableMode was set to "strict" and the input contained HTML line breaks.
    • Added support for the semanticolor package in modern tree-sitter grammars.
    • Added new --force flag to ppm link command that will uninstall any conflicting package already installed.
    • Added language entity colors to syntax-variables.less.
    • Numerous Tree-Sitter Grammar syntax highlighting fixes.
    • Bumped dugite to make the github package compatible with ARM Linux.

    Pulsar

    PPM

    github

    + + + diff --git a/blog/20240323-savetheclocktower-v1.115.0.html b/blog/20240323-savetheclocktower-v1.115.0.html new file mode 100644 index 0000000000..a09860c931 --- /dev/null +++ b/blog/20240323-savetheclocktower-v1.115.0.html @@ -0,0 +1,232 @@ + + + + + + + + A week later than you’re accustomed to — but worth the wait! Pulsar 1.115.0 is available now! | + + + + + + +

    A week later than you’re accustomed to — but worth the wait! Pulsar 1.115.0 is available now!

    savetheclocktowerMarch 23, 2024
    • dev
    • release
    About 2 min

    Pulsar 1.115.0open in new window is available now!

    A week later than you’re accustomed to — but worth the wait! Pulsar 1.115.0 is available now!

    Last month’s 1.114.0 release was full of fixes related to the recent migration to modern Tree-sitter. This month’s release is much smaller, but still dominated by Tree-sitter fixes affecting syntax highlighting, code folding, and indentation.

    The most visible fixes are related to your ability to customize the grammar that Pulsar uses on a per-language basis. This is an approach we’ve encouraged for users that want or need to revert to an older grammar for a specific language — better to do so on a targeted basis than globally. If you can edit your config file, you can do per-language customization.

    For instance, now it’s even easier than before to say “use legacy Tree-sitter, but only for Python”:

    ".python.source":
    +  core:
    +    useLegacyTreeSitter: true
    +

    Or “use modern Tree-sitter for JavaScript, but TextMate-style grammars everywhere else”:

    "*":
    +  core:
    +    useTreeSitterParsers: false
    +".source.javascript":
    +  core:
    +    useTreeSitterParsers: true
    +

    Better yet, now the grammar-selector package will be attuned to these choices. When you manually reassign a buffer to use a different grammar, it will offer you only the “correct” grammar for each language based on what you’ve opted into.

    We’ve also delivered our customary incremental improvements in language support, and one change that affects nearly all languages: indentation hinting will be more accurate in transactions with multiple buffer changes. The most common example of a multi-edit transaction is when the user places more than one cursor and starts typing.

    We’ve made improvements to our language-ruby bundle, primarily with code folding and indentation hinting. TypeScript and C/C++ also got some small enhancements, and the language-shellscript bundle got a parser update.

    And lastly, on another note, we have a few maintenance and upkeep PR's to keep our Cirrus CI active and working, to ensure you can keep using and enjoying the latest builds Pulsar has to offer.

    Until next time, happy coding, and see you amongst the stars!

    • The Pulsar team

    • Fixed some folds in Ruby like unless, some blocks, multiline comments, function calls, and different array syntaxes for strings and keywords.
    • Improved the accuracy of indentation hinting in modern Tree-sitter grammars, especially in multi-cursor scenarios.
    • Improved the ability of the user to opt into a specific kind of grammar for a specific language.
    • Changed the behavior of the grammar-selector package so that it will show the user's preferred grammar for a specific language.
    • Updated to version 0.20.9 of web-tree-sitter.
    • Improved syntax highlighting, indentation, and code folding in various languages, including TypeScript, shell scripts, Ruby, and C/C++.

    Pulsar

    + + + diff --git a/blog/20240417-confused-Techie-v1.116.0.html b/blog/20240417-confused-Techie-v1.116.0.html new file mode 100644 index 0000000000..ded5534e83 --- /dev/null +++ b/blog/20240417-confused-Techie-v1.116.0.html @@ -0,0 +1,223 @@ + + + + + + + + For all the code ninjas out there, Pulsar 1.116.0 is available now! | + + + + + + +

    For all the code ninjas out there, Pulsar 1.116.0 is available now!

    confused-TechieApril 17, 2024
    • dev
    • release
    About 2 min

    Pulsar 1.116.0open in new window is available now!

    Pulsar 1.116.0: Ready for all the code ninjas out there!

    Like the past few releases, this Pulsar release is full of Tree-sitter improvements and fixes! From TypeScript and Python getting improved syntax highlighting all around, to big changes like replacing the underlying Markdown Tree-sitter parser, Pulsar 1.116 is sure to make these grammars more stable and aesthetically pleasing.

    But on to some of the really exciting stuff this month: we have a flurry of new features and updates to snippets, which is sure to make otherwise repetitive coding exciting and high-tech!

    For anyone familiar with the super secret code ninja techniques of snippets, there's now support for new snippet variables and transformation flags. Be sure to read the full changelog to get caught up on all of these changes.

    As always, this release has some bug fixes and housekeeping. Linux folks will appreciate improved metadata for their installs, whereas Windows folks will be happy to see that adding Pulsar to the PATH is easier than ever whether Pulsar is installed for just one user or system-wide.

    That's just about everything this time around. We can't wait to see what people do with these powerful new snippets features and look forward to the next one!

    Until next time, happy coding, and see you amongst the stars! - The Pulsar team


    • Added TextEditor::getCommentDelimitersForBufferPosition for retrieving comment delimiter strings appropriate for a given buffer position. This allows us to support three new snippet variables: LINE_COMMENT, BLOCK_COMMENT_START, and BLOCK_COMMENT_END.
    • Added ability to use “simple” transformation flags in snippets (like /upcase and /camelcase) within sed-style snippet transformation replacements.
    • Improved TypeScript syntax highlighting of regular expressions, TSX fragments, wildcard export identifiers, namespaced types, and template string punctuation.
    • Replaced our underlying Tree-sitter parser for Markdown files with one that’s more stable.
    • Fixed issues in Python with unwanted indentation after type annotations and applying scope names to constructor functions.
    • Removed Machine PATH handling for Pulsar on Windows, ensuring to only ever attempt PATH manipulation per user. Added additional safety mechanisms when handling a user's PATH variable.
    • Update (Linux) metainfo from downstream Pulsar Flatpak

    Pulsar

    snippets

    + + + diff --git a/blog/20240520-confused-Techie-v1.117.0.html b/blog/20240520-confused-Techie-v1.117.0.html new file mode 100644 index 0000000000..b2a6746be8 --- /dev/null +++ b/blog/20240520-confused-Techie-v1.117.0.html @@ -0,0 +1,223 @@ + + + + + + + + With special love for Markdown and Tree-sitter, Pulsar 1.117.0 is available now! | + + + + + + +

    With special love for Markdown and Tree-sitter, Pulsar 1.117.0 is available now!

    confused-TechieMay 20, 2024
    • dev
    • release
    About 2 min

    Pulsar 1.117.0open in new window is available now!

    Pulsar 1.117.0: With special love for Markdown and Tree-sitter

    As you may have come to expect, this release is full of new improvements for Tree-sitter grammars! From SCSS now having Tree-sitter support, to Ruby getting an updated parser, to performance and highlighting enhancements in Markdown and TypeScript, there's hopefully something in there for everybody.

    Speaking of Markdown, the markdown-preview package has gotten significant performance increases in the preview pane (especially for documents with lots of fenced code blocks), and boasts brand new dark mode support for the "GitHub.com style" option!

    But dark mode support doesn't stop there! Pulsar can now (optionally) use the OS-level dark mode window theme, switching automatically based on the lightness or darkness of the colors in the active Pulsar theme if enabled.

    Beyond the fixes and features within the editor itself, you'll be glad to know this release also saw many improvements for our Continuous Integration to ensure we can deliver timely and bug-free updates in the future. - The Pulsar team


    • [markdown-preview] Improve rendering performance in preview panes, especially in documents with lots of fenced code blocks.
    • [markdown-preview] GitHub-style Markdown preview now uses up-to-date styles and supports dark mode.
    • Pulsar's OS level theme will now change according to the selected editor theme if core.syncWindowThemeWithPulsarTheme is enabled.
    • [language-sass] Add SCSS Tree-sitter grammar.
    • [language-ruby] Update to latest Tree-sitter Ruby parser.
    • [language-gfm] Make each block-level HTML tag its own injection.
    • [language-typescript] More highlighting fixes, especially for operators.

    Pulsar

    + + + diff --git a/blog/20240616-confused-Techie-v1.118.0.html b/blog/20240616-confused-Techie-v1.118.0.html new file mode 100644 index 0000000000..4772250c77 --- /dev/null +++ b/blog/20240616-confused-Techie-v1.118.0.html @@ -0,0 +1,223 @@ + + + + + + + + Hot dog, it's another Pulsar release! | + + + + + + +

    Hot dog, it's another Pulsar release!

    confused-TechieJune 16, 2024
    • dev
    • release
    About 2 min

    Pulsar 1.118.0open in new window is available now!

    Hot dog, it's another Pulsar release!

    Get your grills ready, Pulsar v1.118.0 is cooking with gas! This release brings lots of love to syntax highlighting, along with a zesty sprinkling of features and fixes.

    We've got Tree-sitter fixes and improvements, including improved documentation around Tree-sitter's usage, an updated PHP parser, fixed syntax quoting on Clojure, as well as enhanced Clojure highlighting support for metadata and "def" elements, as well as improved injection points for Clojure. EDN files are back to being detected as Clojure, but will highlight correctly as EDN. Tasty!

    Within the Pulsar application the new setting core.allowWindowTransparency lets users set the background of the editor to support transparency, although transparent CSS styles must be set by a theme or user stylesheet for Pulsar to actually be transparent. The textChanged property is now accurate when characters are deleted.

    The Tree-sitter syntax highlighting system now has a new test for queries ancestorTypeNearerThan which helps matching the first type as an ancestor, as well as supporting a second buffer argument in the content field of addInjectionPoint for easier customization.

    Finally, for our community package developers, they will be glad to hear that first time publication issues have been resolved with a rewrite of the ppm publish command, improving the workflow, and ensuring things work properly with the updated backend. Please feel free to share what you've been cooking by publishing your packages!

    As always, thanks a ton to all of our users and supporters for sticking with the Pulsar project, and until next time, happy coding, and see you amongst the stars!

    - The Pulsar Team


    • Various tree-sitter grammar improvements
      • Docs fixes
      • A parser update for PHP
      • Miscellaneous grammar fixes and improvements
    • Added a preference core.allowWindowTransparency so that themes and user stylesheets can make editor windows' backgrounds transparent.
    • Added a new modern tree sitter "test" for highlight query - ancestorTypeNearerThan that matches if it finds the first type as an ancestor, but doesn't match if any "other" ancestors are found before
    • Syntax quoting and unquoting in Clojure now highlights correctly, and also highlights full qualified keywords differently than generated ones
    • content field of addInjectionPoint for modern-tree-sitter now supports a second buffer argument, for better customization if one wants to
    • EDN is back to being detected as Clojure (for compatibility) but highlights as EDN
    • Fixed syntax quoting on Clojure grammar (newer tree-sitter), fixed some injection points on Clojure. Added support for highligting metadata, and added better support for "def" elements (for example - doesn't scope default or definition as a def, but highlights p/defresolver)
    • Fixed textChanged property to be accurate when deleting characters
    • Fixed ppm publish for publishing brand new packages

    Pulsar

    + + + diff --git a/blog/20240717-confused-Techie-v1.119.0.html b/blog/20240717-confused-Techie-v1.119.0.html new file mode 100644 index 0000000000..4333798a22 --- /dev/null +++ b/blog/20240717-confused-Techie-v1.119.0.html @@ -0,0 +1,223 @@ + + + + + + + + Pulsar v1.119.0 is Live! | + + + + + + +

    Pulsar v1.119.0 is Live!

    confused-TechieJuly 17, 2024
    • dev
    • release
    About 1 min

    Pulsar 1.119.0open in new window is available now!

    Pulsar v1.119.0 is live!

    While a smaller release this time around, v1.119.0 still manages to pack a punch.

    For macOS, we've gone to great lengths to ensure Pulsar should build just fine on macOS 13+, while our Linux users get greater compatibility for DevTools on various platforms. For our programmers, there's been more of the constant incremental improvements to various languages' built-in syntax highlighting and code folding this time around, with a focus on PHP, Python, Javascript, Typescript, Shell script, and C.

    As always thanks a ton to all of those that support the project and keep it moving forward, we appreciate you all, and look forward to seeing you amongst the stars.

    - The Pulsar Team

    Notice

    Originally, the binaries from our normal "Pull Request" CI build (which produces unsigned binaries) were accidentally uploaded to this release, instead of the binaries from the special "tag push" CI build (which signs the macOS binaries). In order to provide you with signed Intel macOS binaries, we have re-uploaded them and updated SHA256SUMS.txt to reflect this. The following binaries have been swapped out for the correct versions and the checksums updated:

    • Intel.Mac.Pulsar-1.119.0-mac.zip
    • Intel.Mac.Pulsar-1.119.0.dmg

    • Changed language-php to continue syntax-highlighting even when encountering unbalanced PHP tags. (Avoid throwing a syntax error)
    • Indentation, fold, and highlighting fixes in language-python, language-javascript, language-typescript, language-shell and language-c.
    • Worked around API breakage (FreeBSD libiconv vs GNU libiconv) in the iconv library shipped in macOS 13+
    • Fix --no-sandbox flag not being applied to the .desktop launcher on Linux (Fixes Dev Tools)

    Pulsar

    superstring

    + + + diff --git a/blog/index.html b/blog/index.html new file mode 100644 index 0000000000..f34c8cdfc8 --- /dev/null +++ b/blog/index.html @@ -0,0 +1,232 @@ + + + + + + + + Articles | + + + + + + +
    2
    3
    4
    5
    6
    + + + diff --git a/category/dev/index.html b/category/dev/index.html new file mode 100644 index 0000000000..d6bca2d1c2 --- /dev/null +++ b/category/dev/index.html @@ -0,0 +1,232 @@ + + + + + + + + dev Category | + + + + + + +
    2
    3
    4
    + + + diff --git a/category/index.html b/category/index.html new file mode 100644 index 0000000000..4c85d024f4 --- /dev/null +++ b/category/index.html @@ -0,0 +1,223 @@ + + + + + + + + Category | + + + + + + +
    + + + diff --git a/category/log/index.html b/category/log/index.html new file mode 100644 index 0000000000..565c9f92d8 --- /dev/null +++ b/category/log/index.html @@ -0,0 +1,233 @@ + + + + + + + + log Category | + + + + + + +
    2
    + + + diff --git a/category/news/index.html b/category/news/index.html new file mode 100644 index 0000000000..cfb02cb720 --- /dev/null +++ b/category/news/index.html @@ -0,0 +1,233 @@ + + + + + + + + news Category | + + + + + + +
    2
    + + + diff --git a/category/survey/index.html b/category/survey/index.html new file mode 100644 index 0000000000..641b60b829 --- /dev/null +++ b/category/survey/index.html @@ -0,0 +1,228 @@ + + + + + + + + survey Category | + + + + + + + + + + diff --git a/community.html b/community.html new file mode 100644 index 0000000000..42bc9f0593 --- /dev/null +++ b/community.html @@ -0,0 +1,223 @@ + + + + + + + + Community areas | + + + + + + +

    Community areas

    Less than 1 minute

    Here you will find a number of community links to discuss Pulsar, get help, suggest features and enhancements and otherwise interact with the team and community.

    + + + diff --git a/docs/atom-archive/api/index.html b/docs/atom-archive/api/index.html new file mode 100644 index 0000000000..e153b7cea5 --- /dev/null +++ b/docs/atom-archive/api/index.html @@ -0,0 +1,223 @@ + + + + + + + + Reference : API | + + + + + + + + + + diff --git a/docs/atom-archive/atom-server-side-apis/index.html b/docs/atom-archive/atom-server-side-apis/index.html new file mode 100644 index 0000000000..5dc7e9547b --- /dev/null +++ b/docs/atom-archive/atom-server-side-apis/index.html @@ -0,0 +1,287 @@ + + + + + + + + Appendix E : Atom server-side APIs | + + + + + + +

    Appendix E : Atom server-side APIs

    Less than 1 minute

    STOP

    This is being kept for archival purposes only from the original Atom documentation. As this may no longer be relevant to Pulsar, use this at your own risk. Current Pulsar documentation is found at documentation home.

    Atom server-side APIs

    This appendix covers the the Atom server-side APIs that various parts of Atom consume.

    WARNING

    Warning: These APIs should be considered pre-release and are subject to change.

    Atom package server API

    This guide describes the web API used by apmopen in new window and Atom. The vast majority of use cases are met by the apm command-line tool, which does other useful things like incrementing your version in package.json and making sure you have pushed your git tag. In fact, Atom itself shells out to apm rather than hitting the API directly. If you're curious about how Atom uses apm, see the PackageManager classopen in new window in the settings-view package.

    WARNING

    Warning: This API should be considered pre-release and is subject to change.

    Authorization

    For calls to the API that require authentication, provide a valid token from your atom.io account pageopen in new window in the Authorization header.

    Media type

    All requests that take parameters require application/json.

    API Resources

    Packages
    Listing packages
    GET /api/packages

    Parameters:

    • page (optional)
    • sort (optional) - One of downloads, created_at, updated_at, stars. Defaults to downloads
    • direction (optional) - asc or desc. Defaults to desc. stars can only be ordered desc

    Returns a list of all packages in the following format:

      [
    +    {
    +      "releases": {
    +        "latest": "0.6.0"
    +      },
    +      "name": "thedaniel-test-package",
    +      "repository": {
    +        "type": "git",
    +        "url": "https://github.com/thedaniel/test-package"
    +      }
    +    },
    +    ...
    +  ]
    +

    Results are paginated 30 at a time, and links to the next and last pages are provided in the Link header:

    Link: <https://www.atom.io/api/packages?page=1>; rel="self",
    +      <https://www.atom.io/api/packages?page=41>; rel="last",
    +      <https://www.atom.io/api/packages?page=2>; rel="next"
    +

    By default, results are sorted by download count, descending.

    Searching packages

    Parameters:

    • q (required) - Search query
    • page (optional)
    • sort (optional) - One of downloads, created_at, updated_at, stars. Defaults to the relevance of the search query.
    • direction (optional) - asc or desc. Defaults to desc.

    Returns results in the same format as listing packages.

    Showing package details
    GET /api/packages/:package_name

    Returns package details and versions for a single package

    Parameters:

    • engine (optional) - Only show packages with versions compatible with this Atom version. Must be valid SemVeropen in new window.

    Returns:

      {
    +    "releases": {
    +      "latest": "0.6.0"
    +    },
    +    "name": "thedaniel-test-package",
    +    "repository": {
    +      "type": "git",
    +      "url": "https://github.com/thedaniel/test-package"
    +    },
    +    "versions": [
    +      (see single version output below)
    +      ...,
    +    ]
    +  }
    +
    Creating a package
    POST /api/packages

    Create a new package; requires authentication.

    The name and version will be fetched from the package.json file in the specified repository. The authenticating user must have access to the indicated repository.

    Parameters:

    • repository - String. The repository containing the plugin, in the form "owner/repo"

    Returns:

    • 201 - Successfully created, returns created package.
    • 400 - Repository is inaccessible, nonexistent, not an atom package. Possible error messages include:
      • That repo does not exist, isn't an atom package, or atombot does not have access
      • The package.json at owner/repo isn't valid
    • 409 - A package by that name already exists
    Deleting a package
    DELETE /api/packages/:package_name

    Delete a package; requires authentication.

    Returns:

    • 204 - Success
    • 400 - Repository is inaccessible
    • 401 - Unauthorized
    Renaming a package

    Packages are renamed by publishing a new version with the name changed in package.json. See Creating a new package version for details.

    Requests made to the previous name will forward to the new name.

    Package Versions
    GET /api/packages/:package_name/versions/:version_name

    Returns package.json with dist key added for e.g. tarball download:

    {
    +	"bugs": {
    +		"url": "https://github.com/thedaniel/test-package/issues"
    +	},
    +	"dependencies": {
    +		"async": "~0.2.6",
    +		"pegjs": "~0.7.0",
    +		"season": "~0.13.0"
    +	},
    +	"description": "Expand snippets matching the current prefix with `tab`.",
    +	"dist": {
    +		"tarball": "https://codeload.github.com/..."
    +	},
    +	"engines": {
    +		"atom": "*"
    +	},
    +	"main": "./lib/snippets",
    +	"name": "thedaniel-test-package",
    +	"publishConfig": {
    +		"registry": "https://..."
    +	},
    +	"repository": {
    +		"type": "git",
    +		"url": "https://github.com/thedaniel/test-package.git"
    +	},
    +	"version": "0.6.0"
    +}
    +
    Creating a new package version
    POST /api/packages/:package_name/versions

    Creates a new package version from a git tag; requires authentication. If rename is not true, the name field in package.json must match the current package name.

    Parameters:

    • tag - A git tag for the version you'd like to create. It's important to note that the version name will not be taken from the tag, but from the version key in the package.json file at that ref. The authenticating user must have access to the package repository.
    • rename - Boolean indicating whether this version contains a new name for the package.

    Returns:

    • 201 - Successfully created. Returns created version.
    • 400 - Git tag not found / Repository inaccessible / package.json invalid
    • 409 - Version exists
    Deleting a version
    DELETE /api/packages/:package_name/versions/:version_name

    Deletes a package version; requires authentication.

    Note that a version cannot be republished with a different tag if it is deleted. If you need to delete the latest version of a package for example for security reasons, you'll need to increment the version when republishing.

    Returns 204 No Content

    Stars

    Listing user stars
    GET /api/users/:login/stars

    List a user's starred packages.

    Return value is similar to GET /api/packages

    GET /api/stars

    List the authenticated user's starred packages; requires authentication.

    Return value is similar to GET /api/packages

    Starring a package
    POST /api/packages/:name/star

    Star a package; requires authentication.

    Returns a package.

    Unstarring a package
    DELETE /api/packages/:name/star

    Unstar a package; requires authentication.

    Returns 204 No Content.

    Listing a package's stargazers
    GET /api/packages/:name/stargazers

    List the users that have starred a package.

    Returns a list of user objects:

    [{ "login": "aperson" }, { "login": "anotherperson" }]
    +

    Atom update server API

    WARNING

    Warning: This API should be considered pre-release and is subject to change.

    Atom updates

    Listing Atom updates
    GET /api/updates

    Atom update feed, following the format expected by Squirrelopen in new window.

    Returns:

    {
    +	"name": "0.96.0",
    +	"notes": "[HTML release notes]",
    +	"pub_date": "2014-05-19T15:52:06.000Z",
    +	"url": "https://www.atom.io/api/updates/download"
    +}
    +
    + + + diff --git a/docs/atom-archive/atom-server-side-apis/sections/atom-package-server-api.html b/docs/atom-archive/atom-server-side-apis/sections/atom-package-server-api.html new file mode 100644 index 0000000000..87eaa068e9 --- /dev/null +++ b/docs/atom-archive/atom-server-side-apis/sections/atom-package-server-api.html @@ -0,0 +1,281 @@ + + + + + + + + + + + + + + +

    About 3 min

    Atom package server API

    This guide describes the web API used by apmopen in new window and Atom. The vast majority of use cases are met by the apm command-line tool, which does other useful things like incrementing your version in package.json and making sure you have pushed your git tag. In fact, Atom itself shells out to apm rather than hitting the API directly. If you're curious about how Atom uses apm, see the PackageManager classopen in new window in the settings-view package.

    WARNING

    Warning: This API should be considered pre-release and is subject to change.

    Authorization

    For calls to the API that require authentication, provide a valid token from your atom.io account pageopen in new window in the Authorization header.

    Media type

    All requests that take parameters require application/json.

    API Resources

    Packages
    Listing packages
    GET /api/packages

    Parameters:

    • page (optional)
    • sort (optional) - One of downloads, created_at, updated_at, stars. Defaults to downloads
    • direction (optional) - asc or desc. Defaults to desc. stars can only be ordered desc

    Returns a list of all packages in the following format:

      [
    +    {
    +      "releases": {
    +        "latest": "0.6.0"
    +      },
    +      "name": "thedaniel-test-package",
    +      "repository": {
    +        "type": "git",
    +        "url": "https://github.com/thedaniel/test-package"
    +      }
    +    },
    +    ...
    +  ]
    +

    Results are paginated 30 at a time, and links to the next and last pages are provided in the Link header:

    Link: <https://www.atom.io/api/packages?page=1>; rel="self",
    +      <https://www.atom.io/api/packages?page=41>; rel="last",
    +      <https://www.atom.io/api/packages?page=2>; rel="next"
    +

    By default, results are sorted by download count, descending.

    Searching packages

    Parameters:

    • q (required) - Search query
    • page (optional)
    • sort (optional) - One of downloads, created_at, updated_at, stars. Defaults to the relevance of the search query.
    • direction (optional) - asc or desc. Defaults to desc.

    Returns results in the same format as listing packages.

    Showing package details
    GET /api/packages/:package_name

    Returns package details and versions for a single package

    Parameters:

    • engine (optional) - Only show packages with versions compatible with this Atom version. Must be valid SemVeropen in new window.

    Returns:

      {
    +    "releases": {
    +      "latest": "0.6.0"
    +    },
    +    "name": "thedaniel-test-package",
    +    "repository": {
    +      "type": "git",
    +      "url": "https://github.com/thedaniel/test-package"
    +    },
    +    "versions": [
    +      (see single version output below)
    +      ...,
    +    ]
    +  }
    +
    Creating a package
    POST /api/packages

    Create a new package; requires authentication.

    The name and version will be fetched from the package.json file in the specified repository. The authenticating user must have access to the indicated repository.

    Parameters:

    • repository - String. The repository containing the plugin, in the form "owner/repo"

    Returns:

    • 201 - Successfully created, returns created package.
    • 400 - Repository is inaccessible, nonexistent, not an atom package. Possible error messages include:
      • That repo does not exist, isn't an atom package, or atombot does not have access
      • The package.json at owner/repo isn't valid
    • 409 - A package by that name already exists
    Deleting a package
    DELETE /api/packages/:package_name

    Delete a package; requires authentication.

    Returns:

    • 204 - Success
    • 400 - Repository is inaccessible
    • 401 - Unauthorized
    Renaming a package

    Packages are renamed by publishing a new version with the name changed in package.json. See Creating a new package version for details.

    Requests made to the previous name will forward to the new name.

    Package Versions
    GET /api/packages/:package_name/versions/:version_name

    Returns package.json with dist key added for e.g. tarball download:

    {
    +	"bugs": {
    +		"url": "https://github.com/thedaniel/test-package/issues"
    +	},
    +	"dependencies": {
    +		"async": "~0.2.6",
    +		"pegjs": "~0.7.0",
    +		"season": "~0.13.0"
    +	},
    +	"description": "Expand snippets matching the current prefix with `tab`.",
    +	"dist": {
    +		"tarball": "https://codeload.github.com/..."
    +	},
    +	"engines": {
    +		"atom": "*"
    +	},
    +	"main": "./lib/snippets",
    +	"name": "thedaniel-test-package",
    +	"publishConfig": {
    +		"registry": "https://..."
    +	},
    +	"repository": {
    +		"type": "git",
    +		"url": "https://github.com/thedaniel/test-package.git"
    +	},
    +	"version": "0.6.0"
    +}
    +
    Creating a new package version
    POST /api/packages/:package_name/versions

    Creates a new package version from a git tag; requires authentication. If rename is not true, the name field in package.json must match the current package name.

    Parameters:

    • tag - A git tag for the version you'd like to create. It's important to note that the version name will not be taken from the tag, but from the version key in the package.json file at that ref. The authenticating user must have access to the package repository.
    • rename - Boolean indicating whether this version contains a new name for the package.

    Returns:

    • 201 - Successfully created. Returns created version.
    • 400 - Git tag not found / Repository inaccessible / package.json invalid
    • 409 - Version exists
    Deleting a version
    DELETE /api/packages/:package_name/versions/:version_name

    Deletes a package version; requires authentication.

    Note that a version cannot be republished with a different tag if it is deleted. If you need to delete the latest version of a package for example for security reasons, you'll need to increment the version when republishing.

    Returns 204 No Content

    Stars

    Listing user stars
    GET /api/users/:login/stars

    List a user's starred packages.

    Return value is similar to GET /api/packages

    GET /api/stars

    List the authenticated user's starred packages; requires authentication.

    Return value is similar to GET /api/packages

    Starring a package
    POST /api/packages/:name/star

    Star a package; requires authentication.

    Returns a package.

    Unstarring a package
    DELETE /api/packages/:name/star

    Unstar a package; requires authentication.

    Returns 204 No Content.

    Listing a package's stargazers
    GET /api/packages/:name/stargazers

    List the users that have starred a package.

    Returns a list of user objects:

    [{ "login": "aperson" }, { "login": "anotherperson" }]
    +
    + + + diff --git a/docs/atom-archive/atom-server-side-apis/sections/atom-update-server-api.html b/docs/atom-archive/atom-server-side-apis/sections/atom-update-server-api.html new file mode 100644 index 0000000000..7c2065500b --- /dev/null +++ b/docs/atom-archive/atom-server-side-apis/sections/atom-update-server-api.html @@ -0,0 +1,229 @@ + + + + + + + + + + + + + + +

    Less than 1 minute

    Atom update server API

    WARNING

    Warning: This API should be considered pre-release and is subject to change.

    Atom updates

    Listing Atom updates
    GET /api/updates

    Atom update feed, following the format expected by Squirrelopen in new window.

    Returns:

    {
    +	"name": "0.96.0",
    +	"notes": "[HTML release notes]",
    +	"pub_date": "2014-05-19T15:52:06.000Z",
    +	"url": "https://www.atom.io/api/updates/download"
    +}
    +
    + + + diff --git a/docs/atom-archive/behind-atom/index.html b/docs/atom-archive/behind-atom/index.html new file mode 100644 index 0000000000..55d82d9f3d --- /dev/null +++ b/docs/atom-archive/behind-atom/index.html @@ -0,0 +1,482 @@ + + + + + + + + Chapter 4 : Behind Atom | + + + + + + +

    Chapter 4 : Behind Atom

    Less than 1 minute

    STOP

    This is being kept for archival purposes only from the original Atom documentation. As this may no longer be relevant to Pulsar, use this at your own risk. Current Pulsar documentation is found at documentation home.

    Behind Atom

    Now that we've written a number of packages and themes, let's take minute to take a closer look at some of the ways that Atom works in greater depth. Here we'll go into more of a deep dive on individual internal APIs and systems of Atom, even looking at some Atom source to see how things are really getting done.

    Configuration API

    Reading Config Settings

    If you are writing a package that you want to make configurable, you'll need to read config settings via the atom.config global. You can read the current value of a namespaced config key with atom.config.get:

    // read a value with `config.get`
    +if (atom.config.get("editor.showInvisibles")) {
    +	this.showInvisibles();
    +}
    +

    Or you can subscribe via atom.config.observe to track changes from any view object.

    const {View} = require('space-pen')
    +
    +class MyView extends View {
    +  function attached() {
    +    this.fontSizeObserveSubscription =
    +      atom.config.observe('editor.fontSize', (newValue, {previous}) => {
    +        this.adjustFontSize(newValue)
    +      })
    +  }
    +
    +  function detached() {
    +    this.fontSizeObserveSubscription.dispose()
    +  }
    +}
    +

    The atom.config.observe method will call the given callback immediately with the current value for the specified key path, and it will also call it in the future whenever the value of that key path changes. If you only want to invoke the callback the next time the value changes, use atom.config.onDidChange instead.

    Subscription methods return Disposableopen in new window objects that can be used to unsubscribe. Note in the example above how we save the subscription to the @fontSizeObserveSubscription instance variable and dispose of it when the view is detached. To group multiple subscriptions together, you can add them all to a CompositeDisposableopen in new window that you dispose when the view is detached.

    Writing Config Settings

    The atom.config database is populated on startup from ~/.atom/config.cson%USERPROFILE%\.atom\config.cson, but you can programmatically write to it with atom.config.set:

    // basic key update
    +atom.config.set("core.showInvisibles", true);
    +

    If you're exposing package configuration via specific key paths, you'll want to associate them with a schema in your package's main module. Read more about schemas in the Config API documentationopen in new window.

    Keymaps In-Depth

    Structure of a Keymap File

    Keymap files are encoded as JSON or CSON files containing nested hashes. They work much like style sheets, but instead of applying style properties to elements matching the selector, they specify the meaning of keystrokes on elements matching the selector. Here is an example of some bindings that apply when keystrokes pass through atom-text-editor elements:

    Beneath the first selector are several keybindings, mapping specific key combinations to commands. When an element with the atom-text-editor class is focused and Alt+BackspaceCtrl+Backspace is pressed, a custom DOM event called editor:delete-to-beginning-of-word is emitted on the atom-text-editor element.

    The second selector group also targets editors, but only if they don't have the mini attribute. In this example, the commands for code folding don't really make sense on mini-editors, so the selector restricts them to regular editors.

    Key Combinations

    Key combinations express one or more keys combined with optional modifier keys. For example: ctrl-w v, or cmd-shift-up. A key combination is composed of the following symbols, separated by a -. A key sequence can be expressed as key combinations separated by spaces.

    TypeExamples
    Character literalsa 4 $
    Modifier keyscmd ctrl alt shift
    Special keysenter escape backspace delete tab home end pageup pagedown left right up down space
    Commands

    Commands are custom DOM events that are triggered when a key combination or sequence matches a binding. This allows user interface code to listen for named commands without specifying the specific keybinding that triggers it. For example, the following code creates a command to insert the current date in an editor:

    atom.commands.add("atom-text-editor", {
    +	"user:insert-date": function (event) {
    +		const editor = this.getModel();
    +		return editor.insertText(new Date().toLocaleString());
    +	},
    +});
    +

    atom.commands refers to the global CommandRegistry instance where all commands are set and consequently picked up by the command palette.

    When you are looking to bind new keys, it is often useful to use the Command Palette (Cmd+Shift+PCtrl+Shift+P) to discover what commands are being listened for in a given focus context. Commands are "humanized" following a simple algorithm, so a command like editor:fold-current-row would appear as "Editor: Fold Current Row".

    "Composed" Commands

    A common question is, "How do I make a single keybinding execute two or more commands?" There isn't any direct support for this in Atom, but it can be achieved by creating a custom command that performs the multiple actions you desire and then creating a keybinding for that command. For example, let's say I want to create a "composed" command that performs a Select Line followed by Cut. You could add the following to your init.coffee:

    atom.commands.add("atom-text-editor", "custom:cut-line", function () {
    +	const editor = this.getModel();
    +	editor.selectLinesContainingCursors();
    +	editor.cutSelectedText();
    +});
    +

    Then let's say we want to map this custom command to alt-ctrl-z, you could add the following to your keymap:

    'atom-text-editor':
    +  'alt-ctrl-z': 'custom:cut-line'
    +
    Specificity and Cascade Order

    As is the case with CSS applying styles, when multiple bindings match for a single element, the conflict is resolved by choosing the most specific selector. If two matching selectors have the same specificity, the binding for the selector appearing later in the cascade takes precedence.

    Currently, there's no way to specify selector ordering within a single keymap, because JSON objects do not preserve order. We handle cases where selector ordering is critical by breaking the keymap into separate files, such as snippets-1.cson and snippets-2.cson.

    Selectors and Custom Packages

    If a keybinding should only apply to a specific grammar, you can limit bindings to that grammar using the data-grammar attribute on the atom-text-editor element:

    "atom-text-editor[data-grammar='source example']":
    +  'ctrl-.': 'custom:custom-command'
    +

    While selectors can be applied to the entire editor by what grammar is associated with it, they cannot be applied to scopes defined within the grammar or to sub-elements of atom-text-editor.

    Removing Bindings

    When the keymap system encounters a binding with the unset! directive as its command, it will treat the current element as if it had no key bindings matching the current keystroke sequence and continue searching from its parent. For example, the following code removes the keybinding for a in the Tree View, which is normally used to trigger the tree-view:add-file command:

    '.tree-view':
    +  'a': 'unset!'
    +

    Keybinding Resolver

    But if some element above the Tree View had a keybinding for a, that keybinding would still execute even when the focus is inside the Tree View.

    When the keymap system encounters a binding with the abort! directive as its command, it will stop searching for a keybinding. For example, the following code removes the keybinding for Cmd+OCtrl+O when the selection is inside an editor pane:

    ::: codetabs#keymaps-in-depth

    + + + diff --git a/docs/atom-archive/behind-atom/sections/configuration-api.html b/docs/atom-archive/behind-atom/sections/configuration-api.html new file mode 100644 index 0000000000..82cf8354fd --- /dev/null +++ b/docs/atom-archive/behind-atom/sections/configuration-api.html @@ -0,0 +1,243 @@ + + + + + + + + + + + + + + +

    Less than 1 minute

    Configuration API

    Reading Config Settings

    If you are writing a package that you want to make configurable, you'll need to read config settings via the atom.config global. You can read the current value of a namespaced config key with atom.config.get:

    // read a value with `config.get`
    +if (atom.config.get("editor.showInvisibles")) {
    +	this.showInvisibles();
    +}
    +

    Or you can subscribe via atom.config.observe to track changes from any view object.

    const {View} = require('space-pen')
    +
    +class MyView extends View {
    +  function attached() {
    +    this.fontSizeObserveSubscription =
    +      atom.config.observe('editor.fontSize', (newValue, {previous}) => {
    +        this.adjustFontSize(newValue)
    +      })
    +  }
    +
    +  function detached() {
    +    this.fontSizeObserveSubscription.dispose()
    +  }
    +}
    +

    The atom.config.observe method will call the given callback immediately with the current value for the specified key path, and it will also call it in the future whenever the value of that key path changes. If you only want to invoke the callback the next time the value changes, use atom.config.onDidChange instead.

    Subscription methods return Disposableopen in new window objects that can be used to unsubscribe. Note in the example above how we save the subscription to the @fontSizeObserveSubscription instance variable and dispose of it when the view is detached. To group multiple subscriptions together, you can add them all to a CompositeDisposableopen in new window that you dispose when the view is detached.

    Writing Config Settings

    The atom.config database is populated on startup from ~/.atom/config.cson%USERPROFILE%\.atom\config.cson, but you can programmatically write to it with atom.config.set:

    // basic key update
    +atom.config.set("core.showInvisibles", true);
    +

    If you're exposing package configuration via specific key paths, you'll want to associate them with a schema in your package's main module. Read more about schemas in the Config API documentationopen in new window.

    + + + diff --git a/docs/atom-archive/behind-atom/sections/developing-node-modules.html b/docs/atom-archive/behind-atom/sections/developing-node-modules.html new file mode 100644 index 0000000000..a0451e9504 --- /dev/null +++ b/docs/atom-archive/behind-atom/sections/developing-node-modules.html @@ -0,0 +1,260 @@ + + + + + + + + + + + + + + +

    About 1 min

    Developing Node Modules

    Atom contains a number of packages that are Node modules instead of Atom packages. If you want to make changes to the Node modules, for instance atom-keymap, you have to link them into the development environment differently than you would a normal Atom package.

    Linking a Node Module Into Your Atom Dev Environment

    Here are the steps to run a local version of a Node module within Atom. We're using atom-keymap as an example:

    ::: tab#developing-node-modules

    + + + diff --git a/docs/atom-archive/behind-atom/sections/how-atom-uses-chromium-snapshots.html b/docs/atom-archive/behind-atom/sections/how-atom-uses-chromium-snapshots.html new file mode 100644 index 0000000000..e441d92b0d --- /dev/null +++ b/docs/atom-archive/behind-atom/sections/how-atom-uses-chromium-snapshots.html @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + +

    About 1 min

    How Atom Uses Chromium Snapshots

    In order to improve startup time, when Atom is built we create a V8 snapshotopen in new window in which we preload core services and packages. Then, at runtime, we finish loading Atom by supplying all the information we didn't have during the compilation phase (e.g. loading third party packages, custom style sheets, configuration, etc.).

    electron-linkopen in new window is the tool that powers snapshots, as it enables us to traverse the entire require graph (starting at the entry point) and replace all the forbidden require calls (e.g. require calls to native modules, node core modules or other modules that can't be accessed in the snapshot V8 contextopen in new window) with a function that will be called at runtime. When adding new code to Atom, we always try to put it inside the snapshot by, for example, deferring the usage of DOM APIs or native node modules to a later moment in time when those facilities are available. If that is not possible, we will add the unsupported code paths to the list of files that get excluded from the snapshotopen in new window, ensuring we only exclude those ones that are not supported as opposed to skipping an entire Node module.

    The output of electron-link is a single script containing the code for all the modules reachable from the entry point, which we then supply to mksnapshotopen in new window to generate a snapshot blob.

    The generated blob is finally copied into the application bundleopen in new window and will be automatically loaded by Electron when running Atom.

    + + + diff --git a/docs/atom-archive/behind-atom/sections/interacting-with-other-packages-via-services.html b/docs/atom-archive/behind-atom/sections/interacting-with-other-packages-via-services.html new file mode 100644 index 0000000000..e6535623dd --- /dev/null +++ b/docs/atom-archive/behind-atom/sections/interacting-with-other-packages-via-services.html @@ -0,0 +1,274 @@ + + + + + + + + + + + + + + +

    Less than 1 minute

    Interacting With Other Packages Via Services

    Atom packages can interact with each other through versioned APIs called services. To provide a service, in your package.json, specify one or more version numbers, each paired with the name of a method on your package's main module:

    {
    +	"providedServices": {
    +		"my-service": {
    +			"description": "Does a useful thing",
    +			"versions": {
    +				"1.2.3": "provideMyServiceV1",
    +				"2.3.4": "provideMyServiceV2"
    +			}
    +		}
    +	}
    +}
    +

    In your package's main module, implement the methods named above. These methods will be called any time a package is activated that consumes their corresponding service. They should return a value that implements the service's API.

    module.exports = {
    +	activate() {
    +		// ...
    +	},
    +
    +	provideMyServiceV1() {
    +		return adaptToLegacyAPI(myService);
    +	},
    +
    +	provideMyServiceV2() {
    +		return myService;
    +	},
    +};
    +

    Similarly, to consume a service, specify one or more version rangesopen in new window, each paired with the name of a method on the package's main module:

    {
    +	"consumedServices": {
    +		"another-service": {
    +			"versions": {
    +				"^1.2.3": "consumeAnotherServiceV1",
    +				">=2.3.4 <2.5": "consumeAnotherServiceV2"
    +			}
    +		}
    +	}
    +}
    +

    These methods will be called any time a package is activated that provides their corresponding service. They will receive the service object as an argument. You will usually need to perform some kind of cleanup in the event that the package providing the service is deactivated. To do this, return a Disposable from your service-consuming method:

    const { Disposable } = require("atom");
    +
    +module.exports = {
    +	activate() {
    +		// ...
    +	},
    +
    +	consumeAnotherServiceV1(service) {
    +		useService(adaptServiceFromLegacyAPI(service));
    +		return new Disposable(() => stopUsingService(service));
    +	},
    +
    +	consumeAnotherServiceV2(service) {
    +		useService(service);
    +		return new Disposable(() => stopUsingService(service));
    +	},
    +};
    +
    + + + diff --git a/docs/atom-archive/behind-atom/sections/keymaps-in-depth.html b/docs/atom-archive/behind-atom/sections/keymaps-in-depth.html new file mode 100644 index 0000000000..b4b26494bc --- /dev/null +++ b/docs/atom-archive/behind-atom/sections/keymaps-in-depth.html @@ -0,0 +1,268 @@ + + + + + + + + + + + + + + +

    About 6 min

    Keymaps In-Depth

    Structure of a Keymap File

    Keymap files are encoded as JSON or CSON files containing nested hashes. They work much like style sheets, but instead of applying style properties to elements matching the selector, they specify the meaning of keystrokes on elements matching the selector. Here is an example of some bindings that apply when keystrokes pass through atom-text-editor elements:

    Beneath the first selector are several keybindings, mapping specific key combinations to commands. When an element with the atom-text-editor class is focused and Alt+BackspaceCtrl+Backspace is pressed, a custom DOM event called editor:delete-to-beginning-of-word is emitted on the atom-text-editor element.

    The second selector group also targets editors, but only if they don't have the mini attribute. In this example, the commands for code folding don't really make sense on mini-editors, so the selector restricts them to regular editors.

    Key Combinations

    Key combinations express one or more keys combined with optional modifier keys. For example: ctrl-w v, or cmd-shift-up. A key combination is composed of the following symbols, separated by a -. A key sequence can be expressed as key combinations separated by spaces.

    TypeExamples
    Character literalsa 4 $
    Modifier keyscmd ctrl alt shift
    Special keysenter escape backspace delete tab home end pageup pagedown left right up down space
    Commands

    Commands are custom DOM events that are triggered when a key combination or sequence matches a binding. This allows user interface code to listen for named commands without specifying the specific keybinding that triggers it. For example, the following code creates a command to insert the current date in an editor:

    atom.commands.add("atom-text-editor", {
    +	"user:insert-date": function (event) {
    +		const editor = this.getModel();
    +		return editor.insertText(new Date().toLocaleString());
    +	},
    +});
    +

    atom.commands refers to the global CommandRegistry instance where all commands are set and consequently picked up by the command palette.

    When you are looking to bind new keys, it is often useful to use the Command Palette (Cmd+Shift+PCtrl+Shift+P) to discover what commands are being listened for in a given focus context. Commands are "humanized" following a simple algorithm, so a command like editor:fold-current-row would appear as "Editor: Fold Current Row".

    "Composed" Commands

    A common question is, "How do I make a single keybinding execute two or more commands?" There isn't any direct support for this in Atom, but it can be achieved by creating a custom command that performs the multiple actions you desire and then creating a keybinding for that command. For example, let's say I want to create a "composed" command that performs a Select Line followed by Cut. You could add the following to your init.coffee:

    atom.commands.add("atom-text-editor", "custom:cut-line", function () {
    +	const editor = this.getModel();
    +	editor.selectLinesContainingCursors();
    +	editor.cutSelectedText();
    +});
    +

    Then let's say we want to map this custom command to alt-ctrl-z, you could add the following to your keymap:

    'atom-text-editor':
    +  'alt-ctrl-z': 'custom:cut-line'
    +
    Specificity and Cascade Order

    As is the case with CSS applying styles, when multiple bindings match for a single element, the conflict is resolved by choosing the most specific selector. If two matching selectors have the same specificity, the binding for the selector appearing later in the cascade takes precedence.

    Currently, there's no way to specify selector ordering within a single keymap, because JSON objects do not preserve order. We handle cases where selector ordering is critical by breaking the keymap into separate files, such as snippets-1.cson and snippets-2.cson.

    Selectors and Custom Packages

    If a keybinding should only apply to a specific grammar, you can limit bindings to that grammar using the data-grammar attribute on the atom-text-editor element:

    "atom-text-editor[data-grammar='source example']":
    +  'ctrl-.': 'custom:custom-command'
    +

    While selectors can be applied to the entire editor by what grammar is associated with it, they cannot be applied to scopes defined within the grammar or to sub-elements of atom-text-editor.

    Removing Bindings

    When the keymap system encounters a binding with the unset! directive as its command, it will treat the current element as if it had no key bindings matching the current keystroke sequence and continue searching from its parent. For example, the following code removes the keybinding for a in the Tree View, which is normally used to trigger the tree-view:add-file command:

    '.tree-view':
    +  'a': 'unset!'
    +

    Keybinding Resolver

    But if some element above the Tree View had a keybinding for a, that keybinding would still execute even when the focus is inside the Tree View.

    When the keymap system encounters a binding with the abort! directive as its command, it will stop searching for a keybinding. For example, the following code removes the keybinding for Cmd+OCtrl+O when the selection is inside an editor pane:

    ::: codetabs#keymaps-in-depth

    + + + diff --git a/docs/atom-archive/behind-atom/sections/maintaining-your-packages.html b/docs/atom-archive/behind-atom/sections/maintaining-your-packages.html new file mode 100644 index 0000000000..2bfbe31cd6 --- /dev/null +++ b/docs/atom-archive/behind-atom/sections/maintaining-your-packages.html @@ -0,0 +1,226 @@ + + + + + + + + + + + + + + +

    About 3 min

    Maintaining Your Packages

    While publishing is, by far, the most common action you will perform when working with the packages you provide, there are other things you may need to do.

    Publishing a Package Manually

    Warning

    Danger: 🚨 Publishing a package manually is not a recommended practice and is only for the advanced user who has published packages before. If you perform the steps wrong, you may be unable to publish the new version of your package and may have to completely unpublish your package in order to correct the faulty state. You have been warned.

    Some people prefer to control every aspect of the package publishing process. Normally, the apm tool manages certain details during publishing to keep things consistent and make everything work smoothly. If you're one of those people that prefers to do things manually, there are certain steps you'll have to take in order to make things work just as smoothly as if apm has taken care of things for you.

    Note

    Note: The apm tool will only publish and https://atom.io will only list packages that are hosted on GitHubopen in new window, regardless of what process is used to publish them.

    When you have completed the changes that you want to publish and are ready to start the publishing process, you must perform the following steps on the master branch:

    1. Update the version number in your package's package.json. The version number must match the regular expression: ^\d+\.\d+\.\d+
    2. Commit the version number change
    3. Create a Git tag referencing the above commit. The tag must match the regular expression ^v\d+\.\d+\.\d+ and the part after the v must match the full text of the version number in the package.json
    4. Execute git push --follow-tags
    5. Execute apm publish --tag tagname where tagname must match the name of the tag created in the above step

    Adding a Collaborator

    Some packages get too big for one person. Sometimes priorities change and someone else wants to help out. You can let others help or create co-owners by adding them as a collaboratoropen in new window on the GitHub repository for your package. Note: Anyone that has push access to your repository will have the ability to publish new versions of the package that belongs to that repository.

    You can also have packages that are owned by a GitHub organizationopen in new window. Anyone who is a member of an organization's teamopen in new window which has push access to the package's repository will be able to publish new versions of the package.

    Transferring Ownership

    Warning

    Danger: 🚨 This is a permanent change. There is no going back! 🚨

    If you want to hand off support of your package to someone else, you can do that by transferring the package's repositoryopen in new window to the new owner. Once you do that, they can publish a new version with the updated repository information in the package.json.

    Unpublish Your Package

    If you no longer want to support your package and cannot find anyone to take it over, you can unpublish your package from https://atom.io. For example, if your package is named package-name then the command you would execute is:

    $ apm unpublish <em>package-name</em>
    +

    This will remove your package from the https://atom.io package registry. Anyone who has already downloaded a copy of your package will still have it and be able to use it, but it will no longer be available for installation by others.

    Unpublish a Specific Version

    If you mistakenly published a version of your package or perhaps you find a glaring bug or security hole, you may want to unpublish just that version of your package. For example, if your package is named package-name and the bad version of your package is v1.2.3 then the command you would execute is:

    $ apm unpublish <em>package-name@1.2.3</em>
    +

    This will remove just this particular version from the https://atom.io package registry.

    Rename Your Package

    If you need to rename your package for any reason, you can do so with one simple command – apm publish --rename changes the name field in your package's package.json, pushes a new commit and tag, and publishes your renamed package. Requests made to the previous name will be forwarded to the new name.

    $ apm publish --rename <em>new-package-name</em>
    +

    Tips

    Tip: Once a package name has been used, it cannot be re-used by another package even if the original package is unpublished.

    + + + diff --git a/docs/atom-archive/behind-atom/sections/scoped-settings-scopes-and-scope-descriptors.html b/docs/atom-archive/behind-atom/sections/scoped-settings-scopes-and-scope-descriptors.html new file mode 100644 index 0000000000..af8a364b99 --- /dev/null +++ b/docs/atom-archive/behind-atom/sections/scoped-settings-scopes-and-scope-descriptors.html @@ -0,0 +1,249 @@ + + + + + + + + + + + + + + +

    About 2 min

    Scoped Settings, Scopes and Scope Descriptors

    Atom supports language-specific settings. You can soft wrap only Markdown files, or set the tab length to 4 in Python files.

    Language-specific settings are a subset of something more general we call "scoped settings". Scoped settings allow targeting down to a specific syntax token type. For example, you could conceivably set a setting to target only Ruby comments, only code inside Markdown files, or even only JavaScript function names.

    Scope Names in Syntax Tokens

    Each token in the editor has a collection of scope names. For example, the aforementioned JavaScript function name might have the scope names function and name. An open paren might have the scope names punctuation, parameters, begin.

    Scope names work just like CSS classes. In fact, in the editor, scope names are attached to a token's DOM node as CSS classes.

    Take this piece of JavaScript:

    function functionName() {
    +	console.log("Log it out");
    +}
    +

    In the dev tools, the first line's markup looks like this.

    Markup

    All the class names on the spans are scope names. Any scope name can be used to target a setting's value.

    Scope Selectors

    Scope selectors allow you to target specific tokens just like a CSS selector targets specific nodes in the DOM. Some examples:

    '.source.js' # selects all javascript tokens
    +'.source.js .function.name' # selects all javascript function names
    +'.function.name' # selects all function names in any language
    +

    Config::setopen in new window accepts a scopeSelector. If you'd like to set a setting for JavaScript function names, you can give it the JavaScript function name scopeSelector:

    atom.config.set("my-package.my-setting", "special value", {
    +	scopeSelector: ".source.js .function.name",
    +});
    +

    Scope Descriptors

    A scope descriptor is an Objectopen in new window that wraps an Array of Strings. The Array describes a path from the root of the syntax tree to a token including all scope names for the entire path.

    In our JavaScript example above, a scope descriptor for the function name token would be:

    ["source.js", "meta.function.js", "entity.name.function.js"];
    +

    Config::getopen in new window accepts a scopeDescriptor. You can get the value for your setting scoped to JavaScript function names via:

    const scopeDescriptor = [
    +	"source.js",
    +	"meta.function.js",
    +	"entity.name.function.js",
    +];
    +const value = atom.config.get("my-package.my-setting", {
    +	scope: scopeDescriptor,
    +});
    +

    But, you do not need to generate scope descriptors by hand. There are a couple methods available to get the scope descriptor from the editor:

    Let's revisit our example using these methods:

    const editor = atom.workspace.getActiveTextEditor();
    +const cursor = editor.getLastCursor();
    +const valueAtCursor = atom.config.get("my-package.my-setting", {
    +	scope: cursor.getScopeDescriptor(),
    +});
    +const valueForLanguage = atom.config.get("my-package.my-setting", {
    +	scope: editor.getRootScopeDescriptor(),
    +});
    +
    + + + diff --git a/docs/atom-archive/behind-atom/sections/serialization-in-atom.html b/docs/atom-archive/behind-atom/sections/serialization-in-atom.html new file mode 100644 index 0000000000..e117b6bda2 --- /dev/null +++ b/docs/atom-archive/behind-atom/sections/serialization-in-atom.html @@ -0,0 +1,300 @@ + + + + + + + + + + + + + + +

    About 2 min

    Serialization in Atom

    When a window is refreshed or restored from a previous session, the view and its associated objects are deserialized from a JSON representation that was stored during the window's previous shutdown. For your own views and objects to be compatible with refreshing, you'll need to make them play nicely with the serializing and deserializing.

    Package Serialization Hook

    Your package's main module can optionally include a serialize method, which will be called before your package is deactivated. You should return a JSON-serializable object, which will be handed back to you as an object argument to activate next time it is called. In the following example, the package keeps an instance of MyObject in the same state across refreshes.

    module.exports = {
    +	activate(state) {
    +		this.myObject = state
    +			? atom.deserializers.deserialize(state)
    +			: new MyObject("Hello");
    +	},
    +
    +	serialize() {
    +		return this.myObject.serialize();
    +	},
    +};
    +

    Serialization Methods

    class MyObject {
    +	constructor(data) {
    +		this.data = data;
    +	}
    +
    +	serialize() {
    +		return {
    +			deserializer: "MyObject",
    +			data: this.data,
    +		};
    +	}
    +}
    +
    serialize()

    Objects that you want to serialize should implement .serialize(). This method should return a serializable object, and it must contain a key named deserializer whose value is the name of a registered deserializer that can convert the rest of the data to an object. It's usually just the name of the class itself.

    Registering Deserializers

    The other side of the coin is deserializers, whose job is to convert a state object returned from a previous call to serialize back into a genuine object.

    deserializers in package.json

    The preferred way to register deserializers is via your package's package.json file:

    {
    +  "name": "wordcount",
    +  ...
    +  "deserializers": {
    +    "MyObject": "deserializeMyObject"
    +  }
    +}
    +

    Here, the key ("MyObject") is the name of the deserializer—the same string used by the deserializer field in the object returned by your serialize() method. The value ("deserializeMyObject") is the name of a function in your main module that'll be passed the serialized data and will return a genuine object. For example, your main module might look like this:

    module.exports = {
    +	deserializeMyObject({ data }) {
    +		return new MyObject(data);
    +	},
    +};
    +

    Now you can call the global deserialize method with state returned from serialize, and your class's deserialize method will be selected automatically.

    atom.deserializers.add(klass)

    An alternative is to use the atom.deserializers.add method with your class in order to make it available to the deserialization system. Usually this is used in conjunction with a class-level deserialize method:

    class MyObject {
    +	static initClass() {
    +		atom.deserializers.add(this);
    +	}
    +
    +	static deserialize({ data }) {
    +		return new MyObject(data);
    +	}
    +
    +	constructor(data) {
    +		this.data = data;
    +	}
    +
    +	serialize() {
    +		return {
    +			deserializer: "MyObject",
    +			data: this.data,
    +		};
    +	}
    +}
    +
    +MyObject.initClass();
    +

    While this used to be the standard method of registering a deserializer, the package.json method is now preferred since it allows Atom to defer loading and executing your code until it's actually needed.

    Versioning

    class MyObject {
    +	static initClass() {
    +		atom.deserializers.add(this);
    +
    +		this.version = 2;
    +	}
    +
    +	static deserialize(state) {
    +		// ...
    +	}
    +
    +	serialize() {
    +		return {
    +			version: this.constructor.version,
    +			// ...
    +		};
    +	}
    +}
    +
    +MyObject.initClass();
    +

    Your serializable class can optionally have a class-level @version property and include a version key in its serialized state. When deserializing, Atom will only attempt to call deserialize if the two versions match, and otherwise return undefined. We plan on implementing a migration system in the future, but this at least protects you from improperly deserializing old state.

    + + + diff --git a/docs/atom-archive/behind-atom/sections/summary.html b/docs/atom-archive/behind-atom/sections/summary.html new file mode 100644 index 0000000000..5c5a530784 --- /dev/null +++ b/docs/atom-archive/behind-atom/sections/summary.html @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + +

    Less than 1 minute

    Summary

    You should now have a better understanding of some of the core Atom APIs and systems.

    + + + diff --git a/docs/atom-archive/faq/index.html b/docs/atom-archive/faq/index.html new file mode 100644 index 0000000000..7c67d59c1d --- /dev/null +++ b/docs/atom-archive/faq/index.html @@ -0,0 +1,255 @@ + + + + + + + + Appendix B : FAQ | + + + + + + +

    Appendix B : FAQ

    About 2 min

    STOP

    This is being kept for archival purposes only from the original Atom documentation. As this may no longer be relevant to Pulsar, use this at your own risk. Current Pulsar documentation is found at documentation home.

    FAQ

    The collection of Frequently Asked Questions about Atom.

    Is Atom open source?

    Yes, Atom is licensed under the MIT license.open in new window

    What does Atom cost?

    Since the 6th of May, 2014, Atom has been available for download free of charge for everyoneopen in new window. This includes business and enterprise use.

    What platforms does Atom run on?

    Prebuilt versions of Atom are available for OS X 10.10 or later, Windows 7 or later, RedHat Linux, and Ubuntu Linux.

    If you would like to build from source on Windows, Linux, or OS X, see the Atom READMEopen in new window for more information.

    How can I contribute to Atom?

    You can contribute by creating a packageopen in new window that adds something awesome to Atom!

    Also, if you’d like to contribute to the core editor, one of the bundled packages, or one of the libraries that power Atom, just go to github.com/atomopen in new window.

    You should also read the contributing guideopen in new window before getting started.

    Why does Atom collect usage data?

    In the same way that aggregate usage information is important when developing a web application, we've found that it's just as important for desktop applications. By knowing which Atom features are being used the most, and how the editor is performing, we can focus our development efforts in the right place. For details on what data Atom is sending or to learn how to disable metrics gathering, visit https://github.com/atom/metrics.

    Atom in the cloud?

    The Atom team has no plans to make a cloud- or server-based version of Atom. For discussion of the idea, see the Atom message boardopen in new window.

    What's the difference between an IDE and an editor?

    The term "IDE" comes from Integrated Development Environment. It is intended as a set of tools that all work together: text editor, compiler, build or make integration, debugging, etc. Virtually all IDEs are tied specifically to a language or framework or tightly collected set of languages or frameworks. Some examples: Visual Studio for .NET and other Microsoft languages, RubyMine for Ruby, IntelliJ for Java, XCode for Apple technologies.

    An editor is simply that, a tool that is designed to edit text. Typically they are optimized for programming languages though many programmer's text editors are branching out and adding features for non-programming text like Markdownopen in new window or Org Modeopen in new window. The key here is that text editors are designed to work with whatever language or framework you choose.

    The tradeoff here is that while you can generally get off the ground faster if you're working within the realm of a given IDE, over the long term you spend a bunch of time retraining yourself when you inevitably change from one language or toolchain to the next. If you use an editor, you can continue to use the same workflows that you always have. Tools that you've built into your editor can be carried over to the next language and framework. Your editor becomes more powerful and more customized to how you want to work not just over years but potentially decades. Just ask people who use vim or Emacs ... both of which have been available for over 25 years!

    So, if you want something that you can just jump into and be productive right away in a specific technology, perhaps an IDE is what you're looking for. If you want a tool that you can shape and customize into exactly what you want out of it even if it costs you some time up front configuring things, then an editor is probably more your speed ⚡

    How can I tell if subpixel antialiasing is working?

    If you take a screenshot and blow it up you'll see something like this:

    Subpixel antialiasing enlarged

    The text of lines 34-36 are subpixel antialiased. Line 37 is not. You can tell it is subpixel antialiased because one side of the characters will be shifted red and the other side will be shifted blue. The direction of the shift is dependent on the monitor being used.

    You can find more information on subpixel rendering on Wikipediaopen in new window.

    Why is Atom deleting trailing whitespace? Why is there a newline at the end of the file?

    Atom ships with the whitespace packageopen in new window, which by default strips trailing whitespace from lines in your file, and inserts a final trailing newline to indicate end-of-file as per the POSIX standardopen in new window.

    You can disable this feature by going to the Packages list in the Settings View and finding the whitespace package:

    Whitespace package settings

    Take a look at the Whitespace section for more information.

    What does Safe Mode do?

    Atom's Safe Mode, which can be activated by completely exiting all instances of Atom and launching it again using the command atom --safe from the command line, does the following:

    1. Does not load any packages from ~/.atom/packages or ~/.atom/dev/packages
    2. Does not run your init.coffee
    3. Loads only default-installed themes

    The intent of Safe Mode is to determine if a problem is being caused by a community package or is caused by built-in functionality of Atom. Disabling the init script was added because people tend to use the init script as a mini-package of sorts by adding code, commands and other functionality that would normally be in a package.

    For more information on Safe Mode, check the debugging sectionopen in new window.

    I have a question about a specific Atom community package. Where is the best place to ask it?

    The best place to get a question answered quickly is probably the Issues list for that specific package. You can find the Issues list for a package by going to that package's page on https://atom.io and clicking the Bugs button:

    Bugs button link

    And you can always ask Atom-related questions in the official Atom message boardopen in new window. Someone here may know the answer! It's just with over 3,500 packages (as of early February 2016), the forum members may not know all answers for all packages 😀

    I’m using an international keyboard and keys that use AltGr or Ctrl+Alt aren’t working

    As of Atom v1.12, a fix is available for this. See the blog post "The Wonderful World of Keyboards"open in new window for more information.

    I’m having a problem with Julia! What do I do?

    Junoopen in new window is a development environment built on top of Atom but has enough separate customizations that they have their own message boardopen in new window. You will probably have better luck asking your question there.

    I’m getting an error about a “self-signed certificate”. What do I do?

    This means that there is a proxy between you and our servers where someone (typically your employer) has installed a "self-signed" security certificate in the proxy. A self-signed certificate is one that isn't trusted by anyone but the person who created the certificate. Most security certificates are backed by known, trusted and certified companies. So Atom is warning you that your connection to our servers can be snooped and even hacked by whoever created the self-signed certificate. Since it is self-signed, Atom has no way of knowing who that is.

    If you decide that unsecured connections to our servers is acceptable to you, you can use the following instructions.

    DANGER

    🚨 Danger: If you decide that unsecured connections to our servers is acceptable to you, you can use the following command:

    apm config set strict-ssl false
    +

    I’m having a problem with PlatformIO! What do I do?

    PlatformIOopen in new window is a development environment built on top of Atom but has enough separate customizations that they have their own message boardopen in new window. If your question has to do with PlatformIO specifically, you may have better luck getting your answer there.

    How do I make Atom recognize a file with extension X as language Y?

    Atom includes a feature called "custom file types" which you can use by adding some entries into your config.cson that look like this:

    core:
    +  customFileTypes:
    +    'source.ruby': [
    +      'Cheffile'
    +      'this-is-also-ruby'
    +    ]
    +    'source.cpp': [
    +      'h'
    +    ]
    +

    The key (for example source.ruby in the above snippet) is the language's scope nameopen in new window. The value is an array of file extensions, without the period, to match to that scope name.

    How do I make the Welcome screen stop showing up?

    You can make the Welcome screen stop showing up by unchecking this box in the welcome screen itself:

    Box to uncheck to make the Welcome screen not show next time Atom is launched

    How do I preview web page changes automatically?

    There are a couple different approaches, for example:

    Other packages may be available now, you can search for Atom packages on the packages siteopen in new window.

    How do I accept input from my program or script when using the script package?

    The script packageopen in new window doesn't support accepting input from the user in the scripts it runs. The option with the best chance of success is to run the script or program from the terminal that comes with your operating system. If that isn't something you want to do, you could try one of the many terminal packagesopen in new window that are available.

    See rgbkrk/atom-script#743open in new window for details.

    I am unable to update to the latest version of Atom on macOS. How do I fix this?

    Atom shows there is a new version available but the version fails to install. You might have an error message showing a permissions error for example:

    Updating Atom on macOS

    or it will say downloading but forever loops without restarting or updating.

    You need to fix one or more of the following directories:

    • /Applications/Atom.app/
    • ~/Library/Caches/com.github.atom.ShipIt
    • ~/Library/Application Support/com.github.atom.ShipIt

    Do the following:

    1. Completely exit Atom
    2. Open a terminal
    3. Execute: whoami
    4. Write down the result of the above command, this is your user name

    And then execute these steps for each directory listed above in order:

    1. Execute: stat -f "%Su" [directory]
    2. It should output either your username or root
    3. If it says root then execute: sudo chown -R $(whoami) [directory]

    Once you've done the above for both directories, start Atom normally and attempt to update 👍

    I’m trying to change my syntax colors from styles.less, but it isn’t working!

    The best way to tweak the syntax is to wrap your syntax style rules with atom-text-editor and then prepend every scope with syntax--. If you want your comments to be blue, for example, you would do the following:

    atom-text-editor {
    +	.syntax--comment {
    +		color: blue;
    +	}
    +}
    +

    How do I build or execute code I've written in Atom?

    Atom doesn't have built-in support for building any type of code nor does it have built-in support for executing any kind of code other than JavaScript. Atom has a JavaScript interactive command-line (also known as a REPLopen in new window) available through the Developer Tools. You can access the JavaScript REPL by using the following steps:

    1. Launch Atom
    2. Select the menu View > Developer > Toggle Developer Tools
    3. Click the "Console" tab

    If you're looking for a JavaScript execution environment beyond a REPL, Atom doesn't come with anything built-in for that purpose.

    If you want to build code or execute scripts from within Atom there are a number of packages available including:


    Resources on getting started with languages that are commonly asked about:

    How do I uninstall Atom on macOS?

    To uninstall Atom on macOS, run the following commands from the command line:

    rm -rf ~/.atom
    +rm -rf /usr/local/bin/atom
    +rm -rf /usr/local/bin/apm
    +rm -rf /Applications/Atom.app
    +rm -rf ~/Library/Preferences/com.github.atom.plist
    +rm -rf "~/Library/Application Support/com.github.atom.ShipIt"
    +rm -rf "~/Library/Application Support/Atom"
    +rm -rf "~/Library/Saved Application State/com.github.atom.savedState"
    +rm -rf ~/Library/Caches/com.github.atom
    +rm -rf ~/Library/Caches/Atom
    +

    MacOS Mojave font rendering change

    In macOS Mojave v10.14.xopen in new window, Apple disabled subpixel antialiasing on all monitors by default. Previous to Mojave, subpixel antialiasing was disabled only on Retina displays or on all displays if the "LCD font smoothing" option was disabled in System Preferences. With this change in Mojave, some users have reported that their fonts in Atom appear "thinner" or "dimmer" than they did previously.open in new window It can look better or worse depending on your font and theme selections, but in all cases this is completely a side-effect of the change that Apple made to their font rendering and is outside Atom's and Electron's control.

    If this change is something that you dislike, there are a couple workarounds that the community has identified.

    Change the OS defaults

    1. Execute at the Terminal: defaults write -g CGFontRenderingFontSmoothingDisabled -bool NO
    2. Completely exit Atom
    3. Start Atom again

    This appears to re-enable the old "LCD font smoothing" option that was removed in Mojave. It is important to note that this is an OS-wide change.

    Change your font weight

    Add the following to your stylesheetopen in new window:

    atom-text-editor {
    +	font-weight: bold;
    +}
    +

    This has the benefit of being a change local to Atom only if the rest of the OS looks fine to you.

    Why does macOS say that Atom wants to access my calendar, contacts, photos, etc.?

    With macOS 10.14 Mojave, Apple introduced new privacy protections similar to the existing protections found in iOS. Whenever an application attempts to access the files inside certain newly-protected directories, macOS asks the user whether they want to allow the application to access the content in those directories. These new privacy protections apply to the directories that contain your calendars, contacts, photos, mail, messages, and Time Machine backups.

    Applications trigger these new macOS prompts when attempting to access these directories in any way. Simply attempting to list the files in one of these directories is enough to trigger these prompts. These protections even apply to Apple's own applications. For example, if you open Terminal.app and try to list the files in ~/Library/Calendars, macOS shows a prompt saying, '"Terminal" would like access to your calendar.'

    screen shot 2018-10-03 at 14 04 40 pm

    Why does Atom need access to my calendar, contacts, photos, etc.?

    Atom doesn't need access to these items, but you might unintentionally cause Atom to try to access these items. This commonly occurs when you open your home directory (~) inside Atom and run a command that examines all files and directories beneath your home directory. For example, when you open the fuzzy-finderopen in new window, it indexes the currently-open directory so that it can show you the available files:

    fuzzy-finder can trigger prompt

    Similarly, using find-and-replaceopen in new window across the entire home directory will cause Atom to scan all files under your home directory.

    In addition to containing the files you're intending to edit inside Atom, your home directory also contains your files that have new OS-level protections in Mojave:

    • Calendar files (~/Library/Calendars)
    • Contacts files (~/Library/Application\ Support/AddressBook
    • Mail files (~/Library/Mail)
    • Photos files (~/Pictures/Photos\ Library.photoslibrary)

    Before letting Atom read these files, Mojave is understandably asking whether you want Atom to be able to access this personal data.

    What should I do when I see these prompts?

    Most people don't use Atom to view or edit their calendar files, contact files, photo library, etc. If you don't intend to use Atom to view/edit these files, then Atom doesn't need access to them. If you see a prompt from macOS saying that Atom would like to access these items, simply click Don't Allow.

    What happens if I allow Atom to access my calendar, contacts, photos, etc.?

    To Atom, these items are just files on disk. Atom treats them exactly like any other file you would view in Atom. Therefore, if you allow Atom to access these items, you'll be able to use Atom to browse the directories that contain these items, and you'll be able to view the files in those directories. That's it. Nothing more.

    You'll only be prompted once

    Fortunately, macOS will only prompt you once for each type of personal data. In other words, you might see a prompt asking you whether Atom can access your calendar, and you might see a prompt asking you whether Atom can access your contacts, but once you make those decisions, you won't see those prompts again.

    What if I change my mind?

    At any time, you can change your choices via System Preferences. Inside System Preferences, go to Security and Privacy, click the Privacy tab, and then click on Calendars to manage which apps can access your Calendars. The same goes for Contacts, Photos, etc.:

    manage access in system preferences

    What if I never want to see these prompts?

    Many people understandably expect their text editor to be able to open any file on disk. And that's exactly how things worked prior to macOS Mojave. If you would like to restore that behavior, you can proactively instruct macOS to allow you to access all files with Atom. To do so:

    1. Open your Applications folder in the Finder
    2. Open System Preferences, click the Security and Privacy icon, click the Privacy tab, and then click on Full Disk Access in the left-hand sidebar
    3. Click the lock icon to unlock System Preferences
    4. Drag Atom into Full Disk Access as shown below

    restore pre-mojave security via full-disk access

    How do I turn on line wrap?

    1. Open the Settings View using Cmd+, on macOS or Ctrl+, on other platforms
    2. Click the “Editor” tab on the left of the settings view
    3. Put a check in the “Soft Wrap” setting

    For more details about soft wrap, see: https://flight-manual.atom.io/getting-started/sections/atom-basics/#soft-wrap.

    The menu bar disappeared, how do I get it back?

    If you're running Windows or Linux and you don't see the menu bar, it may have been accidentally toggled it off. You can bring it back from the Command Palette with Window: Toggle Menu Bar or by pressing Alt.

    You can disable hiding the menu bar with Alt by unchecking Settings > Core > Auto Hide Menu Bar.

    How do I use a newline in the result of find and replace?

    To use a newline in the result of find and replace, enable the Use Regex option and use "\n" in your replacement text. For example, given this text:

    hello, world, goodbye
    +

    If you'd like to replace the ", " with a newline so you end up with this text:

    hello
    +world
    +goodbye
    +

    In the find and replace settings, enable Use Regex, enter ", " as the find text, and enter "\n" as the replace text:

    Find and replace with newline replace

    Then click Find All and finally, click Replace All.

    What is this line on the right in the editor view?

    Wrap guide line

    That's the wrap guide. It is a visual indicator of when your lines of code are getting too long. It defaults to the column that your Preferred Line Length is set to.

    If you want to turn it off, you can disable the wrap-guide package in the Settings View.

    + + + diff --git a/docs/atom-archive/faq/sections/atom-in-the-cloud.html b/docs/atom-archive/faq/sections/atom-in-the-cloud.html new file mode 100644 index 0000000000..513d65edf5 --- /dev/null +++ b/docs/atom-archive/faq/sections/atom-in-the-cloud.html @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + +

    Less than 1 minute

    Atom in the cloud?

    The Atom team has no plans to make a cloud- or server-based version of Atom. For discussion of the idea, see the Atom message boardopen in new window.

    + + + diff --git a/docs/atom-archive/faq/sections/how-can-i-contribute-to-atom.html b/docs/atom-archive/faq/sections/how-can-i-contribute-to-atom.html new file mode 100644 index 0000000000..a212944787 --- /dev/null +++ b/docs/atom-archive/faq/sections/how-can-i-contribute-to-atom.html @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + +

    Less than 1 minute

    How can I contribute to Atom?

    You can contribute by creating a packageopen in new window that adds something awesome to Atom!

    Also, if you’d like to contribute to the core editor, one of the bundled packages, or one of the libraries that power Atom, just go to github.com/atomopen in new window.

    You should also read the contributing guideopen in new window before getting started.

    + + + diff --git a/docs/atom-archive/faq/sections/how-can-i-tell-if-subpixel-antialiasing-is-working.html b/docs/atom-archive/faq/sections/how-can-i-tell-if-subpixel-antialiasing-is-working.html new file mode 100644 index 0000000000..b8f12a19f9 --- /dev/null +++ b/docs/atom-archive/faq/sections/how-can-i-tell-if-subpixel-antialiasing-is-working.html @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + +

    Less than 1 minute

    How can I tell if subpixel antialiasing is working?

    If you take a screenshot and blow it up you'll see something like this:

    Subpixel antialiasing enlarged

    The text of lines 34-36 are subpixel antialiased. Line 37 is not. You can tell it is subpixel antialiased because one side of the characters will be shifted red and the other side will be shifted blue. The direction of the shift is dependent on the monitor being used.

    You can find more information on subpixel rendering on Wikipediaopen in new window.

    + + + diff --git a/docs/atom-archive/faq/sections/how-do-i-accept-input-from-my-program-or-script-when-using-the-script-package.html b/docs/atom-archive/faq/sections/how-do-i-accept-input-from-my-program-or-script-when-using-the-script-package.html new file mode 100644 index 0000000000..304a909cae --- /dev/null +++ b/docs/atom-archive/faq/sections/how-do-i-accept-input-from-my-program-or-script-when-using-the-script-package.html @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + +

    Less than 1 minute

    How do I accept input from my program or script when using the script package?

    The script packageopen in new window doesn't support accepting input from the user in the scripts it runs. The option with the best chance of success is to run the script or program from the terminal that comes with your operating system. If that isn't something you want to do, you could try one of the many terminal packagesopen in new window that are available.

    See rgbkrk/atom-script#743open in new window for details.

    + + + diff --git a/docs/atom-archive/faq/sections/how-do-i-build-or-execute-code-i-ve-written-in-atom.html b/docs/atom-archive/faq/sections/how-do-i-build-or-execute-code-i-ve-written-in-atom.html new file mode 100644 index 0000000000..68be6f1e36 --- /dev/null +++ b/docs/atom-archive/faq/sections/how-do-i-build-or-execute-code-i-ve-written-in-atom.html @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + +

    Less than 1 minute

    How do I build or execute code I've written in Atom?

    Atom doesn't have built-in support for building any type of code nor does it have built-in support for executing any kind of code other than JavaScript. Atom has a JavaScript interactive command-line (also known as a REPLopen in new window) available through the Developer Tools. You can access the JavaScript REPL by using the following steps:

    1. Launch Atom
    2. Select the menu View > Developer > Toggle Developer Tools
    3. Click the "Console" tab

    If you're looking for a JavaScript execution environment beyond a REPL, Atom doesn't come with anything built-in for that purpose.

    If you want to build code or execute scripts from within Atom there are a number of packages available including:


    Resources on getting started with languages that are commonly asked about:

    + + + diff --git a/docs/atom-archive/faq/sections/how-do-i-make-atom-recognize-a-file-with-extension-x-as-language-y.html b/docs/atom-archive/faq/sections/how-do-i-make-atom-recognize-a-file-with-extension-x-as-language-y.html new file mode 100644 index 0000000000..8e64d270f2 --- /dev/null +++ b/docs/atom-archive/faq/sections/how-do-i-make-atom-recognize-a-file-with-extension-x-as-language-y.html @@ -0,0 +1,232 @@ + + + + + + + + + + + + + + +

    Less than 1 minute

    How do I make Atom recognize a file with extension X as language Y?

    Atom includes a feature called "custom file types" which you can use by adding some entries into your config.cson that look like this:

    core:
    +  customFileTypes:
    +    'source.ruby': [
    +      'Cheffile'
    +      'this-is-also-ruby'
    +    ]
    +    'source.cpp': [
    +      'h'
    +    ]
    +

    The key (for example source.ruby in the above snippet) is the language's scope nameopen in new window. The value is an array of file extensions, without the period, to match to that scope name.

    + + + diff --git a/docs/atom-archive/faq/sections/how-do-i-make-the-welcome-screen-stop-showing-up.html b/docs/atom-archive/faq/sections/how-do-i-make-the-welcome-screen-stop-showing-up.html new file mode 100644 index 0000000000..1440c12840 --- /dev/null +++ b/docs/atom-archive/faq/sections/how-do-i-make-the-welcome-screen-stop-showing-up.html @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + +

    Less than 1 minute

    How do I make the Welcome screen stop showing up?

    You can make the Welcome screen stop showing up by unchecking this box in the welcome screen itself:

    Box to uncheck to make the Welcome screen not show next time Atom is launched

    + + + diff --git a/docs/atom-archive/faq/sections/how-do-i-preview-web-page-changes-automatically.html b/docs/atom-archive/faq/sections/how-do-i-preview-web-page-changes-automatically.html new file mode 100644 index 0000000000..7a247ccffd --- /dev/null +++ b/docs/atom-archive/faq/sections/how-do-i-preview-web-page-changes-automatically.html @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + +

    Less than 1 minute

    How do I preview web page changes automatically?

    There are a couple different approaches, for example:

    Other packages may be available now, you can search for Atom packages on the packages siteopen in new window.

    + + + diff --git a/docs/atom-archive/faq/sections/how-do-i-turn-on-line-wrap.html b/docs/atom-archive/faq/sections/how-do-i-turn-on-line-wrap.html new file mode 100644 index 0000000000..18341f743c --- /dev/null +++ b/docs/atom-archive/faq/sections/how-do-i-turn-on-line-wrap.html @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + +

    Less than 1 minute

    How do I turn on line wrap?

    1. Open the Settings View using Cmd+, on macOS or Ctrl+, on other platforms
    2. Click the “Editor” tab on the left of the settings view
    3. Put a check in the “Soft Wrap” setting

    For more details about soft wrap, see: https://flight-manual.atom.io/getting-started/sections/atom-basics/#soft-wrap.

    + + + diff --git a/docs/atom-archive/faq/sections/how-do-i-uninstall-atom-on-macos.html b/docs/atom-archive/faq/sections/how-do-i-uninstall-atom-on-macos.html new file mode 100644 index 0000000000..ea47551e80 --- /dev/null +++ b/docs/atom-archive/faq/sections/how-do-i-uninstall-atom-on-macos.html @@ -0,0 +1,233 @@ + + + + + + + + + + + + + + +

    Less than 1 minute

    How do I uninstall Atom on macOS?

    To uninstall Atom on macOS, run the following commands from the command line:

    rm -rf ~/.atom
    +rm -rf /usr/local/bin/atom
    +rm -rf /usr/local/bin/apm
    +rm -rf /Applications/Atom.app
    +rm -rf ~/Library/Preferences/com.github.atom.plist
    +rm -rf "~/Library/Application Support/com.github.atom.ShipIt"
    +rm -rf "~/Library/Application Support/Atom"
    +rm -rf "~/Library/Saved Application State/com.github.atom.savedState"
    +rm -rf ~/Library/Caches/com.github.atom
    +rm -rf ~/Library/Caches/Atom
    +
    + + + diff --git a/docs/atom-archive/faq/sections/how-do-i-use-a-newline-in-the-result-of-find-and-replace.html b/docs/atom-archive/faq/sections/how-do-i-use-a-newline-in-the-result-of-find-and-replace.html new file mode 100644 index 0000000000..be4f859f19 --- /dev/null +++ b/docs/atom-archive/faq/sections/how-do-i-use-a-newline-in-the-result-of-find-and-replace.html @@ -0,0 +1,227 @@ + + + + + + + + + + + + + + +

    Less than 1 minute

    How do I use a newline in the result of find and replace?

    To use a newline in the result of find and replace, enable the Use Regex option and use "\n" in your replacement text. For example, given this text:

    hello, world, goodbye
    +

    If you'd like to replace the ", " with a newline so you end up with this text:

    hello
    +world
    +goodbye
    +

    In the find and replace settings, enable Use Regex, enter ", " as the find text, and enter "\n" as the replace text:

    Find and replace with newline replace

    Then click Find All and finally, click Replace All.

    + + + diff --git a/docs/atom-archive/faq/sections/i-am-unable-to-update-to-the-latest-version-of-atom-on-macos-how-do-i-fix-this.html b/docs/atom-archive/faq/sections/i-am-unable-to-update-to-the-latest-version-of-atom-on-macos-how-do-i-fix-this.html new file mode 100644 index 0000000000..e166541126 --- /dev/null +++ b/docs/atom-archive/faq/sections/i-am-unable-to-update-to-the-latest-version-of-atom-on-macos-how-do-i-fix-this.html @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + +

    Less than 1 minute

    I am unable to update to the latest version of Atom on macOS. How do I fix this?

    Atom shows there is a new version available but the version fails to install. You might have an error message showing a permissions error for example:

    Updating Atom on macOS

    or it will say downloading but forever loops without restarting or updating.

    You need to fix one or more of the following directories:

    • /Applications/Atom.app/
    • ~/Library/Caches/com.github.atom.ShipIt
    • ~/Library/Application Support/com.github.atom.ShipIt

    Do the following:

    1. Completely exit Atom
    2. Open a terminal
    3. Execute: whoami
    4. Write down the result of the above command, this is your user name

    And then execute these steps for each directory listed above in order:

    1. Execute: stat -f "%Su" [directory]
    2. It should output either your username or root
    3. If it says root then execute: sudo chown -R $(whoami) [directory]

    Once you've done the above for both directories, start Atom normally and attempt to update 👍

    + + + diff --git a/docs/atom-archive/faq/sections/i-have-a-question-about-a-specific-atom-community-package-where-is-the-best-place-to-ask-it.html b/docs/atom-archive/faq/sections/i-have-a-question-about-a-specific-atom-community-package-where-is-the-best-place-to-ask-it.html new file mode 100644 index 0000000000..98d78367b4 --- /dev/null +++ b/docs/atom-archive/faq/sections/i-have-a-question-about-a-specific-atom-community-package-where-is-the-best-place-to-ask-it.html @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + +

    Less than 1 minute

    I have a question about a specific Atom community package. Where is the best place to ask it?

    The best place to get a question answered quickly is probably the Issues list for that specific package. You can find the Issues list for a package by going to that package's page on https://atom.io and clicking the Bugs button:

    Bugs button link

    And you can always ask Atom-related questions in the official Atom message boardopen in new window. Someone here may know the answer! It's just with over 3,500 packages (as of early February 2016), the forum members may not know all answers for all packages 😀

    + + + diff --git a/docs/atom-archive/faq/sections/i-m-getting-an-error-about-a-self-signed-certificate-what-do-i-do.html b/docs/atom-archive/faq/sections/i-m-getting-an-error-about-a-self-signed-certificate-what-do-i-do.html new file mode 100644 index 0000000000..fcc66fcfa1 --- /dev/null +++ b/docs/atom-archive/faq/sections/i-m-getting-an-error-about-a-self-signed-certificate-what-do-i-do.html @@ -0,0 +1,224 @@ + + + + + + + + + + + + + + +

    Less than 1 minute

    I’m getting an error about a “self-signed certificate”. What do I do?

    This means that there is a proxy between you and our servers where someone (typically your employer) has installed a "self-signed" security certificate in the proxy. A self-signed certificate is one that isn't trusted by anyone but the person who created the certificate. Most security certificates are backed by known, trusted and certified companies. So Atom is warning you that your connection to our servers can be snooped and even hacked by whoever created the self-signed certificate. Since it is self-signed, Atom has no way of knowing who that is.

    If you decide that unsecured connections to our servers is acceptable to you, you can use the following instructions.

    DANGER

    🚨 Danger: If you decide that unsecured connections to our servers is acceptable to you, you can use the following command:

    apm config set strict-ssl false
    +
    + + + diff --git a/docs/atom-archive/faq/sections/i-m-having-a-problem-with-julia-what-do-i-do.html b/docs/atom-archive/faq/sections/i-m-having-a-problem-with-julia-what-do-i-do.html new file mode 100644 index 0000000000..f969ae295b --- /dev/null +++ b/docs/atom-archive/faq/sections/i-m-having-a-problem-with-julia-what-do-i-do.html @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + +

    Less than 1 minute

    I’m having a problem with Julia! What do I do?

    Junoopen in new window is a development environment built on top of Atom but has enough separate customizations that they have their own message boardopen in new window. You will probably have better luck asking your question there.

    + + + diff --git a/docs/atom-archive/faq/sections/i-m-having-a-problem-with-platformio-what-do-i-do.html b/docs/atom-archive/faq/sections/i-m-having-a-problem-with-platformio-what-do-i-do.html new file mode 100644 index 0000000000..94e3d3ea6a --- /dev/null +++ b/docs/atom-archive/faq/sections/i-m-having-a-problem-with-platformio-what-do-i-do.html @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + +

    Less than 1 minute

    I’m having a problem with PlatformIO! What do I do?

    PlatformIOopen in new window is a development environment built on top of Atom but has enough separate customizations that they have their own message boardopen in new window. If your question has to do with PlatformIO specifically, you may have better luck getting your answer there.

    + + + diff --git a/docs/atom-archive/faq/sections/i-m-trying-to-change-my-syntax-colors-from-styles-less-but-it-isn-t-working.html b/docs/atom-archive/faq/sections/i-m-trying-to-change-my-syntax-colors-from-styles-less-but-it-isn-t-working.html new file mode 100644 index 0000000000..25f4817316 --- /dev/null +++ b/docs/atom-archive/faq/sections/i-m-trying-to-change-my-syntax-colors-from-styles-less-but-it-isn-t-working.html @@ -0,0 +1,228 @@ + + + + + + + + + + + + + + +

    Less than 1 minute

    I’m trying to change my syntax colors from styles.less, but it isn’t working!

    The best way to tweak the syntax is to wrap your syntax style rules with atom-text-editor and then prepend every scope with syntax--. If you want your comments to be blue, for example, you would do the following:

    atom-text-editor {
    +	.syntax--comment {
    +		color: blue;
    +	}
    +}
    +
    + + + diff --git a/docs/atom-archive/faq/sections/i-m-using-an-international-keyboard-and-keys-that-use-altgr-or-ctrl-alt-aren-t-working.html b/docs/atom-archive/faq/sections/i-m-using-an-international-keyboard-and-keys-that-use-altgr-or-ctrl-alt-aren-t-working.html new file mode 100644 index 0000000000..e883ee6a8c --- /dev/null +++ b/docs/atom-archive/faq/sections/i-m-using-an-international-keyboard-and-keys-that-use-altgr-or-ctrl-alt-aren-t-working.html @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + +

    Less than 1 minute

    I’m using an international keyboard and keys that use AltGr or Ctrl+Alt aren’t working

    As of Atom v1.12, a fix is available for this. See the blog post "The Wonderful World of Keyboards"open in new window for more information.

    + + + diff --git a/docs/atom-archive/faq/sections/is-atom-open-source.html b/docs/atom-archive/faq/sections/is-atom-open-source.html new file mode 100644 index 0000000000..bd72b850bb --- /dev/null +++ b/docs/atom-archive/faq/sections/is-atom-open-source.html @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + + + + + diff --git a/docs/atom-archive/faq/sections/macos-mojave-font-rendering-change.html b/docs/atom-archive/faq/sections/macos-mojave-font-rendering-change.html new file mode 100644 index 0000000000..5e421cf5b4 --- /dev/null +++ b/docs/atom-archive/faq/sections/macos-mojave-font-rendering-change.html @@ -0,0 +1,226 @@ + + + + + + + + + + + + + + +

    Less than 1 minute

    MacOS Mojave font rendering change

    In macOS Mojave v10.14.xopen in new window, Apple disabled subpixel antialiasing on all monitors by default. Previous to Mojave, subpixel antialiasing was disabled only on Retina displays or on all displays if the "LCD font smoothing" option was disabled in System Preferences. With this change in Mojave, some users have reported that their fonts in Atom appear "thinner" or "dimmer" than they did previously.open in new window It can look better or worse depending on your font and theme selections, but in all cases this is completely a side-effect of the change that Apple made to their font rendering and is outside Atom's and Electron's control.

    If this change is something that you dislike, there are a couple workarounds that the community has identified.

    Change the OS defaults

    1. Execute at the Terminal: defaults write -g CGFontRenderingFontSmoothingDisabled -bool NO
    2. Completely exit Atom
    3. Start Atom again

    This appears to re-enable the old "LCD font smoothing" option that was removed in Mojave. It is important to note that this is an OS-wide change.

    Change your font weight

    Add the following to your stylesheetopen in new window:

    atom-text-editor {
    +	font-weight: bold;
    +}
    +

    This has the benefit of being a change local to Atom only if the rest of the OS looks fine to you.

    + + + diff --git a/docs/atom-archive/faq/sections/the-menu-bar-disappeared-how-do-i-get-it-back.html b/docs/atom-archive/faq/sections/the-menu-bar-disappeared-how-do-i-get-it-back.html new file mode 100644 index 0000000000..b20767e3a1 --- /dev/null +++ b/docs/atom-archive/faq/sections/the-menu-bar-disappeared-how-do-i-get-it-back.html @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + +

    Less than 1 minute

    The menu bar disappeared, how do I get it back?

    If you're running Windows or Linux and you don't see the menu bar, it may have been accidentally toggled it off. You can bring it back from the Command Palette with Window: Toggle Menu Bar or by pressing Alt.

    You can disable hiding the menu bar with Alt by unchecking Settings > Core > Auto Hide Menu Bar.

    + + + diff --git a/docs/atom-archive/faq/sections/what-does-atom-cost.html b/docs/atom-archive/faq/sections/what-does-atom-cost.html new file mode 100644 index 0000000000..5240802858 --- /dev/null +++ b/docs/atom-archive/faq/sections/what-does-atom-cost.html @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + +

    Less than 1 minute

    What does Atom cost?

    Since the 6th of May, 2014, Atom has been available for download free of charge for everyoneopen in new window. This includes business and enterprise use.

    + + + diff --git a/docs/atom-archive/faq/sections/what-does-safe-mode-do.html b/docs/atom-archive/faq/sections/what-does-safe-mode-do.html new file mode 100644 index 0000000000..4bfd6f93d0 --- /dev/null +++ b/docs/atom-archive/faq/sections/what-does-safe-mode-do.html @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + +

    Less than 1 minute

    What does Safe Mode do?

    Atom's Safe Mode, which can be activated by completely exiting all instances of Atom and launching it again using the command atom --safe from the command line, does the following:

    1. Does not load any packages from ~/.atom/packages or ~/.atom/dev/packages
    2. Does not run your init.coffee
    3. Loads only default-installed themes

    The intent of Safe Mode is to determine if a problem is being caused by a community package or is caused by built-in functionality of Atom. Disabling the init script was added because people tend to use the init script as a mini-package of sorts by adding code, commands and other functionality that would normally be in a package.

    For more information on Safe Mode, check the debugging sectionopen in new window.

    + + + diff --git a/docs/atom-archive/faq/sections/what-is-this-line-on-the-right-in-the-editor-view.html b/docs/atom-archive/faq/sections/what-is-this-line-on-the-right-in-the-editor-view.html new file mode 100644 index 0000000000..169f4d28d1 --- /dev/null +++ b/docs/atom-archive/faq/sections/what-is-this-line-on-the-right-in-the-editor-view.html @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + +

    Less than 1 minute

    What is this line on the right in the editor view?

    Wrap guide line

    That's the wrap guide. It is a visual indicator of when your lines of code are getting too long. It defaults to the column that your Preferred Line Length is set to.

    If you want to turn it off, you can disable the wrap-guide package in the Settings View.

    + + + diff --git a/docs/atom-archive/faq/sections/what-platforms-does-atom-run-on.html b/docs/atom-archive/faq/sections/what-platforms-does-atom-run-on.html new file mode 100644 index 0000000000..ed1a211a7d --- /dev/null +++ b/docs/atom-archive/faq/sections/what-platforms-does-atom-run-on.html @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + +

    Less than 1 minute

    What platforms does Atom run on?

    Prebuilt versions of Atom are available for OS X 10.10 or later, Windows 7 or later, RedHat Linux, and Ubuntu Linux.

    If you would like to build from source on Windows, Linux, or OS X, see the Atom READMEopen in new window for more information.

    + + + diff --git a/docs/atom-archive/faq/sections/what-s-the-difference-between-an-ide-and-an-editor.html b/docs/atom-archive/faq/sections/what-s-the-difference-between-an-ide-and-an-editor.html new file mode 100644 index 0000000000..9c1ce925a5 --- /dev/null +++ b/docs/atom-archive/faq/sections/what-s-the-difference-between-an-ide-and-an-editor.html @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + +

    About 1 min

    What's the difference between an IDE and an editor?

    The term "IDE" comes from Integrated Development Environment. It is intended as a set of tools that all work together: text editor, compiler, build or make integration, debugging, etc. Virtually all IDEs are tied specifically to a language or framework or tightly collected set of languages or frameworks. Some examples: Visual Studio for .NET and other Microsoft languages, RubyMine for Ruby, IntelliJ for Java, XCode for Apple technologies.

    An editor is simply that, a tool that is designed to edit text. Typically they are optimized for programming languages though many programmer's text editors are branching out and adding features for non-programming text like Markdownopen in new window or Org Modeopen in new window. The key here is that text editors are designed to work with whatever language or framework you choose.

    The tradeoff here is that while you can generally get off the ground faster if you're working within the realm of a given IDE, over the long term you spend a bunch of time retraining yourself when you inevitably change from one language or toolchain to the next. If you use an editor, you can continue to use the same workflows that you always have. Tools that you've built into your editor can be carried over to the next language and framework. Your editor becomes more powerful and more customized to how you want to work not just over years but potentially decades. Just ask people who use vim or Emacs ... both of which have been available for over 25 years!

    So, if you want something that you can just jump into and be productive right away in a specific technology, perhaps an IDE is what you're looking for. If you want a tool that you can shape and customize into exactly what you want out of it even if it costs you some time up front configuring things, then an editor is probably more your speed ⚡

    + + + diff --git a/docs/atom-archive/faq/sections/why-does-atom-collect-usage-data.html b/docs/atom-archive/faq/sections/why-does-atom-collect-usage-data.html new file mode 100644 index 0000000000..05ffa116a0 --- /dev/null +++ b/docs/atom-archive/faq/sections/why-does-atom-collect-usage-data.html @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + +

    Less than 1 minute

    Why does Atom collect usage data?

    In the same way that aggregate usage information is important when developing a web application, we've found that it's just as important for desktop applications. By knowing which Atom features are being used the most, and how the editor is performing, we can focus our development efforts in the right place. For details on what data Atom is sending or to learn how to disable metrics gathering, visit https://github.com/atom/metrics.

    + + + diff --git a/docs/atom-archive/faq/sections/why-does-macos-say-that-atom-wants-to-access-my-calendar-contacts-photos-etc.html b/docs/atom-archive/faq/sections/why-does-macos-say-that-atom-wants-to-access-my-calendar-contacts-photos-etc.html new file mode 100644 index 0000000000..42653a8610 --- /dev/null +++ b/docs/atom-archive/faq/sections/why-does-macos-say-that-atom-wants-to-access-my-calendar-contacts-photos-etc.html @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + +

    About 3 min

    Why does macOS say that Atom wants to access my calendar, contacts, photos, etc.?

    With macOS 10.14 Mojave, Apple introduced new privacy protections similar to the existing protections found in iOS. Whenever an application attempts to access the files inside certain newly-protected directories, macOS asks the user whether they want to allow the application to access the content in those directories. These new privacy protections apply to the directories that contain your calendars, contacts, photos, mail, messages, and Time Machine backups.

    Applications trigger these new macOS prompts when attempting to access these directories in any way. Simply attempting to list the files in one of these directories is enough to trigger these prompts. These protections even apply to Apple's own applications. For example, if you open Terminal.app and try to list the files in ~/Library/Calendars, macOS shows a prompt saying, '"Terminal" would like access to your calendar.'

    screen shot 2018-10-03 at 14 04 40 pm

    Why does Atom need access to my calendar, contacts, photos, etc.?

    Atom doesn't need access to these items, but you might unintentionally cause Atom to try to access these items. This commonly occurs when you open your home directory (~) inside Atom and run a command that examines all files and directories beneath your home directory. For example, when you open the fuzzy-finderopen in new window, it indexes the currently-open directory so that it can show you the available files:

    fuzzy-finder can trigger prompt

    Similarly, using find-and-replaceopen in new window across the entire home directory will cause Atom to scan all files under your home directory.

    In addition to containing the files you're intending to edit inside Atom, your home directory also contains your files that have new OS-level protections in Mojave:

    • Calendar files (~/Library/Calendars)
    • Contacts files (~/Library/Application\ Support/AddressBook
    • Mail files (~/Library/Mail)
    • Photos files (~/Pictures/Photos\ Library.photoslibrary)

    Before letting Atom read these files, Mojave is understandably asking whether you want Atom to be able to access this personal data.

    What should I do when I see these prompts?

    Most people don't use Atom to view or edit their calendar files, contact files, photo library, etc. If you don't intend to use Atom to view/edit these files, then Atom doesn't need access to them. If you see a prompt from macOS saying that Atom would like to access these items, simply click Don't Allow.

    What happens if I allow Atom to access my calendar, contacts, photos, etc.?

    To Atom, these items are just files on disk. Atom treats them exactly like any other file you would view in Atom. Therefore, if you allow Atom to access these items, you'll be able to use Atom to browse the directories that contain these items, and you'll be able to view the files in those directories. That's it. Nothing more.

    You'll only be prompted once

    Fortunately, macOS will only prompt you once for each type of personal data. In other words, you might see a prompt asking you whether Atom can access your calendar, and you might see a prompt asking you whether Atom can access your contacts, but once you make those decisions, you won't see those prompts again.

    What if I change my mind?

    At any time, you can change your choices via System Preferences. Inside System Preferences, go to Security and Privacy, click the Privacy tab, and then click on Calendars to manage which apps can access your Calendars. The same goes for Contacts, Photos, etc.:

    manage access in system preferences

    What if I never want to see these prompts?

    Many people understandably expect their text editor to be able to open any file on disk. And that's exactly how things worked prior to macOS Mojave. If you would like to restore that behavior, you can proactively instruct macOS to allow you to access all files with Atom. To do so:

    1. Open your Applications folder in the Finder
    2. Open System Preferences, click the Security and Privacy icon, click the Privacy tab, and then click on Full Disk Access in the left-hand sidebar
    3. Click the lock icon to unlock System Preferences
    4. Drag Atom into Full Disk Access as shown below

    restore pre-mojave security via full-disk access

    + + + diff --git a/docs/atom-archive/faq/sections/why-is-atom-deleting-trailing-whitespace-why-is-there-a-newline-at-the-end-of-the-file.html b/docs/atom-archive/faq/sections/why-is-atom-deleting-trailing-whitespace-why-is-there-a-newline-at-the-end-of-the-file.html new file mode 100644 index 0000000000..ef33e697f3 --- /dev/null +++ b/docs/atom-archive/faq/sections/why-is-atom-deleting-trailing-whitespace-why-is-there-a-newline-at-the-end-of-the-file.html @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + +

    Less than 1 minute

    Why is Atom deleting trailing whitespace? Why is there a newline at the end of the file?

    Atom ships with the whitespace packageopen in new window, which by default strips trailing whitespace from lines in your file, and inserts a final trailing newline to indicate end-of-file as per the POSIX standardopen in new window.

    You can disable this feature by going to the Packages list in the Settings View and finding the whitespace package:

    Whitespace package settings

    Take a look at the Whitespace section for more information.

    + + + diff --git a/docs/atom-archive/getting-started/index.html b/docs/atom-archive/getting-started/index.html new file mode 100644 index 0000000000..1dace85344 --- /dev/null +++ b/docs/atom-archive/getting-started/index.html @@ -0,0 +1,242 @@ + + + + + + + + Chapter 1 : Getting started | + + + + + + +

    Chapter 1 : Getting started

    Less than 1 minute

    STOP

    This is being kept for archival purposes only from the original Atom documentation. As this may no longer be relevant to Pulsar, you use this at your own risk. Current Pulsar documentation for this section is found at the documentation home.

    Getting Started

    This chapter is about getting started with Atom.

    Why Atom?

    There are a lot of text editors out there; why should you spend your time learning about and using Atom?

    Editors like Sublime and TextMate offer convenience but only limited extensibility. On the other end of the spectrum, Emacs and Vim offer extreme flexibility, but they aren't very approachable and can only be customized with special-purpose scripting languages.

    We think we can do better. Our goal is a zero-compromise combination of hackability and usability: an editor that will be welcoming to an elementary school student on their first day learning to code, but also a tool they won't outgrow as they develop into seasoned hackers.

    As we've used Atom to build Atom, what began as an experiment has gradually matured into a tool we can't live without. On the surface, Atom is the modern desktop text editor you've come to expect. Pop the hood, however, and you'll discover a system begging to be hacked on.

    The Nucleus of Atom

    The web is not without its faults, but two decades of development has forged it into an incredibly malleable and powerful platform. So when we set out to write a text editor that we ourselves would want to extend, web technology was the obvious choice. But first, we had to free it from its chains.

    The Native Web

    Web browsers are great for browsing web pages, but writing code is a specialized activity that warrants dedicated tools. More importantly, the browser severely restricts access to the local system for security reasons, and for us, a text editor that couldn't write files or run local subprocesses was a non-starter.

    For this reason, we didn't build Atom as a traditional web application. Instead, Atom is a specialized variant of Chromium designed to be a text editor rather than a web browser. Every Atom window is essentially a locally-rendered web page.

    All the APIs available to a typical Node.js application are also available to the code running in each window's JavaScript context. This hybrid provides a unique client-side development experience.

    Since everything is local, you don't have to worry about asset pipelines, script concatenation, and asynchronous module definitions. If you want to load some code, just require it at the top of your file. Node's module system makes it easy to break the system down into lots of small, focused packages.

    JavaScript, Meet C++

    Interacting with native code is also really simple. For example, we wrote a wrapper around the Oniguruma regular expression engine for our TextMate grammar support. In a browser, that would have required adventures with NaCl or Esprima. Node integration made it easy.

    In addition to the Node APIs, we also expose APIs for native dialogs, adding application and context menu items, manipulating the window dimensions, etc.

    Web Tech: The Fun Parts

    Another great benefit, that comes with writing code for Atom, is the guarantee that it's running on the newest version of Chromium. That means we can ignore issues like browser compatibility and polyfills. We can use all the web's shiny features of tomorrow, today.

    For example, the layout of our workspace and panes is based on flexbox. It's an emerging standard and has gone through a lot of change since we started using it, but none of that mattered as long as it worked.

    With the entire industry pushing web technology forward, we're confident that we're building Atom on fertile ground. Native UI technologies come and go, but the web is a standard that becomes more capable and ubiquitous with every passing year. We're excited to dig deeper into its toolbox.

    An Open-Source Text Editor

    We see Atom as a perfect complement to GitHub's primary mission of building better software by working together. Atom is a long-term investment, and GitHub will continue to support its development with a dedicated team going forward. But we also know that we can't achieve our vision for Atom alone. As Emacs and Vim have demonstrated over the past three decades, if you want to build a thriving, long-lasting community around a text editor, it has to be open source.

    The entire Atom editor is free and open source and is available under the https://github.com/atom organization.

    Installing Atom

    To get started with Atom, we'll need to get it on your system. This section will go over installing Atom on your system, as well as the basics of how to build it from source.

    Installing Atom should be fairly simple. Generally, you can go to https://atom.io and you should see a download button as shown here:

    The button or buttons should be specific to your platform and the download package should be easily installable. However, let's go over them here in a bit of detail.

    Updating Atom

    You should consider updating Atom periodically for the latest improvements to the software. Additionally, When Atom receives hotfixes for security vulnerabilities you will want to update your version of Atom as soon as possible.

    Portable Mode

    Atom stores configuration and state in a .atom directory usually located in your home directory (%userprofile% on Windows). You can however run Atom in portable mode where both the app and the configuration are stored together such as on a removable storage device.

    To setup Atom in portable mode download the zip/tar.gz package for your systemopen in new window and extract it to your removable storage.

    Portable Notes
    • The .atom directory must be writeable
    • You can move an existing .atom directory to your portable device
    • Atom can also store its Electron user data in your .atom directory - just create a subdirectory called electronUserData inside .atom
    • Alternatively you can set the ATOM_HOME environment variable to point wherever you want (you can write a .sh or .cmd script to temporarily set it and launch it from that)
    • Portable mode installations will not automatically update

    Building Atom from Source

    The Hacking on Atom Core section of the flight manual covers instructions on how to clone and build the source code if you prefer that option.

    Proxy and Firewall Settings

    Behind a Firewall?

    If you are behind a firewall and seeing SSL errors when installing packages you can disable strict SSL by running:

    $ apm config set strict-ssl false
    +
    Using a Proxy?

    If you are using a HTTP(S) proxy you can configure apm to use it by running:

    $ apm config set https-proxy <em>YOUR_PROXY_ADDRESS</em>
    +

    You can run apm config get https-proxy to verify it has been set correctly.

    Atom Basics

    Now that Atom is installed on your system, let's fire it up, configure it and get acquainted with the editor.

    When you launch Atom for the first time, you should get a screen that looks like this:

    Atom's welcome screen

    This is the Atom welcome screen and gives you a pretty good starting point for how to get started with the editor.

    Terminology

    You can find definitions for all of the various terms that we use throughout the manual in our Glossary.

    Command Palette

    In that welcome screen, we are introduced to probably the most important command in Atom, the Command Palette. If you press Cmd+Shift+PCtrl+Shift+P while focused in an editor pane, the command palette will pop up.

    Note:

    Throughout the book, we will use shortcut keybindings like Cmd+Shift+PCtrl+Shift+P to demonstrate how to run a command. These are the default keybindings for the platform that we detected you running.

    If you want to see a different platform than the one we detected, you may choose a different one by using the platform selector near the top of the page:

    Platform Selector

    If the Platform Selector is not present, then the current page doesn't have any platform-specific content.

    If you have customized your Atom keymap, you can always see the keybinding you have mapped in the Command Palette or the Keybindings tab in the Settings View.

    This search-driven menu can do just about any major task that is possible in Atom. Instead of clicking around all the application menus to look for something, you can press Cmd+Shift+PCtrl+Shift+P and search for the command.

    Command Palette

    Not only can you see and quickly search through thousands of possible commands, but you can also see if there is a keybinding associated with it. This is great because it means you can guess your way to doing interesting things while also learning the shortcut keystrokes for doing it.

    For the rest of the book, we will try to be clear as to the text you can search for in the Command Palette in addition to the keybinding for different commands.

    Settings and Preferences

    Atom has a number of settings and preferences you can modify in the Settings View.

    Settings View

    This includes things like changing the theme, specifying how to handle wrapping, font settings, tab size, scroll speed and much more. You can also use this screen to install new packages and themes, which we'll cover in Atom Packages.

    To open the Settings View, you can: ::: tabs Settings View

    + + + diff --git a/docs/atom-archive/getting-started/sections/atom-basics.html b/docs/atom-archive/getting-started/sections/atom-basics.html new file mode 100644 index 0000000000..8a3a963d2a --- /dev/null +++ b/docs/atom-archive/getting-started/sections/atom-basics.html @@ -0,0 +1,240 @@ + + + + + + + + + + + + + + +

    About 8 min

    Atom Basics

    Now that Atom is installed on your system, let's fire it up, configure it and get acquainted with the editor.

    When you launch Atom for the first time, you should get a screen that looks like this:

    Atom's welcome screen

    This is the Atom welcome screen and gives you a pretty good starting point for how to get started with the editor.

    Terminology

    You can find definitions for all of the various terms that we use throughout the manual in our Glossary.

    Command Palette

    In that welcome screen, we are introduced to probably the most important command in Atom, the Command Palette. If you press Cmd+Shift+PCtrl+Shift+P while focused in an editor pane, the command palette will pop up.

    Note:

    Throughout the book, we will use shortcut keybindings like Cmd+Shift+PCtrl+Shift+P to demonstrate how to run a command. These are the default keybindings for the platform that we detected you running.

    If you want to see a different platform than the one we detected, you may choose a different one by using the platform selector near the top of the page:

    Platform Selector

    If the Platform Selector is not present, then the current page doesn't have any platform-specific content.

    If you have customized your Atom keymap, you can always see the keybinding you have mapped in the Command Palette or the Keybindings tab in the Settings View.

    This search-driven menu can do just about any major task that is possible in Atom. Instead of clicking around all the application menus to look for something, you can press Cmd+Shift+PCtrl+Shift+P and search for the command.

    Command Palette

    Not only can you see and quickly search through thousands of possible commands, but you can also see if there is a keybinding associated with it. This is great because it means you can guess your way to doing interesting things while also learning the shortcut keystrokes for doing it.

    For the rest of the book, we will try to be clear as to the text you can search for in the Command Palette in addition to the keybinding for different commands.

    Settings and Preferences

    Atom has a number of settings and preferences you can modify in the Settings View.

    Settings View

    This includes things like changing the theme, specifying how to handle wrapping, font settings, tab size, scroll speed and much more. You can also use this screen to install new packages and themes, which we'll cover in Atom Packages.

    To open the Settings View, you can: ::: tabs Settings View

    + + + diff --git a/docs/atom-archive/getting-started/sections/installing-atom.html b/docs/atom-archive/getting-started/sections/installing-atom.html new file mode 100644 index 0000000000..48c3e27a86 --- /dev/null +++ b/docs/atom-archive/getting-started/sections/installing-atom.html @@ -0,0 +1,225 @@ + + + + + + + + + + + + + + +

    About 5 min

    Installing Atom

    To get started with Atom, we'll need to get it on your system. This section will go over installing Atom on your system, as well as the basics of how to build it from source.

    Installing Atom should be fairly simple. Generally, you can go to https://atom.io and you should see a download button as shown here:

    The button or buttons should be specific to your platform and the download package should be easily installable. However, let's go over them here in a bit of detail.

    Updating Atom

    You should consider updating Atom periodically for the latest improvements to the software. Additionally, When Atom receives hotfixes for security vulnerabilities you will want to update your version of Atom as soon as possible.

    Portable Mode

    Atom stores configuration and state in a .atom directory usually located in your home directory (%userprofile% on Windows). You can however run Atom in portable mode where both the app and the configuration are stored together such as on a removable storage device.

    To setup Atom in portable mode download the zip/tar.gz package for your systemopen in new window and extract it to your removable storage.

    Portable Notes
    • The .atom directory must be writeable
    • You can move an existing .atom directory to your portable device
    • Atom can also store its Electron user data in your .atom directory - just create a subdirectory called electronUserData inside .atom
    • Alternatively you can set the ATOM_HOME environment variable to point wherever you want (you can write a .sh or .cmd script to temporarily set it and launch it from that)
    • Portable mode installations will not automatically update

    Building Atom from Source

    The Hacking on Atom Core section of the flight manual covers instructions on how to clone and build the source code if you prefer that option.

    Proxy and Firewall Settings

    Behind a Firewall?

    If you are behind a firewall and seeing SSL errors when installing packages you can disable strict SSL by running:

    $ apm config set strict-ssl false
    +
    Using a Proxy?

    If you are using a HTTP(S) proxy you can configure apm to use it by running:

    $ apm config set https-proxy <em>YOUR_PROXY_ADDRESS</em>
    +

    You can run apm config get https-proxy to verify it has been set correctly.

    + + + diff --git a/docs/atom-archive/getting-started/sections/summary.html b/docs/atom-archive/getting-started/sections/summary.html new file mode 100644 index 0000000000..e3894bd166 --- /dev/null +++ b/docs/atom-archive/getting-started/sections/summary.html @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + +

    Less than 1 minute

    Summary

    You should now have a basic understanding of what Atom is and what you want to do with it. You should also have it installed on your system and be able to use it for the most basic text editing operations.

    Now you're ready to start digging into the fun stuff.

    + + + diff --git a/docs/atom-archive/getting-started/sections/why-atom.html b/docs/atom-archive/getting-started/sections/why-atom.html new file mode 100644 index 0000000000..94ceb08efc --- /dev/null +++ b/docs/atom-archive/getting-started/sections/why-atom.html @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + +

    About 2 min

    Why Atom?

    There are a lot of text editors out there; why should you spend your time learning about and using Atom?

    Editors like Sublime and TextMate offer convenience but only limited extensibility. On the other end of the spectrum, Emacs and Vim offer extreme flexibility, but they aren't very approachable and can only be customized with special-purpose scripting languages.

    We think we can do better. Our goal is a zero-compromise combination of hackability and usability: an editor that will be welcoming to an elementary school student on their first day learning to code, but also a tool they won't outgrow as they develop into seasoned hackers.

    As we've used Atom to build Atom, what began as an experiment has gradually matured into a tool we can't live without. On the surface, Atom is the modern desktop text editor you've come to expect. Pop the hood, however, and you'll discover a system begging to be hacked on.

    The Nucleus of Atom

    The web is not without its faults, but two decades of development has forged it into an incredibly malleable and powerful platform. So when we set out to write a text editor that we ourselves would want to extend, web technology was the obvious choice. But first, we had to free it from its chains.

    The Native Web

    Web browsers are great for browsing web pages, but writing code is a specialized activity that warrants dedicated tools. More importantly, the browser severely restricts access to the local system for security reasons, and for us, a text editor that couldn't write files or run local subprocesses was a non-starter.

    For this reason, we didn't build Atom as a traditional web application. Instead, Atom is a specialized variant of Chromium designed to be a text editor rather than a web browser. Every Atom window is essentially a locally-rendered web page.

    All the APIs available to a typical Node.js application are also available to the code running in each window's JavaScript context. This hybrid provides a unique client-side development experience.

    Since everything is local, you don't have to worry about asset pipelines, script concatenation, and asynchronous module definitions. If you want to load some code, just require it at the top of your file. Node's module system makes it easy to break the system down into lots of small, focused packages.

    JavaScript, Meet C++

    Interacting with native code is also really simple. For example, we wrote a wrapper around the Oniguruma regular expression engine for our TextMate grammar support. In a browser, that would have required adventures with NaCl or Esprima. Node integration made it easy.

    In addition to the Node APIs, we also expose APIs for native dialogs, adding application and context menu items, manipulating the window dimensions, etc.

    Web Tech: The Fun Parts

    Another great benefit, that comes with writing code for Atom, is the guarantee that it's running on the newest version of Chromium. That means we can ignore issues like browser compatibility and polyfills. We can use all the web's shiny features of tomorrow, today.

    For example, the layout of our workspace and panes is based on flexbox. It's an emerging standard and has gone through a lot of change since we started using it, but none of that mattered as long as it worked.

    With the entire industry pushing web technology forward, we're confident that we're building Atom on fertile ground. Native UI technologies come and go, but the web is a standard that becomes more capable and ubiquitous with every passing year. We're excited to dig deeper into its toolbox.

    An Open-Source Text Editor

    We see Atom as a perfect complement to GitHub's primary mission of building better software by working together. Atom is a long-term investment, and GitHub will continue to support its development with a dedicated team going forward. But we also know that we can't achieve our vision for Atom alone. As Emacs and Vim have demonstrated over the past three decades, if you want to build a thriving, long-lasting community around a text editor, it has to be open source.

    The entire Atom editor is free and open source and is available under the https://github.com/atom organization.

    + + + diff --git a/docs/atom-archive/hacking-atom/index.html b/docs/atom-archive/hacking-atom/index.html new file mode 100644 index 0000000000..368089ae3e --- /dev/null +++ b/docs/atom-archive/hacking-atom/index.html @@ -0,0 +1,1008 @@ + + + + + + + + Chapter 3 : Hacking Atom | + + + + + + +

    Chapter 3 : Hacking Atom

    About 1 min

    STOP

    This is being kept for archival purposes only from the original Atom documentation. As this may no longer be relevant to Pulsar, you use this at your own risk. Current Pulsar documentation for this section is found at the documentation home.

    Hacking Atom

    Now it's time to come to the "Hackable" part of the Hackable Editor. As we've seen throughout the second section, a huge part of Atom is made up of bundled packages. If you wish to add some functionality to Atom, you have access to the same APIs and tools that the core features of Atom has. From the tree-viewopen in new window to the command-paletteopen in new window to find-and-replaceopen in new window functionality, even the most core features of Atom are implemented as packages.

    In this chapter, we're going to learn how to extend the functionality of Atom through writing packages. This will be everything from new user interfaces to new language grammars to new themes. We'll learn this by writing a series of increasingly complex packages together, introducing you to new APIs and tools and techniques as we need them.

    If you're looking for an example using a specific API or feature, you can skip to the end of the chapter where we've indexed all the examples that way.

    Tools of the Trade

    To begin, there are a few things we'll assume you know, at least to some degree. Since all of Atom is implemented using web technologies, we have to assume you know web technologies such as JavaScript and CSS. Specifically, we'll be using Less, which is a preprocessor for CSS.

    While much of Atom has been converted to JavaScript, a lot of older code has been left implemented in CoffeeScript because changing it would have been too risky. Additionally, Atom's default configuration language is CSON, which is based on CoffeeScript. If you don't know CoffeeScript, but you are familiar with JavaScript, you shouldn't have too much trouble. Here is an example of some simple CoffeeScript code:

    MyPackageView = require './my-package-view'
    +
    +module.exports =
    +  myPackageView: null
    +
    +  activate: (state) ->
    +    @myPackageView = new MyPackageView(state.myPackageViewState)
    +
    +  deactivate: ->
    +    @myPackageView.destroy()
    +
    +  serialize: ->
    +    myPackageViewState: @myPackageView.serialize()
    +

    We'll go over examples like this in a bit, but this is what the language looks like. Just about everything you can do with CoffeeScript in Atom is also doable in JavaScript. You can brush up on CoffeeScript at coffeescript.orgopen in new window.

    Less is an even simpler transition from CSS. It adds a number of useful things like variables and functions to CSS. You can learn about Less at lesscss.orgopen in new window. Our usage of Less won't get too complex in this book however, so as long as you know basic CSS you should be fine.

    The Init File

    When Atom finishes loading, it will evaluate init.coffee in your ~/.atom%USERPROFILE%\.atom directory, giving you a chance to run CoffeeScript code to make customizations. Code in this file has full access to Atom's APIopen in new window. If customizations become extensive, consider creating a package, which we will cover in Package: Word Count.

    You can open the init.coffee file in an editor from the Atom > Init ScriptFile > Init ScriptEdit > Init Script menu. This file can also be named init.js and contain JavaScript code.

    For example, if you have the Audio Beep configuration setting enabled, you could add the following code to your init.coffee file to have Atom greet you with an audio beep every time it loads:

    atom.beep()
    +

    Because init.coffee provides access to Atom's API, you can use it to implement useful commands without creating a new package or extending an existing one. Here's a command which uses the Selection APIopen in new window and Clipboard APIopen in new window to construct a Markdown link from the selected text and the clipboard contents as the URL:

    atom.commands.add 'atom-text-editor', 'markdown:paste-as-link', ->
    +  return unless editor = atom.workspace.getActiveTextEditor()
    +
    +  selection = editor.getLastSelection()
    +  clipboardText = atom.clipboard.read()
    +
    +  selection.insertText("[#{selection.getText()}](#{clipboardText})")
    +

    Now, reload Atom and use the Command Palette to execute the new command, "Markdown: Paste As Link", by name. And if you'd like to trigger the command via a keyboard shortcut, you can define a keybinding for the command.

    Package: Word Count

    Let's get started by writing a very simple package and looking at some of the tools needed to develop one effectively. We'll start by writing a package that tells you how many words are in the current buffer and display it in a small modal window.

    Package Generator

    The simplest way to start a package is to use the built-in package generator that ships with Atom. As you might expect by now, this generator is itself a separate package implemented in package-generatoropen in new window.

    You can run the generator by invoking the command palette and searching for "Generate Package". A dialog will appear asking you to name your new project. Name it your-name-word-count. Atom will then create that directory and fill it out with a skeleton project and link it into your ~/.atom/packages%USERPROFILE%\.atom\packages directory so it's loaded when you launch your editor next time.

    Note

    Note: You may encounter a situation where your package is not loaded. That is because a new package using the same name as an actual package hosted on atom.ioopen in new window (e.g. "wordcount" and "word-count") is not being loaded as you expected. If you follow our suggestion above of using the your-name-word-count package name, you should be safe 😀

    Basic generated Atom package

    You can see that Atom has created about a dozen files that make up the package. Let's take a look at each of them to get an idea of how a package is structured, then we can modify them to get our word count functionality.

    The basic package layout is as follows:

    my-package/
    +├─ grammars/
    +├─ keymaps/
    +├─ lib/
    +├─ menus/
    +├─ spec/
    +├─ snippets/
    +├─ styles/
    +├─ index.js
    +└─ package.json
    +

    Not every package will have (or need) all of these directories and the package generator doesn't create snippets or grammars. Let's see what some of these are so we can start messing with them.

    package.json

    Similar to Node modulesopen in new window, Atom packages contain a package.json file in their top-level directory. This file contains metadata about the package, such as the path to its "main" module, library dependencies, and manifests specifying the order in which its resources should be loaded.

    In addition to some of the regular Node package.json keysopen in new window available, Atom package.json files have their own additions.

    • main: the path to the JavaScript file that's the entry point to your package. If this is missing, Atom will default to looking for an index.coffee or index.js.
    • styles: an Array of Strings identifying the order of the style sheets your package needs to load. If not specified, style sheets in the styles directory are added alphabetically.
    • keymaps: an Array of Strings identifying the order of the key mappings your package needs to load. If not specified, mappings in the keymaps directory are added alphabetically.
    • menus: an Array of Strings identifying the order of the menu mappings your package needs to load. If not specified, mappings in the menus directory are added alphabetically.
    • snippets: an Array of Strings identifying the order of the snippets your package needs to load. If not specified, snippets in the snippets directory are added alphabetically.
    • activationCommands: an Object identifying commands that trigger your package's activation. The keys are CSS selectors, the values are Arrays of Strings identifying the command. The loading of your package is delayed until one of these events is triggered within the associated scope defined by the CSS selector. If not specified, the activate() method of your main export will be called when your package is loaded.
    • activationHooks: an Array of Strings identifying hooks that trigger your package's activation. The loading of your package is delayed until one of these hooks are triggered. Currently, there are three activation hooks:
      • core:loaded-shell-environment for when Atom has finished loading the shell environment variables
      • scope.name:root-scope-used for when a file is opened from the specified language (e.g. source.ruby:root-scope-used)
      • language-package-name:grammar-used for when a specific language package is used (e.g., my-special-language-javascript:grammar-used)
    • workspaceOpeners: An Array of Strings identifying URIs that trigger your package's activation. For example, say your package registers a custom opener for atom://my-custom-panel. By including that string in workspaceOpeners, your package will defer its activation until that URI is opened.

    The package.json in the package we've just generated looks like this currently:

    {
    +	"name": "your-name-word-count",
    +	"main": "./lib/your-name-word-count",
    +	"version": "0.0.0",
    +	"description": "A short description of your package",
    +	"activationCommands": {
    +		"atom-workspace": "your-name-word-count:toggle"
    +	},
    +	"repository": "https://github.com/atom/your-name-word-count",
    +	"license": "MIT",
    +	"engines": {
    +		"atom": ">=1.0.0 <2.0.0"
    +	},
    +	"dependencies": {}
    +}
    +

    If you wanted to use activationHooks, you might have:

    {
    +	"name": "your-name-word-count",
    +	"main": "./lib/your-name-word-count",
    +	"version": "0.0.0",
    +	"description": "A short description of your package",
    +	"activationHooks": [
    +		"language-javascript:grammar-used",
    +		"language-coffee-script:grammar-used"
    +	],
    +	"repository": "https://github.com/atom/your-name-word-count",
    +	"license": "MIT",
    +	"engines": {
    +		"atom": ">=1.0.0 <2.0.0"
    +	},
    +	"dependencies": {}
    +}
    +

    One of the first things you should do is ensure that this information is filled out. The name, description, repository URL the project will be at, and the license can all be filled out immediately. The other information we'll get into more detail on as we go.

    WARNING

    Warning: Do not forget to update the repository URL. The one generated for you is invalid by design and will prevent you from publishing your package until updated.

    Source Code

    If you want to extend Atom's behavior, your package should contain a single top-level module, which you export from whichever file is indicated by the main key in your package.json file. In the package we just generated, the main package file is lib/your-name-word-count.js. The remainder of your code should be placed in the lib directory, and required from your top-level file. If the main key is not in your package.json file, it will look for index.js or index.coffee as the main entry point.

    Your package's top-level module is a singleton object that manages the lifecycle of your extensions to Atom. Even if your package creates ten different views and appends them to different parts of the DOM, it's all managed from your top-level object.

    Your package's top-level module can implement the following basic methods:

    • activate(state): This optional method is called when your package is activated. It is passed the state data from the last time the window was serialized if your module implements the serialize() method. Use this to do initialization work when your package is started (like setting up DOM elements or binding events). If this method returns a promise the package will be considered loading until the promise resolves (or rejects).
    • initialize(state): (Available in Atom 1.14 and above) This optional method is similar to activate() but is called earlier. Whereas activation occurs after the workspace has been deserialized (and can therefore happen after your package's deserializers have been called), initialize() is guaranteed to be called before everything. Use activate() if you want to be sure that the workspace is ready; use initialize() if you need to do some setup prior to your deserializers or view providers being invoked.
    • serialize(): This optional method is called when the window is shutting down, allowing you to return JSON to represent the state of your component. When the window is later restored, the data you returned is passed to your module's activate method so you can restore your view to where the user left off.
    • deactivate(): This optional method is called when the window is shutting down and when the package is disabled. If your package is watching any files or holding external resources in any other way, release them here. You should also dispose of all subscriptions you're holding on to.
    Style Sheets

    Style sheets for your package should be placed in the styles directory. Any style sheets in this directory will be loaded and attached to the DOM when your package is activated. Style sheets can be written as CSS or Lessopen in new window, but Less is recommended.

    Ideally, you won't need much in the way of styling. Atom provides a standard set of components which define both the colors and UI elements for any package that fits into Atom seamlessly. You can view all of Atom's UI components by opening the styleguide: open the command palette Cmd+Shift+PCtrl+Shift+P and search for styleguide, or type Cmd+Ctrl+Shift+GCtrl+Shift+G.

    If you do need special styling, try to keep only structural styles in the package style sheets. If you must specify colors and sizing, these should be taken from the active theme's ui-variables.lessopen in new window.

    An optional styleSheets array in your package.json can list the style sheets by name to specify a loading order; otherwise, style sheets are loaded alphabetically.

    Keymaps

    You can provide key bindings for commonly used actions for your extension, especially if you're also adding a new command. In our new package, we have a keymap filled in for us already in the keymaps/your-name-word-count.json file:

    {
    +  "atom-workspace": {
    +    "ctrl-alt-o": "your-name-word-count:toggle"
    +  }
    +}
    +

    This means that if you press Alt+Ctrl+O, our package will run the your-name-word-count:toggle command. We'll look at that code next, but if you want to change the default key mapping, you can do that in this file.

    Keymaps are placed in the keymaps subdirectory. By default, all keymaps are loaded in alphabetical order. An optional keymaps array in your package.json can specify which keymaps to load and in what order.

    Keybindings are executed by determining which element the keypress occurred on. In the example above, the your-name-word-count:toggle command is executed when pressing Alt+Ctrl+O on the atom-workspace element. Because the atom-workspace element is the parent of the entire Atom UI, this means the key combination will work anywhere in the application.

    We'll cover more advanced keybinding stuff a bit later in Keymaps in Depth.

    Menus are placed in the menus subdirectory. This defines menu elements like what pops up when you right click a context-menu or would go in the application menu to trigger functionality in your plugin.

    By default, all menus are loaded in alphabetical order. An optional menus array in your package.json can specify which menus to load and in what order.

    Application Menu

    It's recommended that you create an application menu item under the Packages menu for common actions with your package that aren't tied to a specific element. If we look in the menus/your-name-word-count.json file that was generated for us, we'll see a section that looks like this:

    
    +"menu": [
    +  {
    +    "label": "Packages",
    +    "submenu": [
    +      {
    +        "label": "Word Count",
    +        "submenu": [
    +          {
    +            "label": "Toggle",
    +            "command": "your-name-word-count:toggle"
    +          }
    +        ]
    +      }
    +    ]
    +  }
    +]
    +
    +

    This section puts a "Toggle" menu item under a menu group named "Your Name Word Count" in the "Packages" menu.

    Application Menu Item

    When you select that menu item, it will run the your-name-word-count:toggle command, which we'll look at in a bit.

    The menu templates you specify are merged with all other templates provided by other packages in the order which they were loaded.

    Context Menu

    It's recommended to specify a context menu item for commands that are linked to specific parts of the interface. In our menus/your-name-word-count.json file, we can see an auto-generated section that looks like this:

    "context-menu": {
    +    "atom-text-editor": [
    +      {
    +        "label": "Toggle your-name-word-count",
    +        "command": "your-name-word-count:toggle"
    +      }
    +    ]
    +  }
    +

    This adds a "Toggle Word Count" menu option to the menu that pops up when you right-click in an Atom text editor pane.

    Context Menu Entry

    When you click that it will again run the your-name-word-count:toggle method in your code.

    Context menus are created by determining which element was selected and then adding all of the menu items whose selectors match that element (in the order which they were loaded). The process is then repeated for the elements until reaching the top of the DOM tree.

    You can also add separators and submenus to your context menus. To add a submenu, provide a submenu key instead of a command. To add a separator, add an item with a single type: 'separator' key/value pair. For instance, you could do something like this:

    {
    +  "context-menu": {
    +    "atom-workspace": [
    +      {
    +        "label": "Text",
    +        "submenu": [
    +          {
    +            "label": "Inspect Element",
    +            "command": "core:inspect"
    +          },
    +          {
    +            "type": "separator"
    +          },
    +          {
    +            "label": "Selector All",
    +            "command": "core:select-all"
    +          },
    +          {
    +            "type": "separator"
    +          },
    +          {
    +            "label": "Deleted Selected Text",
    +            "command": "core:delete"
    +          }
    +        ]
    +      }
    +    ]
    +  }
    +}
    +

    Developing Our Package

    Currently with the generated package we have, if we run that your-name-word-count:toggle command through the menu or the command palette, we'll get a dialog that says "The YourNameWordCount package is Alive! It's ALIVE!".

    Wordcount Package is Alive Dialog

    Understanding the Generated Code

    Let's take a look at the code in our lib directory and see what is happening.

    There are two files in our lib directory. One is the main file (lib/your-name-word-count.js), which is pointed to in the package.json file as the main file to execute for this package. This file handles the logic of the whole plugin.

    The second file is a View class, lib/your-name-word-count-view.js, which handles the UI elements of the package. Let's look at this file first, since it's pretty simple.

    export default class YourNameWordCountView {
    +	constructor(serializedState) {
    +		// Create root element
    +		this.element = document.createElement("div");
    +		this.element.classList.add("your-name-word-count");
    +
    +		// Create message element
    +		const message = document.createElement("div");
    +		message.textContent = "The YourNameWordCount package is Alive! It's ALIVE!";
    +		message.classList.add("message");
    +		this.element.appendChild(message);
    +	}
    +
    +	// Returns an object that can be retrieved when package is activated
    +	serialize() {}
    +
    +	// Tear down any state and detach
    +	destroy() {
    +		this.element.remove();
    +	}
    +
    +	getElement() {
    +		return this.element;
    +	}
    +}
    +

    Basically the only thing happening here is that when the View class is created, it creates a simple div element and adds the your-name-word-count class to it (so we can find or style it later) and then adds the "Your Name Word Count package is Alive!" text to it. There is also a getElement method which returns that div. The serialize and destroy methods don't do anything and we won't have to worry about that until another example.

    Notice that we're simply using the basic browser DOM methods: createElement() and appendChild().

    The second file we have is the main entry point to the package. Again, because it's referenced in the package.json file. Let's take a look at that file.

    import YourNameWordCountView from "./your-name-word-count-view";
    +import { CompositeDisposable } from "atom";
    +
    +export default {
    +	yourNameWordCountView: null,
    +	modalPanel: null,
    +	subscriptions: null,
    +
    +	activate(state) {
    +		this.yourNameWordCountView = new YourNameWordCountView(
    +			state.yourNameWordCountViewState
    +		);
    +		this.modalPanel = atom.workspace.addModalPanel({
    +			item: this.yourNameWordCountView.getElement(),
    +			visible: false,
    +		});
    +
    +		// Events subscribed to in atom's system can be easily cleaned up with a CompositeDisposable
    +		this.subscriptions = new CompositeDisposable();
    +
    +		// Register command that toggles this view
    +		this.subscriptions.add(
    +			atom.commands.add("atom-workspace", {
    +				"your-name-word-count:toggle": () => this.toggle(),
    +			})
    +		);
    +	},
    +
    +	deactivate() {
    +		this.modalPanel.destroy();
    +		this.subscriptions.dispose();
    +		this.yourNameWordCountView.destroy();
    +	},
    +
    +	serialize() {
    +		return {
    +			yourNameWordCountViewState: this.yourNameWordCountView.serialize(),
    +		};
    +	},
    +
    +	toggle() {
    +		console.log("YourNameWordCount was toggled!");
    +		return this.modalPanel.isVisible()
    +			? this.modalPanel.hide()
    +			: this.modalPanel.show();
    +	},
    +};
    +

    There is a bit more going on here. First of all we can see that we are defining four methods. The only required one is activate. The deactivate and serialize methods are expected by Atom but optional. The toggle method is one Atom is not looking for, so we'll have to invoke it somewhere for it to be called, which you may recall we do both in the activationCommands section of the package.json file and in the action we have in the menu file.

    The deactivate method simply destroys the various class instances we've created and the serialize method simply passes on the serialization to the View class. Nothing too exciting here.

    The activate command does a number of things. For one, it is not called automatically when Atom starts up, it is first called when one of the activationCommands as defined in the package.json file are called. In this case, activate is only called the first time the toggle command is called. If nobody ever invokes the menu item or hotkey, this code is never called.

    This method does two things. The first is that it creates an instance of the View class we have and adds the element that it creates to a hidden modal panel in the Atom workspace.

    this.yourNameWordCountView = new YourNameWordCountView(
    +	state.yourNameWordCountViewState
    +);
    +this.modalPanel = atom.workspace.addModalPanel({
    +	item: this.yourNameWordCountView.getElement(),
    +	visible: false,
    +});
    +

    We'll ignore the state stuff for now, since it's not important for this simple plugin. The rest should be fairly straightforward.

    The next thing this method does is create an instance of the CompositeDisposable class so it can register all the commands that can be called from the plugin so other plugins could subscribe to these events.

    // Events subscribed to in atom's system can be easily cleaned up with a CompositeDisposable
    +this.subscriptions = new CompositeDisposable();
    +
    +// Register command that toggles this view
    +this.subscriptions.add(
    +	atom.commands.add("atom-workspace", {
    +		"your-name-word-count:toggle": () => this.toggle(),
    +	})
    +);
    +

    Next we have the toggle method. This method simply toggles the visibility of the modal panel that we created in the activate method.

    toggle() {
    +  console.log('YourNameWordCount was toggled!');
    +  return (
    +    this.modalPanel.isVisible() ?
    +    this.modalPanel.hide() :
    +    this.modalPanel.show()
    +  );
    +}
    +

    This should be fairly simple to understand. We're looking to see if the modal element is visible and hiding or showing it depending on its current state.

    The Flow

    So, let's review the actual flow in this package.

    1. Atom starts up
    2. Atom starts loading packages
    3. Atom reads your package.json
    4. Atom loads keymaps, menus, styles and the main module
    5. Atom finishes loading packages
    6. At some point, the user executes your package command your-name-word-count:toggle
    7. Atom executes the activate method in your main module which sets up the UI by creating the hidden modal view
    8. Atom executes the package command your-name-word-count:toggle which reveals the hidden modal view
    9. At some point, the user executes the your-name-word-count:toggle command again
    10. Atom executes the command which hides the modal view
    11. Eventually, Atom is shut down which can trigger any serializations that your package has defined

    Tip

    Tip: Keep in mind that the flow will be slightly different if you choose not to use activationCommands in your package.

    Counting the Words

    So now that we understand what is happening, let's modify the code so that our little modal box shows us the current word count instead of static text.

    We'll do this in a very simple way. When the dialog is toggled, we'll count the words right before displaying the modal. So let's do this in the toggle command. If we add some code to count the words and ask the view to update itself, we'll have something like this:

    toggle() {
    +  if (this.modalPanel.isVisible()) {
    +    this.modalPanel.hide();
    +  } else {
    +    const editor = atom.workspace.getActiveTextEditor();
    +    const words = editor.getText().split(/\s+/).length;
    +    this.yourNameWordCountView.setCount(words);
    +    this.modalPanel.show();
    +  }
    +}
    +

    Let's look at the 3 lines we've added. First we get an instance of the current editor object (where our text to count is) by calling atom.workspace.getActiveTextEditor()open in new window.

    Next we get the number of words by calling getText()open in new window on our new editor object, then splitting that text on whitespace with a regular expression and then getting the length of that array.

    Finally, we tell our view to update the word count it displays by calling the setCount() method on our view and then showing the modal again. Since that method doesn't yet exist, let's create it now.

    We can add this code to the end of our your-name-word-count-view.js file:

    setCount(count) {
    +  const displayText = `There are ${count} words.`;
    +  this.element.children[0].textContent = displayText;
    +}
    +

    Pretty simple! We take the count number that was passed in and place it into a string that we then stick into the element that our view is controlling.

    Note

    Note: To see your changes, you'll need to reload the code. You can do this by reloading the window (The window:reload command in the Command Palette). A common practice is to have two Atom windows, one for developing your package, and one for testing and reloading.

    Word Count Working

    Basic Debugging

    You'll notice a few console.log statements in the code. One of the cool things about Atom being built on Chromium is that you can use some of the same debugging tools available to you that you have when doing web development.

    To open up the Developer Console, press Alt+Cmd+ICtrl+Shift+I, or choose the menu option View > Developer > Toggle Developer Tools.

    Developer Tools Debugging

    From here you can inspect objects, run code and view console output just as though you were debugging a web site.

    Testing

    Your package should have tests, and if they're placed in the spec directory, they can be run by Atom.

    Under the hood, Jasmine v1.3open in new window executes your tests, so you can assume that any DSL available there is also available to your package.

    Running Tests

    Once you've got your test suite written, you can run it by pressing Alt+Cmd+Ctrl+PAlt+Ctrl+P or via the View > Developer > Run Package Specs menu. Our generated package comes with an example test suite, so you can run this right now to see what happens.

    Spec Suite Results

    You can also use the atom --test spec command to run them from the command line. It prints the test output and results to the console and returns the proper status code depending on whether the tests passed or failed.

    Summary

    We've now generated, customized and tested our first plugin for Atom. Congratulations! Now let's go ahead and publish it so it's available to the world.

    Package: Modifying Text

    Now that we have our first package written, let's go through examples of other types of packages we can make. This section will guide you though creating a simple command that replaces the selected text with ascii artopen in new window. When you run our new command with the word "cool" selected, it will be replaced with:

                                         o888
    +    ooooooo     ooooooo     ooooooo   888
    +  888     888 888     888 888     888 888
    +  888         888     888 888     888 888
    +    88ooo888    88ooo88     88ooo88  o888o
    +
    +

    This should demonstrate how to do basic text manipulation in the current text buffer and how to deal with selections.

    The final package can be viewed at https://github.com/atom/ascii-art.

    Basic Text Insertion

    To begin, press Cmd+Shift+PCtrl+Shift+P to bring up the Command Paletteopen in new window. Type "generate package" and select the "Package Generator: Generate Package" command, just as we did in the section on package generation. Enter ascii-art as the name of the package.

    Now let's edit the package files to make our ASCII Art package do something interesting. Since this package doesn't need any UI, we can remove all view-related code so go ahead and delete lib/ascii-art-view.js, spec/ascii-art-view-spec.js, and styles/.

    Next, open up lib/ascii-art.js and remove all view code, so it looks like this:

    const { CompositeDisposable } = require("atom");
    +
    +module.exports = {
    +	subscriptions: null,
    +
    +	activate() {
    +		this.subscriptions = new CompositeDisposable();
    +		this.subscriptions.add(
    +			atom.commands.add("atom-workspace", {
    +				"ascii-art:convert": () => this.convert(),
    +			})
    +		);
    +	},
    +
    +	deactivate() {
    +		this.subscriptions.dispose();
    +	},
    +
    +	convert() {
    +		console.log("Convert text!");
    +	},
    +};
    +
    Create a Command

    Now let's add a command. You should namespace your commands with the package name followed by a : and then the name of the command. As you can see in the code, we called our command ascii-art:convert and we will define it to call the convert() method when it's executed.

    So far, that will simply log to the console. Let's start by making it insert something into the text buffer.

    convert() {
    +  const editor = atom.workspace.getActiveTextEditor()
    +  if (editor) {
    +    editor.insertText('Hello, World!')
    +  }
    +}
    +

    As in Counting Words, we're using atom.workspace.getActiveTextEditor() to get the object that represents the active text editor. If this convert() method is called when not focused on a text editor, nothing will happen.

    Next we insert a string into the current text editor with the insertText()open in new window method. This will insert the text wherever the cursor currently is in the current editor. If there are selections, it will replace all selections with the "Hello, World!" text.

    Reload the Package

    Before we can trigger ascii-art:convert, we need to load the latest code for our package by reloading the window. Run the command "Window: Reload" from the Command Palette or by pressing Alt+Cmd+Ctrl+LCtrl+Shift+F5.

    Trigger the Command

    Now open the Command Palette and search for the "Ascii Art: Convert" command. But it's not there! To fix this, open package.json and find the property called activationCommands. Activation commands make Atom launch faster by allowing Atom to delay a package's activation until it's needed. So remove the existing command and use ascii-art:convert in activationCommands:

    "activationCommands": {
    +  "atom-workspace": "ascii-art:convert"
    +}
    +

    First, reload the window by running the command "Window: Reload" from the command palette. Now when you run the "Ascii Art: Convert" command it will insert "Hello, World!" into the active editor, if any.

    Add a Key Binding

    Now let's add a key binding to trigger the ascii-art:convert command. Open keymaps/ascii-art.json and add a key binding linking Alt+Ctrl+A to the ascii-art:convert command. You can delete the pre-existing key binding since you won't need it anymore.

    When finished, the file should look like this:

    {
    +  "atom-text-editor": {
    +    "ctrl-alt-a": "ascii-art:convert"
    +  }
    +}
    +
    +

    Now reload the window and verify that the key binding works.

    WARNING

    Warning: The Atom keymap system is case-sensitive. This means that there is a distinction between a and A when creating keybindings. a means that you want to trigger the keybinding when you press A. But A means that you want to trigger the keybinding when you press Shift+A. You can also write shift-a when you want to trigger the keybinding when you press Shift+A.

    We strongly recommend always using lowercase and explicitly spelling out when you want to include Shift in your keybindings.

    Add the ASCII Art

    Now we need to convert the selected text to ASCII art. To do this we will use the figletopen in new window Node module from npmopen in new window. Open package.json and add the latest version of figlet to the dependencies:

    "dependencies": {
    +  "figlet": "1.0.8"
    +}
    +

    After saving the file, run the command "Update Package Dependencies: Update" from the Command Palette. This will install the package's node module dependencies, only figlet in this case. You will need to run "Update Package Dependencies: Update" whenever you update the dependencies field in your package.json file.

    If for some reason this doesn't work, you'll see a message saying "Failed to update package dependencies" and you will find a new npm-debug.log file in your directory. That file should give you some idea as to what went wrong.

    Now require the figlet node module in lib/ascii-art.js and instead of inserting "Hello, World!", convert the selected text to ASCII art.

    convert () {
    +  const editor = atom.workspace.getActiveTextEditor()
    +  if (editor) {
    +    const selection = editor.getSelectedText()
    +
    +    const figlet = require('figlet')
    +    const font = 'o8'
    +    figlet(selection, {font}, function (error, art) {
    +      if (error) {
    +        console.error(error)
    +      } else {
    +        editor.insertText(`\n${art}\n`)
    +      }
    +    })
    +  }
    +}
    +

    Now reload the editor, select some text in an editor window and press Alt+Ctrl+A. It should be replaced with a ridiculous ASCII art version instead.

    There are a couple of new things in this example we should look at quickly. The first is the editor.getSelectedText()open in new window which, as you might guess, returns the text that is currently selected.

    We then call the Figlet code to convert that into something else and replace the current selection with it with the editor.insertText()open in new window call.

    Summary

    In this section, we've made a UI-less package that takes selected text and replaces it with a processed version. This could be helpful in creating linters or checkers for your code.

    Package: Active Editor Info

    We saw in our Word Count package how we could show information in a modal panel. However, panels aren't the only way to extend Atom's UI—you can also add items to the workspace. These items can be dragged to new locations (for example, one of the docks on the edges of the window), and Atom will restore them the next time you open the project. This system is used by Atom's tree view, as well as by third party packages like Nuclideopen in new window for its console, debugger, outline view, and diagnostics (linter results).

    For this package, we'll define a workspace item that tells us some information about our active text editor. The final package can be viewed at https://github.com/atom/active-editor-info.

    Create the Package

    To begin, press Cmd+Shift+PCtrl+Shift+P to bring up the Command Paletteopen in new window. Type "generate package" and select the "Package Generator: Generate Package" command, just as we did in the section on package generation. Enter active-editor-info as the name of the package.

    Add an Opener

    Now let's edit the package files to show our view in a workspace item instead of a modal panel. The way we do this is by registering an opener with Atom. Openers are just functions that accept a URI and return a view (if it's a URI that the opener knows about). When you call atom.workspace.open(), Atom will go through all of its openers until it finds one that can handle the URI you passed.

    Let's open lib/active-editor-info.js and edit our activate() method to register an opener:

    "use babel";
    +
    +import ActiveEditorInfoView from "./active-editor-info-view";
    +import { CompositeDisposable, Disposable } from "atom";
    +
    +export default {
    +	subscriptions: null,
    +
    +	activate(state) {
    +		this.subscriptions = new CompositeDisposable(
    +			// Add an opener for our view.
    +			atom.workspace.addOpener((uri) => {
    +				if (uri === "atom://active-editor-info") {
    +					return new ActiveEditorInfoView();
    +				}
    +			}),
    +
    +			// Register command that toggles this view
    +			atom.commands.add("atom-workspace", {
    +				"active-editor-info:toggle": () => this.toggle(),
    +			}),
    +
    +			// Destroy any ActiveEditorInfoViews when the package is deactivated.
    +			new Disposable(() => {
    +				atom.workspace.getPaneItems().forEach((item) => {
    +					if (item instanceof ActiveEditorInfoView) {
    +						item.destroy();
    +					}
    +				});
    +			})
    +		);
    +	},
    +
    +	deactivate() {
    +		this.subscriptions.dispose();
    +	},
    +
    +	toggle() {
    +		console.log("Toggle it!");
    +	},
    +};
    +

    You'll notice we also removed the activeEditorInfoView property and the serialize() method. That's because, with workspace items, it's possible to have more than one instance of a given view. Since each instance can have its own state, each should do its own serialization instead of relying on a package-level serialize() method. We'll come back to that later.

    You probably also noticed that our toggle() implementation just logs the text "Toggle it!" to the console. Let's make it actually toggle our view:

      toggle() {
    +    atom.workspace.toggle('atom://active-editor-info');
    +  }
    +

    Updating the View

    Atom uses the same view abstractions everywhere, so we can almost use the generated ActiveEditorInfoView class as-is. We just need to add two small methods:

      getTitle() {
    +    // Used by Atom for tab text
    +    return 'Active Editor Info';
    +  }
    +
    +  getURI() {
    +    // Used by Atom to identify the view when toggling.
    +    return 'atom://active-editor-info';
    +  }
    +

    Now reload the window and run the "Active Editor Info: Toggle" command from the command palette! Our view will appear in a new tab in the center of the workspace. If you want, you can drag it into one of the docks. Toggling it again will then hide that dock. If you close the tab and run the toggle command again, it will appear in the last place you had it.

    Note

    We've repeated the same URI three times now. That's okay, but it's probably a good idea to define the URL in one place and then import it from that module wherever you need it.

    Constraining Our Item's Locations

    The purpose of our view is to show information about the active text editor, so it doesn't really make sense to show our item in the center of the workspace (where the text editor will be). Let's add some methods to our view class to influence where its opened:

      getDefaultLocation() {
    +    // This location will be used if the user hasn't overridden it by dragging the item elsewhere.
    +    // Valid values are "left", "right", "bottom", and "center" (the default).
    +    return 'right';
    +  }
    +
    +  getAllowedLocations() {
    +    // The locations into which the item can be moved.
    +    return ['left', 'right', 'bottom'];
    +  }
    +

    Now our item will appear in the right dock initially and users will only be able to drag it to one of the other docks.

    Show Active Editor Info

    Now that we have our view all wired up, let's update it to show some information about the active text editor. Add this to the constructor:

    this.subscriptions = atom.workspace
    +	.getCenter()
    +	.observeActivePaneItem((item) => {
    +		if (!atom.workspace.isTextEditor(item)) {
    +			message.innerText = "Open a file to see important information about it.";
    +			return;
    +		}
    +		message.innerHTML = `
    +    <h2>${item.getFileName() || "untitled"}</h2>
    +    <ul>
    +      <li><b>Soft Wrap:</b> ${item.softWrapped}</li>
    +      <li><b>Tab Length:</b> ${item.getTabLength()}</li>
    +      <li><b>Encoding:</b> ${item.getEncoding()}</li>
    +      <li><b>Line Count:</b> ${item.getLineCount()}</li>
    +    </ul>
    +  `;
    +	});
    +

    Now whenever you open a text editor in the center, the view will update with some information about it.

    WARNING

    We use a template string here because it's simple and we have a lot of control over what's going into it, but this could easily result in the insertion of unwanted HTML if you're not careful. Sanitize your input and use the DOM API or a templating system when doing this for real.

    Also, don't forget to clean up the subscription in the destroy() method:

    destroy() {
    +  this.element.remove();
    +  this.subscriptions.dispose();
    +}
    +

    Serialization

    If you were to reload Atom now, you'd see that our item had disappeared. That's because we haven't told Atom how to serialize it yet. Let's do that now.

    The first step is to implement a serialize() method on our ActiveEditorInfoView class. Atom will call the serialize() method on every item in the workspace periodically to save its state.

      serialize() {
    +    return {
    +      // This is used to look up the deserializer function. It can be any string, but it needs to be
    +      // unique across all packages!
    +      deserializer: 'active-editor-info/ActiveEditorInfoView'
    +    };
    +  }
    +

    Note

    All of our view's state is derived from the active text editor so we only need the deserializer field. If we had other state that we wanted to preserve across reloads, we would just add things to the object we're returning. Just make sure that they're JSON serializable!

    Next we need to register a deserializer function that Atom can use to recreate the real object when it starts up. The best way to do that is to add a "deserializers" object to our package.json file:

    {
    +  "name": "active-editor-info",
    +  ...
    +  "deserializers": {
    +    "active-editor-info/ActiveEditorInfoView": "deserializeActiveEditorInfoView"
    +  }
    +}
    +

    Notice that the key ("active-editor-info/ActiveEditorInfoView") matches the string we used in our serialize() method above. The value ("deserializeActiveEditorInfoView") refers to a function in our main module, which we still need to add. Go back to active-editor-info.js and do that now:

      deserializeActiveEditorInfoView(serialized) {
    +    return new ActiveEditorInfoView();
    +  }
    +

    The value returned from our serialize() method will be passed to this function. Since our serialized object didn't include any state, we can just return a new ActiveEditorInfoView instance.

    Reload Atom and toggle the view with the "Active Editor Info: Toggle" command. Then reload Atom again. Your view should be just where you left it!

    Summary

    In this section, we've made a toggleable workspace item whose placement can be controlled by the user. This could be helpful when creating all sorts of visual tools for working with code!

    Creating a Theme

    Atom's interface is rendered using HTML, and it's styled via Lessopen in new window which is a superset of CSS. Don't worry if you haven't heard of Less before; it's just like CSS, but with a few handy extensions.

    Atom supports two types of themes: UI and Syntax. UI themes style elements such as the tree view, the tabs, drop-down lists, and the status bar. Syntax themes style the code, gutter and other elements inside the editor view.

    Theme boundary

    Themes can be installed and changed from the Settings View which you can open by selecting the Atom > PreferencesFile > PreferencesEdit > Preferences menu, and clicking the "Install" or "Themes" tab on the left hand navigation.

    Getting Started

    Themes are pretty straightforward but it's still helpful to be familiar with a few things before starting:

    • Less is a superset of CSS, but it has some really handy features like variables. If you aren't familiar with its syntax, take a few minutes to familiarize yourselfopen in new window.
    • You may also want to review the concept of a package.json (as covered in Atom package.json). This file is used to help distribute your theme to Atom users.
    • Your theme's package.json must contain a theme key with a value of ui or syntax for Atom to recognize and load it as a theme.
    • You can find existing themes to install or fork in the atom.io themes registryopen in new window.

    Creating a Syntax Theme

    Let's create your first theme.

    To get started, press Cmd+Shift+PCtrl+Shift+P and start typing "Generate Syntax Theme" to generate a new theme package. Select "Generate Syntax Theme," and you'll be asked for the path where your theme will be created. Let's call ours motif-syntax.

    Tip

    Tip: Syntax themes should end with -syntax and UI themes should end with -ui.

    Atom will display a new window, showing the motif-syntax theme, with a default set of folders and files created for us. If you open the Settings View with Cmd+,Ctrl+, and click the "Themes" tab on the left, you'll see the "Motif" theme listed in the "Syntax Theme" drop-down. Select it from the menu to activate it, now when you open an editor you should see your new motif-syntax theme in action.

    Open up styles/colors.less to change the various color variables which have already been defined. For example, turn @red into #f4c2c1.

    Then open styles/base.less and modify the various selectors that have already been defined. These selectors style different parts of code in the editor such as comments, strings and the line numbers in the gutter.

    As an example, let's make the .gutter background-color into @red.

    Reload Atom by pressing Alt+Cmd+Ctrl+LAlt+Ctrl+R to see the changes you made reflected in your Atom window. Pretty neat!

    Tip

    Tip: You can avoid reloading to see changes you make by opening an Atom window in Dev Mode. To open a Dev Mode Atom window run atom --dev . in the terminal, or use the View > Developer > Open in Dev Mode menu. When you edit your theme, changes will instantly be reflected!

    Note

    Note: It's advised to not specify a font-family in your syntax theme because it will override the Font Family field in Atom's settings. If you still like to recommend a font that goes well with your theme, we suggest you do so in your README.

    Creating a UI Theme

    To create a UI theme, do the following:

    1. Fork the ui-theme-templateopen in new window
    2. Clone the forked repository to the local filesystem
    3. Open a terminal in the forked theme's directory
    4. Open your new theme in a Dev Mode Atom window run atom --dev . in the terminal or use the View > Developer > Open in Dev Mode menu
    5. Change the name of the theme in the theme's package.json file
    6. Name your theme end with a -ui, for example super-white-ui
    7. Run apm link --dev to symlink your repository to ~/.atom/dev/packages
    8. Reload Atom using Alt+Cmd+Ctrl+LAlt+Ctrl+R
    9. Enable the theme via the "UI Theme" drop-down in the "Themes" tab of the Settings View
    10. Make changes! Since you opened the theme in a Dev Mode window, changes will be instantly reflected in the editor without having to reload.

    Tip

    Tip: Because we used apm link --dev in the above instructions, if you break anything you can always close Atom and launch Atom normally to force Atom to the default theme. This allows you to continue working on your theme even if something goes catastrophically wrong.

    Theme Variables

    UI themes must provide a ui-variables.less and Syntax themes a syntax-variables.less file. It contains predefined variables that packages use to make sure the look and feel matches.

    Here the variables with the default values:

    These default values will be used as a fallback in case a theme doesn't define its own variables.

    Use in Packages

    In any of your package's .less files, you can access the theme variables by importing the ui-variables or syntax-variables file from Atom.

    Your package should generally only specify structural styling, and these should come from the style guideopen in new window. Your package shouldn't specify colors, padding sizes, or anything in absolute pixels. You should instead use the theme variables. If you follow this guideline, your package will look good out of the box with any theme!

    Here's an example .less file that a package can define using theme variables:

    @import "ui-variables";
    +
    +.my-selector {
    +	background-color: @base-background-color;
    +	padding: @component-padding;
    +}
    +
    @import "syntax-variables";
    +
    +.my-selector {
    +	background-color: @syntax-background-color;
    +}
    +

    Development workflow

    There are a few tools to help make theme development faster and easier.

    Live Reload

    Reloading by pressing Alt+Cmd+Ctrl+LAlt+Ctrl+R after you make changes to your theme is less than ideal. Atom supports live updatingopen in new window of styles on Atom windows in Dev Mode.

    To launch a Dev Mode window:

    • Open your theme directory in a dev window by selecting the View > Developer > Open in Dev Mode menu item
    • Or launch Atom from the terminal with atom --dev

    If you'd like to reload all the styles at any time, you can use the shortcut Alt+Cmd+Ctrl+LAlt+Ctrl+R.

    Developer Tools

    Atom is based on the Chrome browser, and supports Chrome's Developer Tools. You can open them by selecting the View > Developer > Toggle Developer Tools menu, or by using the Alt+Cmd+ICtrl+Shift+I shortcut.

    The dev tools allow you to inspect elements and take a look at their CSS properties.

    Developer Tools

    Check out Google's extensive tutorialopen in new window for a short introduction.

    Atom Styleguide

    If you are creating an UI theme, you'll want a way to see how your theme changes affect all the components in the system. The Styleguideopen in new window is a page that renders every component Atom supports.

    To open the Styleguide, open the command palette with Cmd+Shift+PCtrl+Shift+P and search for "styleguide", or use the shortcut Cmd+Ctrl+Shift+GCtrl+Shift+G.

    Style Guide

    Side by side

    Sometimes when creating a theme (or package) things can go wrong and the editor becomes un-usable. E.g. if the text and background have the same color or something gets pushed out of sight. To avoid having to open Atom in "normal" mode to fix the issue, it's advised to open two Atom windows. One for making changes and one in Dev Mode to see the changes getting applied.

    Side by side screenshot

    Make changes on the left, see the changes getting applied in "Dev Mode" on the right.

    Now if you mess up something, only the window in "Dev Mode" will be affected and you can easily correct the mistake in your "normal" window.

    Publish your theme

    Once you're happy with your theme and would like to share it with other Atom users, it's time to publish it. 🎉

    Follow the steps on the Publishing page. The example used is for the Word Count package, but publishing a theme works exactly the same.

    Creating a Grammar

    Atom's syntax highlighting and code folding system is powered by Tree-sitteropen in new window. Tree-sitter parsers create and maintain full syntax treesopen in new window representing your code.

    This syntax tree gives Atom a comprehensive understanding of the structure of your code, which has several benefits:

    1. Syntax highlighting will not break because of formatting changes.
    2. Code folding will work regardless of how your code is indented.
    3. Editor features can operate on the syntax tree. For instance, the Select Larger Syntax Node and Select Smaller Syntax Node allow you to select conceptually larger and smaller chunks of your code.
    4. Community packages can use the syntax tree to manipulate code intelligently.

    Tree-sitter grammars are relatively new. Many languages in Atom are still supported by TextMate grammars, though we intend to phase these out over time.

    If you're adding support for a new language, you're in the right place!

    Getting Started

    There are two components required to use Tree-sitter in Atom: a parser and a grammar file.

    The Parser

    Tree-sitter generates parsers based on context-free grammarsopen in new window that are typically written in JavaScript. The generated parsers are C libraries that can be used in other applications as well as Atom.

    They can also be developed and tested at the command line, separately from Atom. Tree-sitter has its own documentation pageopen in new window on how to create these parsers. The Tree-sitter GitHub organizationopen in new window also contains a lot of example parsers that you can learn from, each in its own repository.

    Once you have created a parser, you need to publish it to the NPM registryopen in new window to use it in Atom. To do this, make sure you have a name and version in your parser's package.json:

    {
    +  "name": "tree-sitter-mylanguage",
    +  "version": "0.0.1",
    +  // ...
    +}
    +

    then run the command npm publish.

    The Package

    Once you have a Tree-sitter parser that is available on npm, you can use it in your Atom package. Packages with grammars are, by convention, always named starting with language. You'll need a folder with a package.json, a grammars subdirectory, and a single json or cson file in the grammars directory, which can be named anything.

    language-mylanguage
    +├── LICENSE
    +├── README.md
    +├── grammars
    +│   └── mylanguage.cson
    +└── package.json
    +

    The Grammar File

    The mylanguage.cson file specifies how Atom should use the parser you created.

    Basic Fields

    It starts with some required fields:

    name: 'My Language'
    +scopeName: 'mylanguage'
    +type: 'tree-sitter'
    +parser: 'tree-sitter-mylanguage'
    +
    • scopeName - A unique, stable identifier for the language. Atom users will use this in configuration files if they want to specify custom configuration based on the language.
    • name - A human readable name for the language.
    • parser - The name of the parser node module that will be used for parsing. This string will be passed directly to require()open in new window in order to load the parser.
    • type - This should have the value tree-sitter to indicate to Atom that this is a Tree-sitter grammar and not a TextMate grammar.

    Language Recognition

    Next, the file should contain some fields that indicate to Atom when this language should be used. These fields are all optional.

    • fileTypes - An array of filename suffixes. The grammar will be used for files whose names end with one of these suffixes. Note that the suffix may be an entire filename.
    • firstLineRegex - A regex pattern that will be tested against the first line of the file. The grammar will be used if this regex matches.
    • contentRegex - A regex pattern that will be tested against the contents of the file in order to break ties in cases where multiple grammars matched the file using the above two criteria. If the contentRegex matches, this grammar will be preferred over another grammar with no contentRegex. If the contentRegex does not match, a grammar with no contentRegex will be preferred over this one.

    Syntax Highlighting

    The HTML classes that Atom uses for syntax highlighting do not correspond directly to nodes in the syntax tree. Instead, Tree-sitter grammar files specify scope mappings that specify which classes should be applied to which syntax nodes. The scopes object controls these scope mappings. Its keys are CSS selectors that select nodes in the syntax tree. Its values can be of several different types.

    Here is a simple example:

    scopes:
    +  'call_expression > identifier': 'entity.name.function'
    +

    This entry means that, in the syntax tree, any identifier node whose parent is a call_expression should be highlighted using three classes: syntax--entity, syntax--name, and syntax--function.

    Note that in this selector, we're using the immediate child combinatoropen in new window (>). Arbitrary descendant selectors without this combinator (for example 'call_expression identifier', which would match any identifier occurring anywhere within a call_expression) are currently not supported.

    Advanced Selectors

    The keys of the scopes object can also contain multiple CSS selectors, separated by commas, similar to CSS files. The triple-quote syntax in CSON makes it convenient to write keys like this on multiple lines:

    scopes:
    +  '''
    +  function_declaration > identifier,
    +  call_expression > identifier,
    +  call_expression > field_expression > field_identifier
    +  ''': 'entity.name.function'
    +

    You can use the :nth-child pseudo-classopen in new window to select nodes based on their order within their parent. For example, this example selects identifier nodes which are the fourth (zero-indexed) child of a singleton_method node.

    scopes:
    +  'singleton_method > identifier:nth-child(3)': 'entity.name.function'
    +

    Finally, you can use double-quoted strings in the selectors to select anonymous tokens in the syntax tree, like ( and :. See the Tree-sitter documentationopen in new window for more information about named vs anonymous tokens.

    scopes:
    +  '''
    +    "*",
    +    "/",
    +    "+",
    +    "-"
    +  ''': 'keyword.operator'
    +
    Text-based Mappings

    You can also apply different classes to a syntax node based on its text. Here are some examples:

    scopes:
    +
    +  # Apply the classes `syntax--builtin` and `syntax--variable` to all
    +  # `identifier` nodes whose text is `require`.
    +  'identifier': {exact: 'require', scopes: 'builtin.variable'},
    +
    +  # Apply the classes `syntax--type` and `syntax--integer` to all
    +  # `primitive_type` nodes whose text starts with `int` or `uint`.
    +  'primitive_type': {match: /^u?int/, scopes: 'type.integer'},
    +
    +  # Apply the classes `syntax--builtin`, `syntax--class`, and
    +  # `syntax--name` to `constant` nodes with the text `Array`,
    +  # `Hash` and `String`. For all other `constant` nodes, just
    +  # apply the classes `syntax--class` and `syntax--name`.
    +  'constant': [
    +    {match: '^(Array|Hash|String)$', scopes: 'builtin.class.name'},
    +    'class.name'
    +  ]
    +

    In total there are four types of values that can be associated with selectors in scopes:

    • Strings - Each class name in the dot-separated string will be prefixed with syntax-- and applied to the selected node.
    • Objects with the keys exact and scopes - If the node's text equals the exact string, the scopes string will be used as described above.
    • Objects with the keys match and scopes - If the node's text matches the match regex pattern, the scopes string will be used as described above.
    • Arrays - The elements of the array will be processed from beginning to end. The first element that matches the selected node will be used as describe above.
    Specificity

    If multiple selectors in the scopes object match a node, the node's classes will be decided based on the most specificopen in new window selector. Note that the exact and match rules do not affect specificity, so you may need to supply the same exact or match rules for multiple selectors to ensure that they take precedence over other selectors. You can use the same selector multiple times in a scope mapping, within different comma-separated keys:

    scopes:
    +  'call_expression > identifier': 'entity.name.function'
    +
    +  # If we did not include the second selector here, then this rule
    +  # would not apply to identifiers inside of call_expressions,
    +  # because the selector `call_expression > identifier` is more
    +  # specific than the selector `identifier`.
    +  'identifier, call_expression > identifier': [
    +    {exact: 'require', scopes: 'builtin.variable'},
    +    {match: '^[A-Z]', scopes: 'constructor'},
    +  ]
    +

    Language Injection

    Sometimes, a source file can contain code written in several different languages. Tree-sitter grammars support this situation using a two-part process called language injection. First, an 'outer' language must define an injection point - a set of syntax nodes whose text can be parsed using a different language, along with some logic for guessing the name of the other language that should be used. Second, an 'inner' language must define an injectionRegex - a regex pattern that will be tested against the language name provided by the injection point.

    For example, in JavaScript, tagged template literalsopen in new window sometimes contain code written in a different language, and the name of the language is often used in the 'tag' function, as shown in this example:

    // HTML in a template literal
    +const htmlContent = html`<div>Hello ${name}</div>`;
    +

    The tree-sitter-javascript parser parses this tagged template literal as a call_expression with two children: an identifier and a template_literal:

    (call_expression
    +  (identifier)
    +  (template_literal
    +    (interpolation
    +      (identifier))))
    +

    Here is an injection point that would allow syntax highlighting inside of template literals:

    atom.grammars.addInjectionPoint("source.js", {
    +	type: "call_expression",
    +
    +	language(callExpression) {
    +		const { firstChild } = callExpression;
    +		if (firstChild.type === "identifier") {
    +			return firstChild.text;
    +		}
    +	},
    +
    +	content(callExpression) {
    +		const { lastChild } = callExpression;
    +		if (lastChild.type === "template_string") {
    +			return lastChild;
    +		}
    +	},
    +});
    +

    The language callback would then be called with every call_expression node in the syntax tree. In the example above, it would retrieve the first child of the call_expression, which is an identifier with the name "html". The callback would then return the string "html".

    The content callback would then be called with the same call_expression node and return the template_string node within the call_expression node.

    In order to parse the HTML within the template string, the HTML grammar file would need to specify an injectionRegex:

    injectionRegex: 'html|HTML'
    +

    Code Folding

    The next field in the grammar file, folds, controls code folding. Its value is an array of fold pattern objects. Fold patterns are used to decide whether or not a syntax node can be folded, and if so, where the fold should start and end. Here are some example fold patterns:

    folds: [
    +
    +  # All `comment` nodes are foldable. By default, the fold starts at
    +  # the end of the node's first line, and ends at the beginning
    +  # of the node's last line.
    +  {
    +    type: 'comment'
    +  }
    +
    +  # `if_statement` nodes are foldable if they contain an anonymous
    +  # "then" token and either an `elif_clause` or `else_clause` node.
    +  # The fold starts at the end of the "then" token and ends at the
    +  # `elif_clause` or `else_clause`.
    +  {
    +    type: 'if_statement',
    +    start: {type: '"then"'}
    +    end: {type: ['elif_clause', 'else_clause']}
    +  }
    +
    +  # Any node that starts with an anonymous "(" token and ends with
    +  # an anonymous ")" token is foldable. The fold starts after the
    +  # "(" and ends before the ")".
    +  {
    +    start: {type: '"("', index: 0},
    +    end: {type: '")"', index: -1}
    +  }
    +]
    +

    Fold patterns can have one or more of the following fields:

    • type - A string or array of strings. In order to be foldable according to this pattern, a syntax node's type must match one of these strings.
    • start - An object that is used to identify a child node after which the fold should start. The object can have one or both of the following fields:
      • type - A string or array of strings. To start a fold, a child node's type must match one of these strings.
      • index - a number that's used to select a specific child according to its index. Negative values are interpreted as indices relative the last child, so that -1 means the last child.
    • end - An object that is used to identify a child node before which the fold should end. It has the same structure as the start object.

    Comments

    The last field in the grammar file, comments, controls the behavior of Atom's Editor: Toggle Line Comments command. Its value is an object with a start field and an optional end field. The start field is a string that should be prepended to or removed from lines in order to comment or un-comment them.

    In JavaScript, it looks like this:

    comments:
    +  start: '// '
    +

    The end field should be used for languages that only support block comments, not line comments. If present, it will be appended to or removed from the end of the last selected line in order to comment or un-comment the selection.

    In CSS, it would look like this:

    comments:
    +  start: '/* '
    +  end: ' */'
    +

    Example Packages

    More examples of all of these features can be found in the Tree-sitter grammars bundled with Atom:

    Creating a Legacy TextMate Grammar

    Atom's syntax highlighting can be powered by two types of grammars. If you're adding support for a new language, the preferred way is to create a Tree-sitter grammar. Tree-sitter grammars have better performance and provide support for more editor features, such as the Select Larger Syntax Node command.

    This section describes the Atom's legacy support for TextMate grammars.

    TextMate grammars are supported by several popular text editors. They provide a set of regex (regular expression) patterns which are assigned scopes. These scopes are then turned into the CSS classes that you can target in syntax themes.

    Note

    Note: This tutorial is a work in progress.

    Getting Started

    TextMate Grammars depend heavily on regexes, and you should be comfortable with interpreting and writing regexes before continuing. Note that Atom uses the Oniguruma engine, which is very similar to the PCRE or Perl regex engines. Here are some resources to help you out:

    • https://www.regular-expressions.info/tutorial.html provides a comprehensive regex tutorial
    • https://www.rexegg.com/regex-quickstart.html contains a cheat sheet for various regex expressions
    • https://regex101.com/ or https://regexr.com/ allows live prototyping
    • https://github.com/kkos/oniguruma/blob/master/doc/RE the docs for the Oniguruma regex engine

    Grammar files are written in the CSONopen in new window or JSONopen in new window format. Whichever one you decide to use is up to you, but this tutorial will be written in CSON.

    Create the Package

    To get started, press Cmd+Shift+PCtrl+Shift+P and start typing "Generate Package" to generate a new grammar package. Select "Package Generator: Generate Package," and you'll be asked for the path where your package will be created. Let's call ours language-flight-manual.

    Tip

    Tip: Grammar packages should start with language-.

    The default package template creates a lot of folders that aren't needed for grammar packages. Go ahead and delete the keymaps, lib, menus, and styles folders. Furthermore, in package.json, remove the activationCommands section. Now create a new folder called grammars, and inside that a file called flight-manual.cson. This is the main file that we will be working with - start by populating it with a boilerplate templateopen in new window. Now let's go over what each key means.

    scopeName is the root scope of your package. This should generally describe what language your grammar package is highlighting; for example, language-javascript's scopeName is source.js and language-html's is text.html.basic. Name it source.flight-manual for now.

    name is the user-friendly name that is displayed in places like the status bar or the grammar selector. Again, this name should describe what the grammar package is highlighting. Rename it to Flight Manual.

    fileTypes is an array of filetypes that language-flight-manual should highlight. We're interested in highlighting the Flight Manual's Markdown files, so add the md extension to the list and remove the others.

    patterns contains the array of regex patterns that will determine how the file is tokenized.

    Adding Patterns

    To start, let's add a basic pattern to tokenize the words Flight Manual whenever they show up. Your regex should look like \bFlight Manual\b. Here's what your patterns block should look like:

    'patterns': [
    +  {
    +    'match': '\\bFlight Manual\\b'
    +    'name': 'entity.other.flight-manual'
    +  }
    +]
    +

    match is where your regex is contained, and name is the scope name that is to be applied to the entirety of the match. More information about scope names can be found in Section 12.4 of the TextMate Manualopen in new window.

    Tip

    Tip: All scopes should end with the portion of the root scopeName after the leading source or text. In our case, all scopes should end with flight-manual.

    Note

    Note: Astute readers may have noticed that the \b was changed to \\b with two backslashes and not one. This is because CSON processes the regex string before handing it to Oniguruma, so all backslashes need to be escaped twice.

    But what if we wanted to apply different scopes to Flight and Manual? This is possible by adding capture groups to the regex and then referencing those capture groups in a new capture property. For example:

    'match': '\\b(Flight) (Manual)\\b'
    +'name': 'entity.other.flight-manual'
    +'captures':
    +  '1':
    +    'name': 'keyword.other.flight.flight-manual'
    +  '2':
    +    'name': 'keyword.other.manual.flight-manual'
    +

    This will assign the scope keyword.other.flight.flight-manual to Flight, keyword.other.manual.flight-manual to Manual, and entity.other.flight-manual to the overarching Flight Manual.

    Begin/End Patterns

    Now let's say we want to tokenize the ::: note Note blocks that occur in Flight Manual files. Our previous two examples used match, but one limit of match is that it can only match single lines. ::: note Note blocks, on the other hand, can span multiple lines. For these cases, you can use the begin/end keys. Once the regex in the begin key is matched, tokenization will continue until the end pattern is reached.

    'begin': '({{)(#note)(}})'
    +'beginCaptures':
    +  '0': # The 0 capture contains the entire match
    +    'name': 'meta.block.start.flight-manual'
    +  '1':
    +    'name': 'punctuation.definition.block.flight-manual'
    +  '2':
    +    'name': 'keyword.note.flight-manual'
    +  '3':
    +    'name': 'punctuation.definition.block.flight-manual'
    +'end': '({{)(/note)(}})'
    +'endCaptures':
    +  '0':
    +    'name': 'meta.block.end.flight-manual'
    +  '1':
    +    'name': 'punctuation.definition.block.flight-manual'
    +  '2':
    +    'name': 'keyword.note.flight-manual'
    +  '3':
    +    'name': 'punctuation.definition.block.flight-manual'
    +'name': 'meta.block.note.flight-manual'
    +

    Tip

    Tip: Get into the habit of providing punctuation scopes early on. It's much less effort than having to go back and rewriting all your patterns to support punctuation scopes when your grammar starts to get a bit longer!

    Awesome, we have our first multiline pattern! However, if you've been following along and playing around in your own .md file, you may have noticed that Flight Manual doesn't receive any scopes inside a note block. A begin/end block is essentially a subgrammar of its own: once it starts matching, it will only match its own subpatterns until the end pattern is reached. Since we haven't defined any subpatterns, then clearly nothing will be matched inside of a note block. Let's fix that!

    'begin': '({{)(#note)(}})'
    +'beginCaptures':
    +  '0': # The 0 capture contains the entire match
    +    'name': 'meta.block.start.flight-manual'
    +  '1':
    +    'name': 'punctuation.definition.block.flight-manual'
    +  '2':
    +    'name': 'keyword.note.flight-manual'
    +  '3':
    +    'name': 'punctuation.definition.block.flight-manual'
    +'end': '({{)(/note)(}})'
    +'endCaptures':
    +  '0':
    +    'name': 'meta.block.end.flight-manual'
    +  '1':
    +    'name': 'punctuation.definition.block.flight-manual'
    +  '2':
    +    'name': 'keyword.note.flight-manual'
    +  '3':
    +    'name': 'punctuation.definition.block.flight-manual'
    +'name': 'meta.block.note.flight-manual'
    +'patterns': [
    +  {
    +    'match': '\\b(Flight) (Manual)\\b'
    +    'name': 'entity.other.flight-manual'
    +    'captures':
    +      '1':
    +        'name': 'keyword.other.flight.flight-manual'
    +      '2':
    +        'name': 'keyword.other.manual.flight-manual'
    +  }
    +]
    +

    There. With the patterns block, Flight Manual should now receive the proper scopes.

    Repositories and the Include keyword, or how to avoid duplication

    At this point, note blocks are looking pretty nice, as is the Flight Manual keyword, but the rest of the file is noticeably lacking any form of Markdown syntax highlighting. Is there a way to include the GitHub-Flavored Markdown grammar without copying and pasting everything over? This is where the include keyword comes in. include allows you to include other patterns, even from other grammars! language-gfm's scopeName is source.gfm, so let's include that. Our patterns block should now look like the following:

    'patterns': [
    +  {
    +    'include': 'source.gfm'
    +  }
    +  {
    +    # Flight Manual pattern
    +  }
    +  {
    +    # Note begin/end pattern
    +  }
    +]
    +

    However, including source.gfm has led to another problem: note blocks still don't have any Markdown highlighting! The quick fix would be to add the include pattern to the note's pattern block as well, but now we're duplicating two patterns. You can imagine that as this grammar grows it'll quickly become inefficient to keep copying each new global pattern over to the note pattern as well. Therefore, include helpfully recognizes the special $self scope. $self automatically includes all the top-level patterns of the current grammar. The note block can then be simplified to the following:

    'begin': '({{)(#note)(}})'
    +# beginCaptures
    +'end': '({{)(/note)(}})'
    +# endCaptures
    +'name': 'meta.block.note.flight-manual'
    +'patterns': [
    +  {
    +    'include': '$self'
    +  }
    +]
    +

    Where to Go from Here

    There are several good resources out there that help when writing a grammar. The following is a list of some particularly useful ones (some have been linked to in the sections above as well).

    Publishing

    Atom bundles a command line utility called apm which we first used back in Command Line to search for and install packages via the command line. The apm command can also be used to publish Atom packages to the public registry and update them.

    Prepare Your Package

    There are a few things you should double check before publishing:

    • Your package.json file has name, description, and repository fields.
    • Your package.json file has a version field with a value of "0.0.0".
    • Your package.json file has an engines field that contains an entry for Atom such as: "engines": {"atom": ">=1.0.0 <2.0.0"}.
    • Your package has a README.md file at the root.
    • Your repository URL in the package.json file is the same as the URL of your repository.
    • Your package is in a Git repository that has been pushed to GitHubopen in new window. Follow this guideopen in new window if your package isn't already on GitHub.

    Publish Your Package

    Before you publish a package it is a good idea to check ahead of time if a package with the same name has already been published to the atom.io package registryopen in new window. You can do that by visiting https://atom.io/packages/your-package-name to see if the package already exists. If it does, update your package's name to something that is available before proceeding.

    Now let's review what the apm publish command does:

    1. Registers the package name on atom.io if it is being published for the first time.
    2. Updates the version field in the package.json file and commits it.
    3. Creates a new Git tagopen in new window for the version being published.
    4. Pushes the tag and current branch up to GitHub.
    5. Updates atom.io with the new version being published.

    Now run the following commands to publish your package:

    $ cd path-to-your-package
    +$ apm publish minor
    +

    If this is the first package you are publishing, the apm publish command may prompt you for your GitHub username and password. If you have two-factor authentication enabled, use a personal access tokenopen in new window in lieu of a password. This is required to publish and you only need to enter this information the first time you publish. The credentials are stored securely in your keychainopen in new window once you login.

    Your package is now published and available on atom.io. Head on over to https://atom.io/packages/your-package-name to see your package's page.

    With apm publish, you can bump the version and publish by using

    $ apm publish <em>version-type</em>
    +

    where version-type can be major, minor and patch.

    The major option to the publish command tells apm to increment the first number of the version before publishing so the published version will be 1.0.0 and the Git tag created will be v1.0.0.

    The minor option to the publish command tells apm to increment the second number of the version before publishing so the published version will be 0.1.0 and the Git tag created will be v0.1.0.

    The patch option to the publish command tells apm to increment the third number of the version before publishing so the published version will be 0.0.1 and the Git tag created will be v0.0.1.

    Use major when you make a change that breaks backwards compatibility, like changing defaults or removing features. Use minor when adding new functionality or options, but without breaking backwards compatibility. Use patch when you've changed the implementation of existing features, but without changing the behaviour or options of your package. Check out semantic versioningopen in new window to learn more about best practices for versioning your package releases.

    You can also run apm help publish to see all the available options and apm help to see all the other available commands.

    Iconography

    Atom comes bundled with the Octicons 4.4.0open in new window icon set. Use them to add icons to your packages.

    NOTE: Some older icons from version 2.1.2 are still kept for backwards compatibility.

    Overview

    In the Styleguide under the "Icons" section you'll find all the Octicons that are available.

    Octicons in the Styleguide

    Usage

    Octicons can be added with simple CSS classes in your markup. Prefix the icon names with icon icon-.

    As an example, to add a monitor icon (device-desktop), use the icon icon-device-desktop classes:

    <span class="icon icon-device-desktop"></span>
    +

    Size

    Octicons look best with a font-size of 16px. It's already used as the default, so you don't need to worry about it. In case you prefer a different icon size, try to use multiples of 16 (32px, 48px etc.) for the sharpest result. Sizes in between are ok too, but might look a bit blurry for icons with straight lines.

    Usability

    Although icons can make your UI visually appealing, when used without a text label, it can be hard to guess its meaning. In cases where space for a text label is insufficient, consider adding a tooltipopen in new window that appears on hover. Or a more subtle title="label" attribute would help as well.

    Debugging

    Atom provides several tools to help you understand unexpected behavior and debug problems. This guide describes some of those tools and a few approaches to help you debug and provide more helpful information when submitting issuesopen in new window:

    Update to the Latest Version

    You might be running into an issue which was already fixed in a more recent version of Atom than the one you're using.

    If you're using a released version, check which version of Atom you're using:

    $ atom --version
    +> Atom    : 1.8.0
    +> Electron: 0.36.8
    +> Chrome  : 47.0.2526.110
    +> Node    : 5.1.1
    +

    Then check for the latest Stable versionopen in new window.

    If you're building Atom from source, pull down the latest version of master and re-buildopen in new window.

    Using Safe Mode

    A large part of Atom's functionality comes from packages you can install. Atom will also execute the code in your init script on startup. In some cases, these packages and the code in the init script might be causing unexpected behavior, problems, or performance issues.

    To determine if that is happening, start Atom from the terminal in safe mode:

    $ atom --safe
    +

    This starts Atom, but does not load packages from ~/.atom/packages or ~/.atom/dev/packages and disables loading of your init script. If you can no longer reproduce the problem in safe mode, it's likely it was caused by one of the packages or the init script.

    If removing or commenting out all content from the init script and starting Atom normally still produces the error, then try figuring out which package is causing trouble. Start Atom normally again and open the Settings View with Cmd+,Ctrl+,. Since the Settings View allows you to disable each installed package, you can disable packages one by one until you can no longer reproduce the issue. Restart Atom or reload Atom with Alt+Cmd+Ctrl+LCtrl+Shift+F5 after you disable each package to make sure it's completely gone.

    When you find the problematic package, you can disable or uninstall the package. We strongly recommend creating an issue on the package's GitHub repository.

    Clearing Saved State

    Atom saves a number of things about your environment when you exit in order to restore Atom to the same configuration when you next launch the program. In some cases the state that gets saved can be something undesirable that prevents Atom from working properly. In these cases, you may want to clear the state that Atom has saved.

    DANGER

    :rotatinglight: Danger: Clearing the saved state permanently destroys any state that Atom has saved _across all projects. This includes unsaved changes to files you may have been editing in all projects. This is a destructive action.

    Clearing the saved state can be done by opening a terminal and executing:

    $ atom --clear-window-state
    +

    Reset to Factory Defaults

    In some cases, you may want to reset Atom to "factory defaults", in other words clear all of your configuration and remove all packages. This can easily be done by opening a terminal and executing:

    Once that is complete, you can launch Atom as normal. Everything will be just as if you first installed Atom.

    Tip

    Tip: The command given above doesn't delete the old configuration, just puts it somewhere that Atom can't find it. If there are pieces of the old configuration you want to retrieve, you can find them in the ~/.atom-backup%USERPROFILE%\.atom-backup directory.

    Check for Linked Packages

    If you develop or contribute to Atom packages, there may be left-over packages linked to your ~/.atom/packages or ~/.atom/dev/packages directories. You can use the apm links command to list all linked packages:

    $ apm links
    +> /Users/octocat/.atom/dev/packages (0)
    +> └── (no links)
    +> /Users/octocat/.atom/packages (1)
    +> └── color-picker -> /Users/octocat/github/color-picker
    +

    You can remove links using the apm unlink command:

    $ apm unlink color-picker
    +> Unlinking /Users/octocat/.atom/packages/color-picker ✓
    +

    See apm links --help and apm unlink --help for more information on these commands.

    Tip

    Tip: You can also use apm unlink --all to easily unlink all packages and themes.

    Check for Incompatible Packages

    If you have packages installed that use native Node modules, when you upgrade to a new version of Atom, they might need to be rebuilt. Atom detects this and through the incompatible-packages packageopen in new window displays an indicator in the status bar when this happens.

    Incompatible Packages Status Bar Indicator

    If you see this indicator, click it and follow the instructions.

    Check Atom and Package Settings

    In some cases, unexpected behavior might be caused by settings in Atom or in one of the packages.

    Open Atom's Settings Viewopen in new window with Cmd+,Ctrl+,, the Atom > PreferencesFile > PreferencesEdit > Preferences menu option, or the "Settings View: Open" command from the Command Paletteopen in new window.

    Settings View

    Check Atom's settings in the Settings View, there's a description of most configuration options in the Basic Customization section. For example, if you want Atom to hide the invisible symbols representing whitespace characters, disable the "Show Invisibles" option.

    Some of these options are also available on a per-language basis which means that they may be different for specific languages, for example JavaScript or Python. To check the per-language settings, open the settings for the language package under the Packages tab in the Settings View, for example the language-javascript or language-python package.

    Since Atom ships with a set of packages and you can also install additional packages yourself, check the list of packages and their settings. For instance, if you'd like to get rid of the vertical line in the middle of the editor, disable the Wrap Guide packageopen in new window. And if you don't like it when Atom strips trailing whitespace or ensures that there's a single trailing newline in the file, you can configure that in the whitespace package'sopen in new window settings.

    Package Settings

    Check Your Configuration

    You might have defined some custom styles, keymaps or snippets in one of your configuration files. In some situations, these personal hacks might be causing the unexpected behavior you're observing so try clearing those files and restarting Atom.

    Check Your Keybindings

    If a command is not executing when you press a key combination or the wrong command is executing, there might be an issue with the keybinding for that combination. Atom ships with the Keybinding Resolveropen in new window, a neat package which helps you understand what key Atom saw you press and the command that was triggered because of it.

    Show the keybinding resolver with Cmd+.Ctrl+. or with "Keybinding Resolver: Show" from the Command palette. With the Keybinding Resolver shown, press a key combination:

    Keybinding Resolver

    The Keybinding Resolver shows you a list of keybindings that exist for the key combination, where each item in the list has the following:

    • the command for the keybinding
    • the CSS selector used to define the context in which the keybinding is valid
    • the file in which the keybinding is defined

    The keybindings are listed in two colors. All the keybindings that are matched but not executed are shown in gray. The one that is executed, if any, is shown in green. If the command you wanted to trigger isn't listed, then a keybinding for that command hasn't been loaded.

    If multiple keybindings are matched, Atom determines which keybinding will be executed based on the specificity of the selectors and the order in which they were loaded. If the command you wanted to trigger is listed in the Keybinding Resolver, but wasn't the one that was executed, this is normally explained by one of two causes:

    • The key combination was not used in the context defined by the keybinding's selector

      For example, you can't trigger the keybinding for the tree-view:add-file command if the Tree View is not focused.

    • There is another keybinding that took precedence

      This often happens when you install a package which defines keybindings that conflict with existing keybindings. If the package's keybindings have selectors with higher specificity or were loaded later, they'll have priority over existing ones.

    Atom loads core Atom keybindings and package keybindings first, and user-defined keybindings last. Since user-defined keybindings are loaded last, you can use your keymap.cson file to tweak the keybindings and sort out problems like these. See the Keymaps in Depth section for more information.

    If you notice that a package's keybindings are taking precedence over core Atom keybindings, it might be a good idea to report the issue on that package's GitHub repository. You can contact atom maintainers on Atom's github discussionsopen in new window

    Check Font Rendering Issues

    You can determine which fonts are being used to render a specific piece of text by using the Developer Tools. To open the Developer Tools press Alt+Cmd+ICtrl+Shift+I. Once the Developer Tools are open, click the "Elements" tab. Use the standard tools for finding the elementopen in new window containing the text you want to check. Once you have selected the element, you can click the "Computed" tab in the styles pane and scroll to the bottom. The list of fonts being used will be shown there:

    Fonts In Use

    Check for Errors in the Developer Tools

    When an unexpected error occurs in Atom, you will normally see a red notification which provides details about the error and allows you to create an issue on the right repository:

    Exception Notification

    Not all errors are logged with a notification so if you suspect you're experiencing an error but there's no notification, you can also look for errors in the developer tools Console tab. To access the Console tab, press Alt-Cmd-ICtrl-Shift-I to open developer tools and then click the Console tab:

    DevTools Error

    If there are multiple errors, you can scroll down to the bottom of the panel to see the most recent error. Or while reproducing an error, you can right click in the Console tab panel, select Clear console to remove all Console output, and then reproduce the error to see what errors are logged to the Console tab.

    Note

    Note: When running in Dev Mode, the developer tools are automatically shown with the error logged in the Console tab.

    Find Crash Logs

    Diagnose Startup Performance

    If Atom is taking a long time to start, you can use the Timecop packageopen in new window to get insight into where Atom spends time while loading.

    Timecop

    Timecop displays the following information:

    • Atom startup times
    • File compilation times
    • Package loading and activation times
    • Theme loading and activation times

    If a specific package has high load or activation times, you might consider reporting an Issue to the maintainers. You can also disable the package to potentially improve future startup times.

    Diagnose Runtime Performance

    If you're experiencing performance problems in a particular situation, your Issue reportsopen in new window will be more valuable if you include a saved profile from Chrome's CPU profiler that gives some insight into what is slow.

    To run a profile, open the Developer Tools with Alt+Cmd+ICtrl+Shift+I. From there:

    1. Click the Profiles tab
    2. Select "Collect JavaScript CPU Profile"
    3. Click "Start"

    DevTools Profiler

    Once that is done, then perform the slow action to capture a recording. When finished, click "Stop". Switch to the "Chart" view, and a graph of the recorded actions will appear. You can save and post the profile data by clicking "Save" next to the profile's name in the left panel.

    DevTools Profiler

    To learn more, check out the Chrome documentation on CPU profilingopen in new window.

    Profiling Startup Performance

    If the time for loading the window looks high, you can create a CPU profile for that period using the --profile-startup command line flag when starting Atom:

    $ atom --profile-startup .
    +

    This will automatically capture a CPU profile as Atom is loading and open the Developer Tools once Atom loads. From there:

    1. Click the Profiles tab in the Developer Tools
    2. Select the "startup" profile
    3. Click the "Save" link for the startup profile

    You can then include the startup profile in any Issue you report.

    Check Your Build Tools

    If you are having issues installing a package using apm install, this could be because the package has dependencies on libraries that contain native code. This means you will need to have a C++ compiler and Python installed to be able to install it. You can run apm install --check to see if the Atom package manager can build native code on your machine.

    Check out the pre-requisites in the build instructionsopen in new window for your platform for more details.

    Check if your GPU is causing the problem

    If you encounter flickering or other rendering issues, you can stop Atom from using your Graphics Processing Unit (GPU) with the --disable-gpu Chromium flag to see if the fault lies with your GPU:

    $ atom --disable-gpu
    +

    Chromium (and thus Atom) normally uses the GPU to accelerate drawing parts of the interface. --disable-gpu tells Atom to not even attempt to do this, and just use the CPU for rendering everything. This means that the parts of the interface that would normally be accelerated using the GPU will instead take slightly longer and render on the CPU. This likely won't make a noticeable difference, but does slightly increase the battery usage as the CPU has to work harder to do the things the GPU is optimized for.

    Two other Chromium flags that are useful for debugging are --enable-gpu-rasterization and --force-gpu-rasterization:

    $ atom --enable-gpu-rasterization --force-gpu-rasterization
    +

    --enable-gpu-rasterization allows other commands to determine how a layer tile (graphics) should be drawn and --force-gpu-rasterization determines that the Skia GPU backend should be used for drawing layer tiles (only valid with GPU accelerated compositing).

    Be sure to use Chromium flags at the end of the terminal call if you want to use other Atom flags as they will not be executed after the Chromium flags e.g.:

    $ atom --safe --enable-gpu-rasterization --force-gpu-rasterization
    +

    Writing Specs

    We've looked at and written a few specs through the examples already. Now it's time to take a closer look at the spec framework itself. How exactly do you write tests in Atom?

    Atom uses Jasmineopen in new window as its spec framework. Any new functionality should have specs to guard against regressions.

    Create a New Spec

    Atom specsopen in new window and package specsopen in new window are added to their respective spec directory. The example below creates a spec for Atom core.

    Create a Spec File

    Spec files must end with -spec so add sample-spec.coffee to the spec directory.

    Add One or More describe Methods

    The describe method takes two arguments, a description and a function. If the description explains a behavior it typically begins with when; if it is more like a unit test it begins with the method name.

    describe("when a test is written", function () {
    +	// contents
    +});
    +

    or

    describe("Editor::moveUp", function () {
    +	// contents
    +});
    +
    Add One or More it Methods

    The it method also takes two arguments, a description and a function. Try and make the description flow with the it method. For example, a description of "this should work" doesn't read well as "it this should work". But a description of "should work" sounds great as "it should work".

    describe("when a test is written", function () {
    +	it("has some expectations that should pass", function () {
    +		// Expectations
    +	});
    +});
    +
    Add One or More Expectations

    The best way to learn about expectations is to read the Jasmine documentationopen in new window about them. Below is a simple example.

    describe("when a test is written", function () {
    +	it("has some expectations that should pass", function () {
    +		expect("apples").toEqual("apples");
    +		expect("oranges").not.toEqual("apples");
    +	});
    +});
    +
    Custom Matchers

    In addition to the Jasmine's built-in matchers, Atom includes the following:

    • jasmine-jqueryopen in new window
    • The toBeInstanceOf matcher is for the instanceof operator
    • The toHaveLength matcher compares against the .length property
    • The toExistOnDisk matcher checks if the file exists in the filesystem
    • The toHaveFocus matcher checks if the element currently has focus
    • The toShow matcher tests if the element is visible in the dom

    These are defined in spec/spec-helper.coffeeopen in new window.

    Asynchronous Specs

    Writing Asynchronous specs can be tricky at first. Some examples.

    Promises

    Working with promises is rather easy in Atom. You can use our waitsForPromise function.

    describe("when we open a file", function () {
    +	it("should be opened in an editor", function () {
    +		waitsForPromise(function () {
    +			atom.workspace
    +				.open("c.coffee")
    +				.then((editor) => expect(editor.getPath()).toContain("c.coffee"));
    +		});
    +	});
    +});
    +

    This method can be used in the describe, it, beforeEach and afterEach functions.

    describe("when we open a file", function () {
    +	beforeEach(function () {
    +		waitsForPromise(() => atom.workspace.open("c.coffee"));
    +	});
    +
    +	it("should be opened in an editor", function () {
    +		expect(atom.workspace.getActiveTextEditor().getPath()).toContain(
    +			"c.coffee"
    +		);
    +	});
    +});
    +

    If you need to wait for multiple promises use a new waitsForPromise function for each promise. (Caution: Without beforeEach this example will fail!)

    describe("waiting for the packages to load", function () {
    +	beforeEach(function () {
    +		waitsForPromise(() => atom.workspace.open("sample.js"));
    +
    +		waitsForPromise(() => atom.packages.activatePackage("tabs"));
    +
    +		waitsForPromise(() => atom.packages.activatePackage("tree-view"));
    +	});
    +
    +	it("should have waited long enough", function () {
    +		expect(atom.packages.isPackageActive("tabs")).toBe(true);
    +		expect(atom.packages.isPackageActive("tree-view")).toBe(true);
    +	});
    +});
    +

    waitsForPromise can take an additional object argument before the function. The object can have the following properties:

    • shouldReject Whether the promise should reject or resolve (default: false)
    • timeout The amount of time (in ms) to wait for the promise to be resolved or rejected (default: process.env.CI ? 60000 : 5000)
    • label The label to display if promise times out (default: 'promise to be resolved or rejected')
    describe("when we open a file", function () {
    +	it("should be opened in an editor", function () {
    +		waitsForPromise(
    +			{
    +				shouldReject: false,
    +				timeout: 5000,
    +				label: "promise to be resolved or rejected",
    +			},
    +			() =>
    +				atom.workspace
    +					.open("c.coffee")
    +					.then((editor) => expect(editor.getPath()).toContain("c.coffee"))
    +		);
    +	});
    +});
    +
    Asynchronous Functions with Callbacks

    Specs for asynchronous functions can be done using the waitsFor and runs functions. A simple example.

    describe("fs.readdir(path, cb)", function () {
    +	it("is async", function () {
    +		const spy = jasmine.createSpy("fs.readdirSpy");
    +		fs.readdir("/tmp/example", spy);
    +
    +		waitsFor(() => spy.callCount > 0);
    +
    +		runs(function () {
    +			const exp = [null, ["example.coffee"]];
    +
    +			expect(spy.mostRecentCall.args).toEqual(exp);
    +			expect(spy).toHaveBeenCalledWith(null, ["example.coffee"]);
    +		});
    +	});
    +});
    +

    For a more detailed documentation on asynchronous tests please visit the Jasmine documentationopen in new window.

    Running Specs

    Most of the time you'll want to run specs by triggering the window:run-package-specs command. This command is not only to run package specs, it can also be used to run Atom core specs when working on Atom itself. This will run all the specs in the current project's spec directory.

    To run a limited subset of specs use the fdescribe or fit methods. You can use those to focus a single spec or several specs. Modified from the example above, focusing an individual spec looks like this:

    describe("when a test is written", function () {
    +	fit("has some expectations that should pass", function () {
    +		expect("apples").toEqual("apples");
    +		expect("oranges").not.toEqual("apples");
    +	});
    +});
    +
    Running on CI

    It is now easy to run the specs in a CI environment like Travis and AppVeyor. See the Travis CI For Your Packagesopen in new window and AppVeyor CI For Your Packagesopen in new window posts for more details.

    Running via the Command Line

    To run tests on the command line, run Atom with the --test flag followed by one or more paths to test files or directories. You can also specify a --timeout option, which will force-terminate your tests after a certain number of seconds have passed.

    atom --test --timeout 60 ./test/test-1.js ./test/test-2.js
    +

    Customizing your test runner

    WARNING

    Warning: This API is available as of 1.2.0-beta0, and it is experimental and subject to change. Test runner authors should be prepared to test their code against future beta releases until it stabilizes.

    By default, package tests are run with Jasmine 1.3, which is outdated but can't be changed for compatibility reasons. You can specify your own custom test runner by including an atomTestRunner field in your package.json. Atom will require whatever module you specify in this field, so you can use a relative path or the name of a module in your package's dependencies.

    Your test runner module must export a single function, which Atom will call within a new window to run your package's tests. Your function will be called with the following parameters:

    • testPaths An array of paths to tests to run. Could be paths to files or directories.
    • buildAtomEnvironment A function that can be called to construct an instance of the atom global. No atom global will be explicitly assigned, but you can assign one in your runner if desired. This function should be called with the following parameters:
      • applicationDelegate An object responsible for Atom's interaction with the browser process and host OS. Use buildDefaultApplicationDelegate for a default instance. You can override specific methods on this object to prevent or test these interactions.
      • window A window global.
      • document A document global.
      • configDirPath A path to the configuration directory (usually ~/.atom).
      • enablePersistence A boolean indicating whether the Atom environment should save or load state from the file system. You probably want this to be false.
    • buildDefaultApplicationDelegate A function that builds a default instance of the application delegate, suitable to be passed as the applicationDelegate parameter to buildAtomEnvironment.
    • logFile An optional path to a log file to which test output should be logged.
    • headless A boolean indicating whether or not the tests are being run from the command line via atom --test.
    • legacyTestRunner This function can be invoked to run the legacy Jasmine runner, giving your package a chance to transition to a new test runner while maintaining a subset of its tests in the old environment.

    Your function should return a promise that resolves to an exit code when your tests are finish running. This exit code will be returned when running your tests via the command line.

    Handling URIs

    Beginning in Atom 1.23, packages have the ability to handle special URIs triggered from the system; for example, a package named my-package can register itself to handle any URI starting with atom://my-package/.

    WARNING

    Warning: Handling URIs triggered from other applications, like a web browser, is a powerful tool, but also one that can be jarring. You should shape your package's user experience to handle this well. In general, you should avoid taking direct action on behalf of a user. For example, a URI handler that immediately installs a package is too invasive, but a URI handler that shows the package's pane in the settings view is useful. A URI handler that begins to clone a repo is overly aggressive, but a URI handler that prompts the user to clone a repo is okay.

    Any package with a URI handler that we feel violates this guideline is subject to removal from the Atom package registry at our discretion.

    Modifying your package.json

    The first step to handling URIs from your package is to modify its package.json file. You should add a new key called uriHandler, and its value should be an object.

    The uriHandler object must contain a key called method with a string value that tells Atom which method in your package to call when a URI needs to be handled. The object can optionally include a key called deferActivation which can be set to the boolean false to prevent Atom from deferring activation of your package — see more below.

    For example, if we want our package my-package to handle URIs with a method on our package's main module called handleURI, we could add the following to our package.json:

    "uriHandler": {
    +  "method": "handleURI"
    +}
    +

    Modifying your Main Module

    Now that we've told Atom that we want our package to handle URIs beginning with atom://my-package/ via our handleURI method, we need to actually write this method. Atom passes two arguments to your URI handler method; the first one is the fully-parsed URI plus query string, parsed with Node's url.parse(uri, true)open in new window. The second argument is the raw, string URI; this is normally not needed since the first argument gives you structured information about the URI.

    Here's a sample package, written in JavaScript, that handles URIs with the package.json configuration we saw above.

    export default {
    +	activate() {
    +		// normal activation code here
    +	},
    +
    +	handleURI(parsedUri) {
    +		console.log(parsedUri);
    +	},
    +};
    +

    When Atom handles, for example, the URI atom://my-package/my/test/url?value=42&other=false, the package would log out something like the following:

    {
    +  protocol: 'atom:',
    +  slashes: true,
    +  auth: null,
    +  host: 'my-package',
    +  port: null,
    +  hostname: 'my-package',
    +  hash: null,
    +  search: '?value=true&other=false',
    +  query: { value: '42', other: 'false' },
    +  pathname: '/my/test/url',
    +  path: '/my/test/url?value=true&other=false',
    +  href: 'atom://my-package/my/test/url?value=true&other=false'
    +}
    +

    Notice that the query string arguments are available in the query property, but are strings — you'll have to convert to other native types yourself.

    Controlling Activation Deferral

    For performance reasons, adding a uriHandler entry to your package's package.json will enable deferred activation. This means that Atom will not activate your package until it has a URI for it to handle — it will then activate your package and then immediately call the URI handler method. If you want to disable the deferred activation, ensuring your package is activated upon startup, you can add "deferActivation": false to the URI handler config. For example,

    "uriHandler": {
    +  "method": "handleURI",
    +  "deferActivation": false
    +}
    +

    Before doing this, make sure your package actually needs to be activated immediately — disabling deferred activation means Atom takes longer to start since it has to activate all packages without deferred activation.

    Linux Support

    Because URI handling is different across operating systems and distributions, there is no built-in URI handler support for Atom on Linux. If you want to configure URI handling on your system yourself, then you should configure atom: protocol URI's to trigger atom with the --uri-handler flag; for example, the URI atom://test/uri should launch Atom via atom --uri-handler atom://test/uri.

    Core URIs

    Atom provides a core URI to handle opening files with the syntax atom://core/open/file?filename=<filepath>&line=<line>&column=<col>

    Cross-Platform Compatibility

    Atom runs on a number of platforms and while Electron and Node take care of many of the details there are still some considerations to ensure your package works on other operating systems.

    File symlinks can be used on Windows by non-Administrators by specifying 'junction' as the type (this argument is ignored on macOS & Linux).

    Also consider:

    • Symlinks committed to Git will not checkout correctly on Windows - dynamically create what you need with fs.symlink instead
    • Symlinked directories are only available to Administrators on Windows - avoid a dependency on them

    Filenames

    • Reserved filenames on Windows are com1-com9, lpt1-lpt9, con, nul, aux and prn (regardless of extension, e.g. prn.txt is disallowed)
    • Reserved characters on Windows are ? \ / < > ? % | : " so avoid where possible
    • Names with spaces when passed to the command line;
      • Windows requires you surround the path with double quotes e.g. "c:\my test"
      • macOS and Linux require a backslash before each space e.g. /my\ test

    File paths

    • Windows uses \ although some tools and PowerShell allow / too
    • macOS and Linux use /

    You can dynamically find out what your platform uses with path.sep or better yet use the node path library functions such as join and normalize which automatically take care of this.

    Windows supports up to 250 characters for a path - avoid deeply nested directory structures

    Paths are not URLs

    URL parsing routines should not be used on file paths. While they initially look like a relative path it will fail in a number of scenarios on all platforms.

    • Various characters are misinterpreted, e.g. ? as query string, # as a fragment identifier
    • Windows drive specifiers are incorrectly parsed as a protocol

    If you need to use a path for a URL use the file: protocol with an absolute path instead to ensure drive letters and slashes are appropriately addressed, e.g. file:///c|/test/pic.png

    fs.stat on directories

    The fs.stat function does not return the size of the contents of a directory but rather the allocation size of the directory itself. This returns 0 on Windows and 1024 on macOS and so should not be relied upon.

    path.relative can't traverse drives

    • On a macOS or Linux system path.relative can be used to calculate a relative path to traverse between any two given paths.
    • On Windows this is not always possible as it can contain multiple absolute roots, e.g. c:\ and d:\

    Rapid file operations

    Creation and deletion operations may take a few milliseconds to complete. If you need to remove many files and folders consider RimRAFopen in new window which has built-in retry logic for this.

    Line endings

    • Windows uses CRLF
    • macOS and Linux use LF
    • Git on Windows often has autocrlf set which automatically converts between the two

    If you are writing specs that use text file fixtures consider that this will interfere with file lengths, hash codes and direct text comparisons. It will also change the Atom selection length by 1 character per line.

    If you have spec fixtures that are text files you may want to tell Git to force LF, CRLF or not convert them by specifying the paths in .gitattributes e.g.

    spec/fixtures/always-crlf.txt eol=crlf
    +spec/fixtures/always-lf.txt eol=lf
    +spec/fixtures/leave-as-is.txt -text
    +

    Converting from TextMate

    It's possible that you have themes or grammars from TextMateopen in new window that you like and use and would like to convert to Atom. If so, you're in luck because there are tools to help with the conversion.

    Converting a TextMate Grammar Bundle

    Converting a TextMate bundle will allow you to use its editor preferences, snippets, and colorization inside Atom.

    Let's convert the TextMate bundle for the Ropen in new window programming language. You can find other existing TextMate bundles on GitHubopen in new window.

    You can convert the R bundle with the following command:

    $ apm init --package language-r --convert https://github.com/textmate/r.tmbundle
    +

    You can now change directory into language-r to see the converted bundle. Once you link your package with the apm link command, your new package is ready to use. Launch Atom and open a .r file in the editor to see it in action!

    Converting a TextMate Syntax Theme

    This section will go over how to convert a TextMateopen in new window theme to an Atom theme.

    Differences

    TextMate themes use plistopen in new window files while Atom themes use CSSopen in new window or Lessopen in new window to style the UI and syntax in the editor.

    The utility that converts the theme first parses the theme's plist file and then creates comparable CSS rules and properties that will style Atom similarly.

    Convert the Theme

    Download the theme you wish to convert, you can browse existing TextMate themes on the TextMate websiteopen in new window.

    Now, let's say you've downloaded the theme to ~/Downloads/MyTheme.tmTheme, you can convert the theme with the following command:

    $ apm init --theme my-theme --convert ~/Downloads/MyTheme.tmTheme
    +

    You can then change directory to my-theme to see the converted theme.

    Activate the Theme

    Once your theme is installed you can enable it by launching Atom and opening the Settings View with the Atom > PreferencesFile > PreferencesEdit > Preferences menu item. Then select the "Themes" tab on the left side navigation. Finally, choose "My Theme" from the "Syntax Theme" dropdown menu to enable your new theme.

    Your theme is now enabled, open an editor to see it in action!

    Hacking on Atom Core

    If you're hitting a bug in Atom or just want to experiment with adding a feature to the core of the system, you'll want to run Atom in Dev Mode with access to a local copy of the Atom source.

    Fork the atom/atom repository

    Follow the GitHub Help instructions on how to fork a repoopen in new window.

    Cloning and bootstrapping

    Once you've set up your fork of the atom/atom repository, you can clone it to your local machine:

    $ git clone git@github.com:<em>your-username</em>/atom.git
    +

    From there, you can navigate into the directory where you've cloned the Atom source code and run the bootstrap script to install all the required dependencies:

    Running in Development Mode

    Once you have a local copy of Atom cloned and bootstrapped, you can then run Atom in Development Mode. But first, if you cloned Atom to somewhere other than ~/github/atom%USERPROFILE%\github\atom you will need to set the ATOM_DEV_RESOURCE_PATH environment variable to point to the folder in which you cloned Atom. To run Atom in Dev Mode, use the --dev parameter from the terminal:

    $ atom --dev <em>path-to-open</em>
    +

    Note

    Note: If the atom command does not respond in the terminal, then try atom-dev or atom-beta. The suffix depends upon the particular source code that was cloned.

    There are a couple benefits of running Atom in Dev Mode:

    1. When the ATOM_DEV_RESOURCE_PATH environment variable is set correctly, Atom is run using the source code from your local atom/atom repository. This means that you don't have to run script/buildscript\build every time you change code. Just restart Atom 👍
    2. Packages that exist in ~/.atom/dev/packages%USERPROFILE%\.atom\dev\packages are loaded instead of packages of the same name normally loaded from other locations. This means that you can have development versions of packages you use loaded but easily go back to the stable versions by launching without Dev Mode.
    3. Packages that contain stylesheets, such as syntax themes, will have those stylesheets automatically reloaded by the dev-live-reloadopen in new window package. This does not live reload JavaScript or CoffeeScript files — you'll need to reload the window (window:reload) to see changes to those.

    Running Atom Core Tests Locally

    In order to run Atom Core tests from the terminal, first be certain to set the ATOM_DEV_RESOURCE_PATH environment variable as mentioned above and then:

    $ cd <em>path-to-your-local-atom-repo</em>
    +$ atom --test spec
    +

    Building

    In order to build Atom from source, you need to have a number of other requirements and take additional steps.

    Instructions
    script/build Options
    Troubleshooting

    Contributing to Official Atom Packages

    If you think you know which package is causing the issue you are reporting, feel free to open up the issue in that specific repository instead. When in doubt just open the issue on the atom/atomopen in new window repository but be aware that it may get closed and reopened in the proper package's repository.

    Hacking on Packages

    Cloning

    The first step is creating your own clone. For some packages, you may also need to install the requirements necessary for building Atom in order to run apm install.

    For example, if you want to make changes to the tree-view package, fork the repo on your github account, then clone it:

    $ git clone git@github.com:<em>your-username</em>/tree-view.git
    +

    Next install all the dependencies:

    $ cd tree-view
    +$ apm install
    +> Installing modules ✓
    +

    Now you can link it to development mode so when you run an Atom window with atom --dev, you will use your fork instead of the built in package:

    $ apm link -d
    +
    Running in Development Mode

    Editing a package in Atom is a bit of a circular experience: you're using Atom to modify itself. What happens if you temporarily break something? You don't want the version of Atom you're using to edit to become useless in the process. For this reason, you'll only want to load packages in development mode while you are working on them. You'll perform your editing in stable mode, only switching to development mode to test your changes.

    To open a development mode window, use the "Application: Open Dev" command. You can also run dev mode from the command line with atom --dev.

    To load your package in development mode, create a symlink to it in ~/.atom/dev/packages. This occurs automatically when you clone the package with apm develop. You can also run apm link --dev and apm unlink --dev from the package directory to create and remove dev-mode symlinks.

    Installing Dependencies

    You'll want to keep dependencies up to date by running apm update after pulling any upstream changes.

    Creating a Fork of a Core Package in atom/atom

    Several of Atom's core packages are maintained in the packages directory of the atom/atom repositoryopen in new window. If you would like to use one of these packages as a starting point for your own package, please follow the steps below.

    Tips

    Tip: In most cases, we recommend generating a brand new package or a brand new theme as the starting point for your creation. The guide below applies only to situations where you want to create a package that closely resembles a core Atom package.

    Creating Your New Package

    For the sake of this guide, let's assume that you want to start with the current code in the one-light-uiopen in new window package, make some customizations to it, and publish your new package under the name "one-light-ui-plus".

    1. Download the current contents of the atom/atom repository as a zip fileopen in new window

    2. Unzip the file to a temporary location (for example /tmp/atomC:\TEMP\atom)

    3. Copy the contents of the desired package into a working directory for your fork

      $ <span class='platform-mac platform-linux'>cp -R /tmp/atom/packages/one-light-ui ~/src/one-light-ui-plus</span><span class='platform-windows'>xcopy C:\TEMP\atom\packages\one-light-ui C:\src\one-light-ui-plus /E /H /K</span>
      +
    4. Create a local repository and commit the initial contents

      $ cd ~/src/one-light-ui-plus
      +$ git init
      +$ git commit -am "Import core Atom package"
      +
    5. Update the name property in package.json to give your package a unique name

    6. Make the other customizations that you have in mind

    7. Commit your changes

      $ git commit -am "Apply initial customizations"
      +
    8. Create a public repository on github.comopen in new window for your new package

    9. Follow the instructions in the github.com UI to push your code to your new online repository

    10. Follow the steps in the Publishing guide to publish your new package

    Merging Upstream Changes into Your Package

    The code in the original package will continue to evolve over time, either to fix bugs or to add new enhancements. You may want to incorporate some or all of those updates into your package. To do so, you can follow these steps for merging upstream changes into your package.

    Maintaining a Fork of a Core Package in atom/atom

    Originally, each of Atom's core packages resided in a separate repository. In 2018, in an effort to streamline the development of Atom by reducing overhead, the Atom team consolidated many core Atom packagesopen in new window into the atom/atom repositoryopen in new window. For example, the one-light-ui package was originally maintained in the atom/one-light-uiopen in new window repository, but it is now maintained in the packages/one-light-ui directory in the atom/atom repositoryopen in new window.

    If you forked one of the core packages before it was moved into the atom/atom repository, and you want to continue merging upstream changes into your fork, please follow the steps below.

    Step-by-step guide

    For the sake of this guide, let's assume that you forked the atom/one-light-uiopen in new window repository, renamed your fork to one-light-ui-plus, and made some customizations.

    Add atom/atom as a Remote

    Navigate to your local clone of your fork:

    $ cd path/to/your/fork
    +

    Add the atom/atom repositoryopen in new window as a git remote:

    $ git remote add upstream https://github.com/atom/atom.git
    +
    Get the Latest Changes for the Core Package

    Tip

    Tip: Follow these steps each time you want to merge upstream changes into your fork.

    Fetch the latest changes from the atom/atom repository:

    $ git fetch upstream
    +

    Identify recent changes to the core package. For example, if you're maintaining a fork of the one-light-ui package, then you'll want to identify recent changes in the packages/one-light-ui directory:

    $ git log upstream/master -- packages/one-light-ui
    +8ac9919a0 Bump up border size (Hugh Baht, 17 minutes ago)
    +3bf4d226e Remove obsolete build status link in one-light-ui README (Jason Rudolph, 3 days ago)
    +3edf64ad0 Merge pull request #42 from atom/sm-select-list (simurai, 2 weeks ago)
    +...
    +

    Look through the log and identify the commits that you want to merge into your fork.

    Merge Upstream Changes into Your Fork

    For each commit that you want to bring into your fork, use git format-patchopen in new window in conjunction with git amopen in new window. For example, to merge commit 8ac9919a0 into your fork:

    $ git format-patch -1 --stdout 8ac9919a0 | git am -p3
    +

    Repeat this step for each commit that you want to merge into your fork.

    Summary

    If you finished this chapter, you should be an Atom-hacking master. We've discussed how you should work with CoffeeScript, and how to put it to good use in creating packages. You should also be able to do this in your own created theme now.

    Even when something goes wrong, you should be able to debug this easily. But also fewer things should go wrong, because you are capable of writing great specs for Atom.

    In the next chapter, we’ll go into more of a deep dive on individual internal APIs and systems of Atom, even looking at some Atom source to see how things are really getting done.

    + + + diff --git a/docs/atom-archive/hacking-atom/sections/contributing-to-official-atom-packages.html b/docs/atom-archive/hacking-atom/sections/contributing-to-official-atom-packages.html new file mode 100644 index 0000000000..9d2c9735fb --- /dev/null +++ b/docs/atom-archive/hacking-atom/sections/contributing-to-official-atom-packages.html @@ -0,0 +1,228 @@ + + + + + + + + + + + + + + +

    About 1 min

    Contributing to Official Atom Packages

    If you think you know which package is causing the issue you are reporting, feel free to open up the issue in that specific repository instead. When in doubt just open the issue on the atom/atomopen in new window repository but be aware that it may get closed and reopened in the proper package's repository.

    Hacking on Packages

    Cloning

    The first step is creating your own clone. For some packages, you may also need to install the requirements necessary for building Atom in order to run apm install.

    For example, if you want to make changes to the tree-view package, fork the repo on your github account, then clone it:

    $ git clone git@github.com:<em>your-username</em>/tree-view.git
    +

    Next install all the dependencies:

    $ cd tree-view
    +$ apm install
    +> Installing modules ✓
    +

    Now you can link it to development mode so when you run an Atom window with atom --dev, you will use your fork instead of the built in package:

    $ apm link -d
    +
    Running in Development Mode

    Editing a package in Atom is a bit of a circular experience: you're using Atom to modify itself. What happens if you temporarily break something? You don't want the version of Atom you're using to edit to become useless in the process. For this reason, you'll only want to load packages in development mode while you are working on them. You'll perform your editing in stable mode, only switching to development mode to test your changes.

    To open a development mode window, use the "Application: Open Dev" command. You can also run dev mode from the command line with atom --dev.

    To load your package in development mode, create a symlink to it in ~/.atom/dev/packages. This occurs automatically when you clone the package with apm develop. You can also run apm link --dev and apm unlink --dev from the package directory to create and remove dev-mode symlinks.

    Installing Dependencies

    You'll want to keep dependencies up to date by running apm update after pulling any upstream changes.

    + + + diff --git a/docs/atom-archive/hacking-atom/sections/converting-from-textmate.html b/docs/atom-archive/hacking-atom/sections/converting-from-textmate.html new file mode 100644 index 0000000000..9aa02ce997 --- /dev/null +++ b/docs/atom-archive/hacking-atom/sections/converting-from-textmate.html @@ -0,0 +1,225 @@ + + + + + + + + + + + + + + +

    About 1 min

    Converting from TextMate

    It's possible that you have themes or grammars from TextMateopen in new window that you like and use and would like to convert to Atom. If so, you're in luck because there are tools to help with the conversion.

    Converting a TextMate Grammar Bundle

    Converting a TextMate bundle will allow you to use its editor preferences, snippets, and colorization inside Atom.

    Let's convert the TextMate bundle for the Ropen in new window programming language. You can find other existing TextMate bundles on GitHubopen in new window.

    You can convert the R bundle with the following command:

    $ apm init --package language-r --convert https://github.com/textmate/r.tmbundle
    +

    You can now change directory into language-r to see the converted bundle. Once you link your package with the apm link command, your new package is ready to use. Launch Atom and open a .r file in the editor to see it in action!

    Converting a TextMate Syntax Theme

    This section will go over how to convert a TextMateopen in new window theme to an Atom theme.

    Differences

    TextMate themes use plistopen in new window files while Atom themes use CSSopen in new window or Lessopen in new window to style the UI and syntax in the editor.

    The utility that converts the theme first parses the theme's plist file and then creates comparable CSS rules and properties that will style Atom similarly.

    Convert the Theme

    Download the theme you wish to convert, you can browse existing TextMate themes on the TextMate websiteopen in new window.

    Now, let's say you've downloaded the theme to ~/Downloads/MyTheme.tmTheme, you can convert the theme with the following command:

    $ apm init --theme my-theme --convert ~/Downloads/MyTheme.tmTheme
    +

    You can then change directory to my-theme to see the converted theme.

    Activate the Theme

    Once your theme is installed you can enable it by launching Atom and opening the Settings View with the Atom > PreferencesFile > PreferencesEdit > Preferences menu item. Then select the "Themes" tab on the left side navigation. Finally, choose "My Theme" from the "Syntax Theme" dropdown menu to enable your new theme.

    Your theme is now enabled, open an editor to see it in action!

    + + + diff --git a/docs/atom-archive/hacking-atom/sections/creating-a-fork-of-a-core-package-in-atom-atom.html b/docs/atom-archive/hacking-atom/sections/creating-a-fork-of-a-core-package-in-atom-atom.html new file mode 100644 index 0000000000..6dfca2768d --- /dev/null +++ b/docs/atom-archive/hacking-atom/sections/creating-a-fork-of-a-core-package-in-atom-atom.html @@ -0,0 +1,228 @@ + + + + + + + + + + + + + + +

    About 2 min

    Creating a Fork of a Core Package in atom/atom

    Several of Atom's core packages are maintained in the packages directory of the atom/atom repositoryopen in new window. If you would like to use one of these packages as a starting point for your own package, please follow the steps below.

    Tips

    Tip: In most cases, we recommend generating a brand new package or a brand new theme as the starting point for your creation. The guide below applies only to situations where you want to create a package that closely resembles a core Atom package.

    Creating Your New Package

    For the sake of this guide, let's assume that you want to start with the current code in the one-light-uiopen in new window package, make some customizations to it, and publish your new package under the name "one-light-ui-plus".

    1. Download the current contents of the atom/atom repository as a zip fileopen in new window

    2. Unzip the file to a temporary location (for example /tmp/atomC:\TEMP\atom)

    3. Copy the contents of the desired package into a working directory for your fork

      $ <span class='platform-mac platform-linux'>cp -R /tmp/atom/packages/one-light-ui ~/src/one-light-ui-plus</span><span class='platform-windows'>xcopy C:\TEMP\atom\packages\one-light-ui C:\src\one-light-ui-plus /E /H /K</span>
      +
    4. Create a local repository and commit the initial contents

      $ cd ~/src/one-light-ui-plus
      +$ git init
      +$ git commit -am "Import core Atom package"
      +
    5. Update the name property in package.json to give your package a unique name

    6. Make the other customizations that you have in mind

    7. Commit your changes

      $ git commit -am "Apply initial customizations"
      +
    8. Create a public repository on github.comopen in new window for your new package

    9. Follow the instructions in the github.com UI to push your code to your new online repository

    10. Follow the steps in the Publishing guide to publish your new package

    Merging Upstream Changes into Your Package

    The code in the original package will continue to evolve over time, either to fix bugs or to add new enhancements. You may want to incorporate some or all of those updates into your package. To do so, you can follow these steps for merging upstream changes into your package.

    + + + diff --git a/docs/atom-archive/hacking-atom/sections/creating-a-grammar.html b/docs/atom-archive/hacking-atom/sections/creating-a-grammar.html new file mode 100644 index 0000000000..5c722c4b2f --- /dev/null +++ b/docs/atom-archive/hacking-atom/sections/creating-a-grammar.html @@ -0,0 +1,341 @@ + + + + + + + + + + + + + + +

    About 7 min

    Creating a Grammar

    Atom's syntax highlighting and code folding system is powered by Tree-sitteropen in new window. Tree-sitter parsers create and maintain full syntax treesopen in new window representing your code.

    This syntax tree gives Atom a comprehensive understanding of the structure of your code, which has several benefits:

    1. Syntax highlighting will not break because of formatting changes.
    2. Code folding will work regardless of how your code is indented.
    3. Editor features can operate on the syntax tree. For instance, the Select Larger Syntax Node and Select Smaller Syntax Node allow you to select conceptually larger and smaller chunks of your code.
    4. Community packages can use the syntax tree to manipulate code intelligently.

    Tree-sitter grammars are relatively new. Many languages in Atom are still supported by TextMate grammars, though we intend to phase these out over time.

    If you're adding support for a new language, you're in the right place!

    Getting Started

    There are two components required to use Tree-sitter in Atom: a parser and a grammar file.

    The Parser

    Tree-sitter generates parsers based on context-free grammarsopen in new window that are typically written in JavaScript. The generated parsers are C libraries that can be used in other applications as well as Atom.

    They can also be developed and tested at the command line, separately from Atom. Tree-sitter has its own documentation pageopen in new window on how to create these parsers. The Tree-sitter GitHub organizationopen in new window also contains a lot of example parsers that you can learn from, each in its own repository.

    Once you have created a parser, you need to publish it to the NPM registryopen in new window to use it in Atom. To do this, make sure you have a name and version in your parser's package.json:

    {
    +  "name": "tree-sitter-mylanguage",
    +  "version": "0.0.1",
    +  // ...
    +}
    +

    then run the command npm publish.

    The Package

    Once you have a Tree-sitter parser that is available on npm, you can use it in your Atom package. Packages with grammars are, by convention, always named starting with language. You'll need a folder with a package.json, a grammars subdirectory, and a single json or cson file in the grammars directory, which can be named anything.

    language-mylanguage
    +├── LICENSE
    +├── README.md
    +├── grammars
    +│   └── mylanguage.cson
    +└── package.json
    +

    The Grammar File

    The mylanguage.cson file specifies how Atom should use the parser you created.

    Basic Fields

    It starts with some required fields:

    name: 'My Language'
    +scopeName: 'mylanguage'
    +type: 'tree-sitter'
    +parser: 'tree-sitter-mylanguage'
    +
    • scopeName - A unique, stable identifier for the language. Atom users will use this in configuration files if they want to specify custom configuration based on the language.
    • name - A human readable name for the language.
    • parser - The name of the parser node module that will be used for parsing. This string will be passed directly to require()open in new window in order to load the parser.
    • type - This should have the value tree-sitter to indicate to Atom that this is a Tree-sitter grammar and not a TextMate grammar.

    Language Recognition

    Next, the file should contain some fields that indicate to Atom when this language should be used. These fields are all optional.

    • fileTypes - An array of filename suffixes. The grammar will be used for files whose names end with one of these suffixes. Note that the suffix may be an entire filename.
    • firstLineRegex - A regex pattern that will be tested against the first line of the file. The grammar will be used if this regex matches.
    • contentRegex - A regex pattern that will be tested against the contents of the file in order to break ties in cases where multiple grammars matched the file using the above two criteria. If the contentRegex matches, this grammar will be preferred over another grammar with no contentRegex. If the contentRegex does not match, a grammar with no contentRegex will be preferred over this one.

    Syntax Highlighting

    The HTML classes that Atom uses for syntax highlighting do not correspond directly to nodes in the syntax tree. Instead, Tree-sitter grammar files specify scope mappings that specify which classes should be applied to which syntax nodes. The scopes object controls these scope mappings. Its keys are CSS selectors that select nodes in the syntax tree. Its values can be of several different types.

    Here is a simple example:

    scopes:
    +  'call_expression > identifier': 'entity.name.function'
    +

    This entry means that, in the syntax tree, any identifier node whose parent is a call_expression should be highlighted using three classes: syntax--entity, syntax--name, and syntax--function.

    Note that in this selector, we're using the immediate child combinatoropen in new window (>). Arbitrary descendant selectors without this combinator (for example 'call_expression identifier', which would match any identifier occurring anywhere within a call_expression) are currently not supported.

    Advanced Selectors

    The keys of the scopes object can also contain multiple CSS selectors, separated by commas, similar to CSS files. The triple-quote syntax in CSON makes it convenient to write keys like this on multiple lines:

    scopes:
    +  '''
    +  function_declaration > identifier,
    +  call_expression > identifier,
    +  call_expression > field_expression > field_identifier
    +  ''': 'entity.name.function'
    +

    You can use the :nth-child pseudo-classopen in new window to select nodes based on their order within their parent. For example, this example selects identifier nodes which are the fourth (zero-indexed) child of a singleton_method node.

    scopes:
    +  'singleton_method > identifier:nth-child(3)': 'entity.name.function'
    +

    Finally, you can use double-quoted strings in the selectors to select anonymous tokens in the syntax tree, like ( and :. See the Tree-sitter documentationopen in new window for more information about named vs anonymous tokens.

    scopes:
    +  '''
    +    "*",
    +    "/",
    +    "+",
    +    "-"
    +  ''': 'keyword.operator'
    +
    Text-based Mappings

    You can also apply different classes to a syntax node based on its text. Here are some examples:

    scopes:
    +
    +  # Apply the classes `syntax--builtin` and `syntax--variable` to all
    +  # `identifier` nodes whose text is `require`.
    +  'identifier': {exact: 'require', scopes: 'builtin.variable'},
    +
    +  # Apply the classes `syntax--type` and `syntax--integer` to all
    +  # `primitive_type` nodes whose text starts with `int` or `uint`.
    +  'primitive_type': {match: /^u?int/, scopes: 'type.integer'},
    +
    +  # Apply the classes `syntax--builtin`, `syntax--class`, and
    +  # `syntax--name` to `constant` nodes with the text `Array`,
    +  # `Hash` and `String`. For all other `constant` nodes, just
    +  # apply the classes `syntax--class` and `syntax--name`.
    +  'constant': [
    +    {match: '^(Array|Hash|String)$', scopes: 'builtin.class.name'},
    +    'class.name'
    +  ]
    +

    In total there are four types of values that can be associated with selectors in scopes:

    • Strings - Each class name in the dot-separated string will be prefixed with syntax-- and applied to the selected node.
    • Objects with the keys exact and scopes - If the node's text equals the exact string, the scopes string will be used as described above.
    • Objects with the keys match and scopes - If the node's text matches the match regex pattern, the scopes string will be used as described above.
    • Arrays - The elements of the array will be processed from beginning to end. The first element that matches the selected node will be used as describe above.
    Specificity

    If multiple selectors in the scopes object match a node, the node's classes will be decided based on the most specificopen in new window selector. Note that the exact and match rules do not affect specificity, so you may need to supply the same exact or match rules for multiple selectors to ensure that they take precedence over other selectors. You can use the same selector multiple times in a scope mapping, within different comma-separated keys:

    scopes:
    +  'call_expression > identifier': 'entity.name.function'
    +
    +  # If we did not include the second selector here, then this rule
    +  # would not apply to identifiers inside of call_expressions,
    +  # because the selector `call_expression > identifier` is more
    +  # specific than the selector `identifier`.
    +  'identifier, call_expression > identifier': [
    +    {exact: 'require', scopes: 'builtin.variable'},
    +    {match: '^[A-Z]', scopes: 'constructor'},
    +  ]
    +

    Language Injection

    Sometimes, a source file can contain code written in several different languages. Tree-sitter grammars support this situation using a two-part process called language injection. First, an 'outer' language must define an injection point - a set of syntax nodes whose text can be parsed using a different language, along with some logic for guessing the name of the other language that should be used. Second, an 'inner' language must define an injectionRegex - a regex pattern that will be tested against the language name provided by the injection point.

    For example, in JavaScript, tagged template literalsopen in new window sometimes contain code written in a different language, and the name of the language is often used in the 'tag' function, as shown in this example:

    // HTML in a template literal
    +const htmlContent = html`<div>Hello ${name}</div>`;
    +

    The tree-sitter-javascript parser parses this tagged template literal as a call_expression with two children: an identifier and a template_literal:

    (call_expression
    +  (identifier)
    +  (template_literal
    +    (interpolation
    +      (identifier))))
    +

    Here is an injection point that would allow syntax highlighting inside of template literals:

    atom.grammars.addInjectionPoint("source.js", {
    +	type: "call_expression",
    +
    +	language(callExpression) {
    +		const { firstChild } = callExpression;
    +		if (firstChild.type === "identifier") {
    +			return firstChild.text;
    +		}
    +	},
    +
    +	content(callExpression) {
    +		const { lastChild } = callExpression;
    +		if (lastChild.type === "template_string") {
    +			return lastChild;
    +		}
    +	},
    +});
    +

    The language callback would then be called with every call_expression node in the syntax tree. In the example above, it would retrieve the first child of the call_expression, which is an identifier with the name "html". The callback would then return the string "html".

    The content callback would then be called with the same call_expression node and return the template_string node within the call_expression node.

    In order to parse the HTML within the template string, the HTML grammar file would need to specify an injectionRegex:

    injectionRegex: 'html|HTML'
    +

    Code Folding

    The next field in the grammar file, folds, controls code folding. Its value is an array of fold pattern objects. Fold patterns are used to decide whether or not a syntax node can be folded, and if so, where the fold should start and end. Here are some example fold patterns:

    folds: [
    +
    +  # All `comment` nodes are foldable. By default, the fold starts at
    +  # the end of the node's first line, and ends at the beginning
    +  # of the node's last line.
    +  {
    +    type: 'comment'
    +  }
    +
    +  # `if_statement` nodes are foldable if they contain an anonymous
    +  # "then" token and either an `elif_clause` or `else_clause` node.
    +  # The fold starts at the end of the "then" token and ends at the
    +  # `elif_clause` or `else_clause`.
    +  {
    +    type: 'if_statement',
    +    start: {type: '"then"'}
    +    end: {type: ['elif_clause', 'else_clause']}
    +  }
    +
    +  # Any node that starts with an anonymous "(" token and ends with
    +  # an anonymous ")" token is foldable. The fold starts after the
    +  # "(" and ends before the ")".
    +  {
    +    start: {type: '"("', index: 0},
    +    end: {type: '")"', index: -1}
    +  }
    +]
    +

    Fold patterns can have one or more of the following fields:

    • type - A string or array of strings. In order to be foldable according to this pattern, a syntax node's type must match one of these strings.
    • start - An object that is used to identify a child node after which the fold should start. The object can have one or both of the following fields:
      • type - A string or array of strings. To start a fold, a child node's type must match one of these strings.
      • index - a number that's used to select a specific child according to its index. Negative values are interpreted as indices relative the last child, so that -1 means the last child.
    • end - An object that is used to identify a child node before which the fold should end. It has the same structure as the start object.

    Comments

    The last field in the grammar file, comments, controls the behavior of Atom's Editor: Toggle Line Comments command. Its value is an object with a start field and an optional end field. The start field is a string that should be prepended to or removed from lines in order to comment or un-comment them.

    In JavaScript, it looks like this:

    comments:
    +  start: '// '
    +

    The end field should be used for languages that only support block comments, not line comments. If present, it will be appended to or removed from the end of the last selected line in order to comment or un-comment the selection.

    In CSS, it would look like this:

    comments:
    +  start: '/* '
    +  end: ' */'
    +

    Example Packages

    More examples of all of these features can be found in the Tree-sitter grammars bundled with Atom:

    + + + diff --git a/docs/atom-archive/hacking-atom/sections/creating-a-legacy-textmate-grammar.html b/docs/atom-archive/hacking-atom/sections/creating-a-legacy-textmate-grammar.html new file mode 100644 index 0000000000..e870dbfe0f --- /dev/null +++ b/docs/atom-archive/hacking-atom/sections/creating-a-legacy-textmate-grammar.html @@ -0,0 +1,316 @@ + + + + + + + + + + + + + + +

    About 5 min

    Creating a Legacy TextMate Grammar

    Atom's syntax highlighting can be powered by two types of grammars. If you're adding support for a new language, the preferred way is to create a Tree-sitter grammar. Tree-sitter grammars have better performance and provide support for more editor features, such as the Select Larger Syntax Node command.

    This section describes the Atom's legacy support for TextMate grammars.

    TextMate grammars are supported by several popular text editors. They provide a set of regex (regular expression) patterns which are assigned scopes. These scopes are then turned into the CSS classes that you can target in syntax themes.

    Note

    Note: This tutorial is a work in progress.

    Getting Started

    TextMate Grammars depend heavily on regexes, and you should be comfortable with interpreting and writing regexes before continuing. Note that Atom uses the Oniguruma engine, which is very similar to the PCRE or Perl regex engines. Here are some resources to help you out:

    • https://www.regular-expressions.info/tutorial.html provides a comprehensive regex tutorial
    • https://www.rexegg.com/regex-quickstart.html contains a cheat sheet for various regex expressions
    • https://regex101.com/ or https://regexr.com/ allows live prototyping
    • https://github.com/kkos/oniguruma/blob/master/doc/RE the docs for the Oniguruma regex engine

    Grammar files are written in the CSONopen in new window or JSONopen in new window format. Whichever one you decide to use is up to you, but this tutorial will be written in CSON.

    Create the Package

    To get started, press Cmd+Shift+PCtrl+Shift+P and start typing "Generate Package" to generate a new grammar package. Select "Package Generator: Generate Package," and you'll be asked for the path where your package will be created. Let's call ours language-flight-manual.

    Tip

    Tip: Grammar packages should start with language-.

    The default package template creates a lot of folders that aren't needed for grammar packages. Go ahead and delete the keymaps, lib, menus, and styles folders. Furthermore, in package.json, remove the activationCommands section. Now create a new folder called grammars, and inside that a file called flight-manual.cson. This is the main file that we will be working with - start by populating it with a boilerplate templateopen in new window. Now let's go over what each key means.

    scopeName is the root scope of your package. This should generally describe what language your grammar package is highlighting; for example, language-javascript's scopeName is source.js and language-html's is text.html.basic. Name it source.flight-manual for now.

    name is the user-friendly name that is displayed in places like the status bar or the grammar selector. Again, this name should describe what the grammar package is highlighting. Rename it to Flight Manual.

    fileTypes is an array of filetypes that language-flight-manual should highlight. We're interested in highlighting the Flight Manual's Markdown files, so add the md extension to the list and remove the others.

    patterns contains the array of regex patterns that will determine how the file is tokenized.

    Adding Patterns

    To start, let's add a basic pattern to tokenize the words Flight Manual whenever they show up. Your regex should look like \bFlight Manual\b. Here's what your patterns block should look like:

    'patterns': [
    +  {
    +    'match': '\\bFlight Manual\\b'
    +    'name': 'entity.other.flight-manual'
    +  }
    +]
    +

    match is where your regex is contained, and name is the scope name that is to be applied to the entirety of the match. More information about scope names can be found in Section 12.4 of the TextMate Manualopen in new window.

    Tip

    Tip: All scopes should end with the portion of the root scopeName after the leading source or text. In our case, all scopes should end with flight-manual.

    Note

    Note: Astute readers may have noticed that the \b was changed to \\b with two backslashes and not one. This is because CSON processes the regex string before handing it to Oniguruma, so all backslashes need to be escaped twice.

    But what if we wanted to apply different scopes to Flight and Manual? This is possible by adding capture groups to the regex and then referencing those capture groups in a new capture property. For example:

    'match': '\\b(Flight) (Manual)\\b'
    +'name': 'entity.other.flight-manual'
    +'captures':
    +  '1':
    +    'name': 'keyword.other.flight.flight-manual'
    +  '2':
    +    'name': 'keyword.other.manual.flight-manual'
    +

    This will assign the scope keyword.other.flight.flight-manual to Flight, keyword.other.manual.flight-manual to Manual, and entity.other.flight-manual to the overarching Flight Manual.

    Begin/End Patterns

    Now let's say we want to tokenize the ::: note Note blocks that occur in Flight Manual files. Our previous two examples used match, but one limit of match is that it can only match single lines. ::: note Note blocks, on the other hand, can span multiple lines. For these cases, you can use the begin/end keys. Once the regex in the begin key is matched, tokenization will continue until the end pattern is reached.

    'begin': '({{)(#note)(}})'
    +'beginCaptures':
    +  '0': # The 0 capture contains the entire match
    +    'name': 'meta.block.start.flight-manual'
    +  '1':
    +    'name': 'punctuation.definition.block.flight-manual'
    +  '2':
    +    'name': 'keyword.note.flight-manual'
    +  '3':
    +    'name': 'punctuation.definition.block.flight-manual'
    +'end': '({{)(/note)(}})'
    +'endCaptures':
    +  '0':
    +    'name': 'meta.block.end.flight-manual'
    +  '1':
    +    'name': 'punctuation.definition.block.flight-manual'
    +  '2':
    +    'name': 'keyword.note.flight-manual'
    +  '3':
    +    'name': 'punctuation.definition.block.flight-manual'
    +'name': 'meta.block.note.flight-manual'
    +

    Tip

    Tip: Get into the habit of providing punctuation scopes early on. It's much less effort than having to go back and rewriting all your patterns to support punctuation scopes when your grammar starts to get a bit longer!

    Awesome, we have our first multiline pattern! However, if you've been following along and playing around in your own .md file, you may have noticed that Flight Manual doesn't receive any scopes inside a note block. A begin/end block is essentially a subgrammar of its own: once it starts matching, it will only match its own subpatterns until the end pattern is reached. Since we haven't defined any subpatterns, then clearly nothing will be matched inside of a note block. Let's fix that!

    'begin': '({{)(#note)(}})'
    +'beginCaptures':
    +  '0': # The 0 capture contains the entire match
    +    'name': 'meta.block.start.flight-manual'
    +  '1':
    +    'name': 'punctuation.definition.block.flight-manual'
    +  '2':
    +    'name': 'keyword.note.flight-manual'
    +  '3':
    +    'name': 'punctuation.definition.block.flight-manual'
    +'end': '({{)(/note)(}})'
    +'endCaptures':
    +  '0':
    +    'name': 'meta.block.end.flight-manual'
    +  '1':
    +    'name': 'punctuation.definition.block.flight-manual'
    +  '2':
    +    'name': 'keyword.note.flight-manual'
    +  '3':
    +    'name': 'punctuation.definition.block.flight-manual'
    +'name': 'meta.block.note.flight-manual'
    +'patterns': [
    +  {
    +    'match': '\\b(Flight) (Manual)\\b'
    +    'name': 'entity.other.flight-manual'
    +    'captures':
    +      '1':
    +        'name': 'keyword.other.flight.flight-manual'
    +      '2':
    +        'name': 'keyword.other.manual.flight-manual'
    +  }
    +]
    +

    There. With the patterns block, Flight Manual should now receive the proper scopes.

    Repositories and the Include keyword, or how to avoid duplication

    At this point, note blocks are looking pretty nice, as is the Flight Manual keyword, but the rest of the file is noticeably lacking any form of Markdown syntax highlighting. Is there a way to include the GitHub-Flavored Markdown grammar without copying and pasting everything over? This is where the include keyword comes in. include allows you to include other patterns, even from other grammars! language-gfm's scopeName is source.gfm, so let's include that. Our patterns block should now look like the following:

    'patterns': [
    +  {
    +    'include': 'source.gfm'
    +  }
    +  {
    +    # Flight Manual pattern
    +  }
    +  {
    +    # Note begin/end pattern
    +  }
    +]
    +

    However, including source.gfm has led to another problem: note blocks still don't have any Markdown highlighting! The quick fix would be to add the include pattern to the note's pattern block as well, but now we're duplicating two patterns. You can imagine that as this grammar grows it'll quickly become inefficient to keep copying each new global pattern over to the note pattern as well. Therefore, include helpfully recognizes the special $self scope. $self automatically includes all the top-level patterns of the current grammar. The note block can then be simplified to the following:

    'begin': '({{)(#note)(}})'
    +# beginCaptures
    +'end': '({{)(/note)(}})'
    +# endCaptures
    +'name': 'meta.block.note.flight-manual'
    +'patterns': [
    +  {
    +    'include': '$self'
    +  }
    +]
    +

    Where to Go from Here

    There are several good resources out there that help when writing a grammar. The following is a list of some particularly useful ones (some have been linked to in the sections above as well).

    + + + diff --git a/docs/atom-archive/hacking-atom/sections/creating-a-theme.html b/docs/atom-archive/hacking-atom/sections/creating-a-theme.html new file mode 100644 index 0000000000..949e4ea56e --- /dev/null +++ b/docs/atom-archive/hacking-atom/sections/creating-a-theme.html @@ -0,0 +1,234 @@ + + + + + + + + + + + + + + +

    About 5 min

    Creating a Theme

    Atom's interface is rendered using HTML, and it's styled via Lessopen in new window which is a superset of CSS. Don't worry if you haven't heard of Less before; it's just like CSS, but with a few handy extensions.

    Atom supports two types of themes: UI and Syntax. UI themes style elements such as the tree view, the tabs, drop-down lists, and the status bar. Syntax themes style the code, gutter and other elements inside the editor view.

    Theme boundary

    Themes can be installed and changed from the Settings View which you can open by selecting the Atom > PreferencesFile > PreferencesEdit > Preferences menu, and clicking the "Install" or "Themes" tab on the left hand navigation.

    Getting Started

    Themes are pretty straightforward but it's still helpful to be familiar with a few things before starting:

    • Less is a superset of CSS, but it has some really handy features like variables. If you aren't familiar with its syntax, take a few minutes to familiarize yourselfopen in new window.
    • You may also want to review the concept of a package.json (as covered in Atom package.json). This file is used to help distribute your theme to Atom users.
    • Your theme's package.json must contain a theme key with a value of ui or syntax for Atom to recognize and load it as a theme.
    • You can find existing themes to install or fork in the atom.io themes registryopen in new window.

    Creating a Syntax Theme

    Let's create your first theme.

    To get started, press Cmd+Shift+PCtrl+Shift+P and start typing "Generate Syntax Theme" to generate a new theme package. Select "Generate Syntax Theme," and you'll be asked for the path where your theme will be created. Let's call ours motif-syntax.

    Tip

    Tip: Syntax themes should end with -syntax and UI themes should end with -ui.

    Atom will display a new window, showing the motif-syntax theme, with a default set of folders and files created for us. If you open the Settings View with Cmd+,Ctrl+, and click the "Themes" tab on the left, you'll see the "Motif" theme listed in the "Syntax Theme" drop-down. Select it from the menu to activate it, now when you open an editor you should see your new motif-syntax theme in action.

    Open up styles/colors.less to change the various color variables which have already been defined. For example, turn @red into #f4c2c1.

    Then open styles/base.less and modify the various selectors that have already been defined. These selectors style different parts of code in the editor such as comments, strings and the line numbers in the gutter.

    As an example, let's make the .gutter background-color into @red.

    Reload Atom by pressing Alt+Cmd+Ctrl+LAlt+Ctrl+R to see the changes you made reflected in your Atom window. Pretty neat!

    Tip

    Tip: You can avoid reloading to see changes you make by opening an Atom window in Dev Mode. To open a Dev Mode Atom window run atom --dev . in the terminal, or use the View > Developer > Open in Dev Mode menu. When you edit your theme, changes will instantly be reflected!

    Note

    Note: It's advised to not specify a font-family in your syntax theme because it will override the Font Family field in Atom's settings. If you still like to recommend a font that goes well with your theme, we suggest you do so in your README.

    Creating a UI Theme

    To create a UI theme, do the following:

    1. Fork the ui-theme-templateopen in new window
    2. Clone the forked repository to the local filesystem
    3. Open a terminal in the forked theme's directory
    4. Open your new theme in a Dev Mode Atom window run atom --dev . in the terminal or use the View > Developer > Open in Dev Mode menu
    5. Change the name of the theme in the theme's package.json file
    6. Name your theme end with a -ui, for example super-white-ui
    7. Run apm link --dev to symlink your repository to ~/.atom/dev/packages
    8. Reload Atom using Alt+Cmd+Ctrl+LAlt+Ctrl+R
    9. Enable the theme via the "UI Theme" drop-down in the "Themes" tab of the Settings View
    10. Make changes! Since you opened the theme in a Dev Mode window, changes will be instantly reflected in the editor without having to reload.

    Tip

    Tip: Because we used apm link --dev in the above instructions, if you break anything you can always close Atom and launch Atom normally to force Atom to the default theme. This allows you to continue working on your theme even if something goes catastrophically wrong.

    Theme Variables

    UI themes must provide a ui-variables.less and Syntax themes a syntax-variables.less file. It contains predefined variables that packages use to make sure the look and feel matches.

    Here the variables with the default values:

    These default values will be used as a fallback in case a theme doesn't define its own variables.

    Use in Packages

    In any of your package's .less files, you can access the theme variables by importing the ui-variables or syntax-variables file from Atom.

    Your package should generally only specify structural styling, and these should come from the style guideopen in new window. Your package shouldn't specify colors, padding sizes, or anything in absolute pixels. You should instead use the theme variables. If you follow this guideline, your package will look good out of the box with any theme!

    Here's an example .less file that a package can define using theme variables:

    @import "ui-variables";
    +
    +.my-selector {
    +	background-color: @base-background-color;
    +	padding: @component-padding;
    +}
    +
    @import "syntax-variables";
    +
    +.my-selector {
    +	background-color: @syntax-background-color;
    +}
    +

    Development workflow

    There are a few tools to help make theme development faster and easier.

    Live Reload

    Reloading by pressing Alt+Cmd+Ctrl+LAlt+Ctrl+R after you make changes to your theme is less than ideal. Atom supports live updatingopen in new window of styles on Atom windows in Dev Mode.

    To launch a Dev Mode window:

    • Open your theme directory in a dev window by selecting the View > Developer > Open in Dev Mode menu item
    • Or launch Atom from the terminal with atom --dev

    If you'd like to reload all the styles at any time, you can use the shortcut Alt+Cmd+Ctrl+LAlt+Ctrl+R.

    Developer Tools

    Atom is based on the Chrome browser, and supports Chrome's Developer Tools. You can open them by selecting the View > Developer > Toggle Developer Tools menu, or by using the Alt+Cmd+ICtrl+Shift+I shortcut.

    The dev tools allow you to inspect elements and take a look at their CSS properties.

    Developer Tools

    Check out Google's extensive tutorialopen in new window for a short introduction.

    Atom Styleguide

    If you are creating an UI theme, you'll want a way to see how your theme changes affect all the components in the system. The Styleguideopen in new window is a page that renders every component Atom supports.

    To open the Styleguide, open the command palette with Cmd+Shift+PCtrl+Shift+P and search for "styleguide", or use the shortcut Cmd+Ctrl+Shift+GCtrl+Shift+G.

    Style Guide

    Side by side

    Sometimes when creating a theme (or package) things can go wrong and the editor becomes un-usable. E.g. if the text and background have the same color or something gets pushed out of sight. To avoid having to open Atom in "normal" mode to fix the issue, it's advised to open two Atom windows. One for making changes and one in Dev Mode to see the changes getting applied.

    Side by side screenshot

    Make changes on the left, see the changes getting applied in "Dev Mode" on the right.

    Now if you mess up something, only the window in "Dev Mode" will be affected and you can easily correct the mistake in your "normal" window.

    Publish your theme

    Once you're happy with your theme and would like to share it with other Atom users, it's time to publish it. 🎉

    Follow the steps on the Publishing page. The example used is for the Word Count package, but publishing a theme works exactly the same.

    + + + diff --git a/docs/atom-archive/hacking-atom/sections/cross-platform-compatibility.html b/docs/atom-archive/hacking-atom/sections/cross-platform-compatibility.html new file mode 100644 index 0000000000..f006e5d68f --- /dev/null +++ b/docs/atom-archive/hacking-atom/sections/cross-platform-compatibility.html @@ -0,0 +1,226 @@ + + + + + + + + + + + + + + +

    About 2 min

    Cross-Platform Compatibility

    Atom runs on a number of platforms and while Electron and Node take care of many of the details there are still some considerations to ensure your package works on other operating systems.

    File symlinks can be used on Windows by non-Administrators by specifying 'junction' as the type (this argument is ignored on macOS & Linux).

    Also consider:

    • Symlinks committed to Git will not checkout correctly on Windows - dynamically create what you need with fs.symlink instead
    • Symlinked directories are only available to Administrators on Windows - avoid a dependency on them

    Filenames

    • Reserved filenames on Windows are com1-com9, lpt1-lpt9, con, nul, aux and prn (regardless of extension, e.g. prn.txt is disallowed)
    • Reserved characters on Windows are ? \ / < > ? % | : " so avoid where possible
    • Names with spaces when passed to the command line;
      • Windows requires you surround the path with double quotes e.g. "c:\my test"
      • macOS and Linux require a backslash before each space e.g. /my\ test

    File paths

    • Windows uses \ although some tools and PowerShell allow / too
    • macOS and Linux use /

    You can dynamically find out what your platform uses with path.sep or better yet use the node path library functions such as join and normalize which automatically take care of this.

    Windows supports up to 250 characters for a path - avoid deeply nested directory structures

    Paths are not URLs

    URL parsing routines should not be used on file paths. While they initially look like a relative path it will fail in a number of scenarios on all platforms.

    • Various characters are misinterpreted, e.g. ? as query string, # as a fragment identifier
    • Windows drive specifiers are incorrectly parsed as a protocol

    If you need to use a path for a URL use the file: protocol with an absolute path instead to ensure drive letters and slashes are appropriately addressed, e.g. file:///c|/test/pic.png

    fs.stat on directories

    The fs.stat function does not return the size of the contents of a directory but rather the allocation size of the directory itself. This returns 0 on Windows and 1024 on macOS and so should not be relied upon.

    path.relative can't traverse drives

    • On a macOS or Linux system path.relative can be used to calculate a relative path to traverse between any two given paths.
    • On Windows this is not always possible as it can contain multiple absolute roots, e.g. c:\ and d:\

    Rapid file operations

    Creation and deletion operations may take a few milliseconds to complete. If you need to remove many files and folders consider RimRAFopen in new window which has built-in retry logic for this.

    Line endings

    • Windows uses CRLF
    • macOS and Linux use LF
    • Git on Windows often has autocrlf set which automatically converts between the two

    If you are writing specs that use text file fixtures consider that this will interfere with file lengths, hash codes and direct text comparisons. It will also change the Atom selection length by 1 character per line.

    If you have spec fixtures that are text files you may want to tell Git to force LF, CRLF or not convert them by specifying the paths in .gitattributes e.g.

    spec/fixtures/always-crlf.txt eol=crlf
    +spec/fixtures/always-lf.txt eol=lf
    +spec/fixtures/leave-as-is.txt -text
    +
    + + + diff --git a/docs/atom-archive/hacking-atom/sections/debugging.html b/docs/atom-archive/hacking-atom/sections/debugging.html new file mode 100644 index 0000000000..04011397cf --- /dev/null +++ b/docs/atom-archive/hacking-atom/sections/debugging.html @@ -0,0 +1,241 @@ + + + + + + + + + + + + + + +

    About 10 min

    Debugging

    Atom provides several tools to help you understand unexpected behavior and debug problems. This guide describes some of those tools and a few approaches to help you debug and provide more helpful information when submitting issuesopen in new window:

    Update to the Latest Version

    You might be running into an issue which was already fixed in a more recent version of Atom than the one you're using.

    If you're using a released version, check which version of Atom you're using:

    $ atom --version
    +> Atom    : 1.8.0
    +> Electron: 0.36.8
    +> Chrome  : 47.0.2526.110
    +> Node    : 5.1.1
    +

    Then check for the latest Stable versionopen in new window.

    If you're building Atom from source, pull down the latest version of master and re-buildopen in new window.

    Using Safe Mode

    A large part of Atom's functionality comes from packages you can install. Atom will also execute the code in your init script on startup. In some cases, these packages and the code in the init script might be causing unexpected behavior, problems, or performance issues.

    To determine if that is happening, start Atom from the terminal in safe mode:

    $ atom --safe
    +

    This starts Atom, but does not load packages from ~/.atom/packages or ~/.atom/dev/packages and disables loading of your init script. If you can no longer reproduce the problem in safe mode, it's likely it was caused by one of the packages or the init script.

    If removing or commenting out all content from the init script and starting Atom normally still produces the error, then try figuring out which package is causing trouble. Start Atom normally again and open the Settings View with Cmd+,Ctrl+,. Since the Settings View allows you to disable each installed package, you can disable packages one by one until you can no longer reproduce the issue. Restart Atom or reload Atom with Alt+Cmd+Ctrl+LCtrl+Shift+F5 after you disable each package to make sure it's completely gone.

    When you find the problematic package, you can disable or uninstall the package. We strongly recommend creating an issue on the package's GitHub repository.

    Clearing Saved State

    Atom saves a number of things about your environment when you exit in order to restore Atom to the same configuration when you next launch the program. In some cases the state that gets saved can be something undesirable that prevents Atom from working properly. In these cases, you may want to clear the state that Atom has saved.

    DANGER

    :rotatinglight: Danger: Clearing the saved state permanently destroys any state that Atom has saved _across all projects. This includes unsaved changes to files you may have been editing in all projects. This is a destructive action.

    Clearing the saved state can be done by opening a terminal and executing:

    $ atom --clear-window-state
    +

    Reset to Factory Defaults

    In some cases, you may want to reset Atom to "factory defaults", in other words clear all of your configuration and remove all packages. This can easily be done by opening a terminal and executing:

    Once that is complete, you can launch Atom as normal. Everything will be just as if you first installed Atom.

    Tip

    Tip: The command given above doesn't delete the old configuration, just puts it somewhere that Atom can't find it. If there are pieces of the old configuration you want to retrieve, you can find them in the ~/.atom-backup%USERPROFILE%\.atom-backup directory.

    Check for Linked Packages

    If you develop or contribute to Atom packages, there may be left-over packages linked to your ~/.atom/packages or ~/.atom/dev/packages directories. You can use the apm links command to list all linked packages:

    $ apm links
    +> /Users/octocat/.atom/dev/packages (0)
    +> └── (no links)
    +> /Users/octocat/.atom/packages (1)
    +> └── color-picker -> /Users/octocat/github/color-picker
    +

    You can remove links using the apm unlink command:

    $ apm unlink color-picker
    +> Unlinking /Users/octocat/.atom/packages/color-picker ✓
    +

    See apm links --help and apm unlink --help for more information on these commands.

    Tip

    Tip: You can also use apm unlink --all to easily unlink all packages and themes.

    Check for Incompatible Packages

    If you have packages installed that use native Node modules, when you upgrade to a new version of Atom, they might need to be rebuilt. Atom detects this and through the incompatible-packages packageopen in new window displays an indicator in the status bar when this happens.

    Incompatible Packages Status Bar Indicator

    If you see this indicator, click it and follow the instructions.

    Check Atom and Package Settings

    In some cases, unexpected behavior might be caused by settings in Atom or in one of the packages.

    Open Atom's Settings Viewopen in new window with Cmd+,Ctrl+,, the Atom > PreferencesFile > PreferencesEdit > Preferences menu option, or the "Settings View: Open" command from the Command Paletteopen in new window.

    Settings View

    Check Atom's settings in the Settings View, there's a description of most configuration options in the Basic Customization section. For example, if you want Atom to hide the invisible symbols representing whitespace characters, disable the "Show Invisibles" option.

    Some of these options are also available on a per-language basis which means that they may be different for specific languages, for example JavaScript or Python. To check the per-language settings, open the settings for the language package under the Packages tab in the Settings View, for example the language-javascript or language-python package.

    Since Atom ships with a set of packages and you can also install additional packages yourself, check the list of packages and their settings. For instance, if you'd like to get rid of the vertical line in the middle of the editor, disable the Wrap Guide packageopen in new window. And if you don't like it when Atom strips trailing whitespace or ensures that there's a single trailing newline in the file, you can configure that in the whitespace package'sopen in new window settings.

    Package Settings

    Check Your Configuration

    You might have defined some custom styles, keymaps or snippets in one of your configuration files. In some situations, these personal hacks might be causing the unexpected behavior you're observing so try clearing those files and restarting Atom.

    Check Your Keybindings

    If a command is not executing when you press a key combination or the wrong command is executing, there might be an issue with the keybinding for that combination. Atom ships with the Keybinding Resolveropen in new window, a neat package which helps you understand what key Atom saw you press and the command that was triggered because of it.

    Show the keybinding resolver with Cmd+.Ctrl+. or with "Keybinding Resolver: Show" from the Command palette. With the Keybinding Resolver shown, press a key combination:

    Keybinding Resolver

    The Keybinding Resolver shows you a list of keybindings that exist for the key combination, where each item in the list has the following:

    • the command for the keybinding
    • the CSS selector used to define the context in which the keybinding is valid
    • the file in which the keybinding is defined

    The keybindings are listed in two colors. All the keybindings that are matched but not executed are shown in gray. The one that is executed, if any, is shown in green. If the command you wanted to trigger isn't listed, then a keybinding for that command hasn't been loaded.

    If multiple keybindings are matched, Atom determines which keybinding will be executed based on the specificity of the selectors and the order in which they were loaded. If the command you wanted to trigger is listed in the Keybinding Resolver, but wasn't the one that was executed, this is normally explained by one of two causes:

    • The key combination was not used in the context defined by the keybinding's selector

      For example, you can't trigger the keybinding for the tree-view:add-file command if the Tree View is not focused.

    • There is another keybinding that took precedence

      This often happens when you install a package which defines keybindings that conflict with existing keybindings. If the package's keybindings have selectors with higher specificity or were loaded later, they'll have priority over existing ones.

    Atom loads core Atom keybindings and package keybindings first, and user-defined keybindings last. Since user-defined keybindings are loaded last, you can use your keymap.cson file to tweak the keybindings and sort out problems like these. See the Keymaps in Depth section for more information.

    If you notice that a package's keybindings are taking precedence over core Atom keybindings, it might be a good idea to report the issue on that package's GitHub repository. You can contact atom maintainers on Atom's github discussionsopen in new window

    Check Font Rendering Issues

    You can determine which fonts are being used to render a specific piece of text by using the Developer Tools. To open the Developer Tools press Alt+Cmd+ICtrl+Shift+I. Once the Developer Tools are open, click the "Elements" tab. Use the standard tools for finding the elementopen in new window containing the text you want to check. Once you have selected the element, you can click the "Computed" tab in the styles pane and scroll to the bottom. The list of fonts being used will be shown there:

    Fonts In Use

    Check for Errors in the Developer Tools

    When an unexpected error occurs in Atom, you will normally see a red notification which provides details about the error and allows you to create an issue on the right repository:

    Exception Notification

    Not all errors are logged with a notification so if you suspect you're experiencing an error but there's no notification, you can also look for errors in the developer tools Console tab. To access the Console tab, press Alt-Cmd-ICtrl-Shift-I to open developer tools and then click the Console tab:

    DevTools Error

    If there are multiple errors, you can scroll down to the bottom of the panel to see the most recent error. Or while reproducing an error, you can right click in the Console tab panel, select Clear console to remove all Console output, and then reproduce the error to see what errors are logged to the Console tab.

    Note

    Note: When running in Dev Mode, the developer tools are automatically shown with the error logged in the Console tab.

    Find Crash Logs

    Diagnose Startup Performance

    If Atom is taking a long time to start, you can use the Timecop packageopen in new window to get insight into where Atom spends time while loading.

    Timecop

    Timecop displays the following information:

    • Atom startup times
    • File compilation times
    • Package loading and activation times
    • Theme loading and activation times

    If a specific package has high load or activation times, you might consider reporting an Issue to the maintainers. You can also disable the package to potentially improve future startup times.

    Diagnose Runtime Performance

    If you're experiencing performance problems in a particular situation, your Issue reportsopen in new window will be more valuable if you include a saved profile from Chrome's CPU profiler that gives some insight into what is slow.

    To run a profile, open the Developer Tools with Alt+Cmd+ICtrl+Shift+I. From there:

    1. Click the Profiles tab
    2. Select "Collect JavaScript CPU Profile"
    3. Click "Start"

    DevTools Profiler

    Once that is done, then perform the slow action to capture a recording. When finished, click "Stop". Switch to the "Chart" view, and a graph of the recorded actions will appear. You can save and post the profile data by clicking "Save" next to the profile's name in the left panel.

    DevTools Profiler

    To learn more, check out the Chrome documentation on CPU profilingopen in new window.

    Profiling Startup Performance

    If the time for loading the window looks high, you can create a CPU profile for that period using the --profile-startup command line flag when starting Atom:

    $ atom --profile-startup .
    +

    This will automatically capture a CPU profile as Atom is loading and open the Developer Tools once Atom loads. From there:

    1. Click the Profiles tab in the Developer Tools
    2. Select the "startup" profile
    3. Click the "Save" link for the startup profile

    You can then include the startup profile in any Issue you report.

    Check Your Build Tools

    If you are having issues installing a package using apm install, this could be because the package has dependencies on libraries that contain native code. This means you will need to have a C++ compiler and Python installed to be able to install it. You can run apm install --check to see if the Atom package manager can build native code on your machine.

    Check out the pre-requisites in the build instructionsopen in new window for your platform for more details.

    Check if your GPU is causing the problem

    If you encounter flickering or other rendering issues, you can stop Atom from using your Graphics Processing Unit (GPU) with the --disable-gpu Chromium flag to see if the fault lies with your GPU:

    $ atom --disable-gpu
    +

    Chromium (and thus Atom) normally uses the GPU to accelerate drawing parts of the interface. --disable-gpu tells Atom to not even attempt to do this, and just use the CPU for rendering everything. This means that the parts of the interface that would normally be accelerated using the GPU will instead take slightly longer and render on the CPU. This likely won't make a noticeable difference, but does slightly increase the battery usage as the CPU has to work harder to do the things the GPU is optimized for.

    Two other Chromium flags that are useful for debugging are --enable-gpu-rasterization and --force-gpu-rasterization:

    $ atom --enable-gpu-rasterization --force-gpu-rasterization
    +

    --enable-gpu-rasterization allows other commands to determine how a layer tile (graphics) should be drawn and --force-gpu-rasterization determines that the Skia GPU backend should be used for drawing layer tiles (only valid with GPU accelerated compositing).

    Be sure to use Chromium flags at the end of the terminal call if you want to use other Atom flags as they will not be executed after the Chromium flags e.g.:

    $ atom --safe --enable-gpu-rasterization --force-gpu-rasterization
    +
    + + + diff --git a/docs/atom-archive/hacking-atom/sections/hacking-on-atom-core.html b/docs/atom-archive/hacking-atom/sections/hacking-on-atom-core.html new file mode 100644 index 0000000000..7a6b1f7690 --- /dev/null +++ b/docs/atom-archive/hacking-atom/sections/hacking-on-atom-core.html @@ -0,0 +1,227 @@ + + + + + + + + + + + + + + +

    About 8 min

    Hacking on Atom Core

    If you're hitting a bug in Atom or just want to experiment with adding a feature to the core of the system, you'll want to run Atom in Dev Mode with access to a local copy of the Atom source.

    Fork the atom/atom repository

    Follow the GitHub Help instructions on how to fork a repoopen in new window.

    Cloning and bootstrapping

    Once you've set up your fork of the atom/atom repository, you can clone it to your local machine:

    $ git clone git@github.com:<em>your-username</em>/atom.git
    +

    From there, you can navigate into the directory where you've cloned the Atom source code and run the bootstrap script to install all the required dependencies:

    Running in Development Mode

    Once you have a local copy of Atom cloned and bootstrapped, you can then run Atom in Development Mode. But first, if you cloned Atom to somewhere other than ~/github/atom%USERPROFILE%\github\atom you will need to set the ATOM_DEV_RESOURCE_PATH environment variable to point to the folder in which you cloned Atom. To run Atom in Dev Mode, use the --dev parameter from the terminal:

    $ atom --dev <em>path-to-open</em>
    +

    Note

    Note: If the atom command does not respond in the terminal, then try atom-dev or atom-beta. The suffix depends upon the particular source code that was cloned.

    There are a couple benefits of running Atom in Dev Mode:

    1. When the ATOM_DEV_RESOURCE_PATH environment variable is set correctly, Atom is run using the source code from your local atom/atom repository. This means that you don't have to run script/buildscript\build every time you change code. Just restart Atom 👍
    2. Packages that exist in ~/.atom/dev/packages%USERPROFILE%\.atom\dev\packages are loaded instead of packages of the same name normally loaded from other locations. This means that you can have development versions of packages you use loaded but easily go back to the stable versions by launching without Dev Mode.
    3. Packages that contain stylesheets, such as syntax themes, will have those stylesheets automatically reloaded by the dev-live-reloadopen in new window package. This does not live reload JavaScript or CoffeeScript files — you'll need to reload the window (window:reload) to see changes to those.

    Running Atom Core Tests Locally

    In order to run Atom Core tests from the terminal, first be certain to set the ATOM_DEV_RESOURCE_PATH environment variable as mentioned above and then:

    $ cd <em>path-to-your-local-atom-repo</em>
    +$ atom --test spec
    +

    Building

    In order to build Atom from source, you need to have a number of other requirements and take additional steps.

    Instructions
    script/build Options
    Troubleshooting
    + + + diff --git a/docs/atom-archive/hacking-atom/sections/handling-uris.html b/docs/atom-archive/hacking-atom/sections/handling-uris.html new file mode 100644 index 0000000000..0727ea9e63 --- /dev/null +++ b/docs/atom-archive/hacking-atom/sections/handling-uris.html @@ -0,0 +1,253 @@ + + + + + + + + + + + + + + +

    About 2 min

    Handling URIs

    Beginning in Atom 1.23, packages have the ability to handle special URIs triggered from the system; for example, a package named my-package can register itself to handle any URI starting with atom://my-package/.

    WARNING

    Warning: Handling URIs triggered from other applications, like a web browser, is a powerful tool, but also one that can be jarring. You should shape your package's user experience to handle this well. In general, you should avoid taking direct action on behalf of a user. For example, a URI handler that immediately installs a package is too invasive, but a URI handler that shows the package's pane in the settings view is useful. A URI handler that begins to clone a repo is overly aggressive, but a URI handler that prompts the user to clone a repo is okay.

    Any package with a URI handler that we feel violates this guideline is subject to removal from the Atom package registry at our discretion.

    Modifying your package.json

    The first step to handling URIs from your package is to modify its package.json file. You should add a new key called uriHandler, and its value should be an object.

    The uriHandler object must contain a key called method with a string value that tells Atom which method in your package to call when a URI needs to be handled. The object can optionally include a key called deferActivation which can be set to the boolean false to prevent Atom from deferring activation of your package — see more below.

    For example, if we want our package my-package to handle URIs with a method on our package's main module called handleURI, we could add the following to our package.json:

    "uriHandler": {
    +  "method": "handleURI"
    +}
    +

    Modifying your Main Module

    Now that we've told Atom that we want our package to handle URIs beginning with atom://my-package/ via our handleURI method, we need to actually write this method. Atom passes two arguments to your URI handler method; the first one is the fully-parsed URI plus query string, parsed with Node's url.parse(uri, true)open in new window. The second argument is the raw, string URI; this is normally not needed since the first argument gives you structured information about the URI.

    Here's a sample package, written in JavaScript, that handles URIs with the package.json configuration we saw above.

    export default {
    +	activate() {
    +		// normal activation code here
    +	},
    +
    +	handleURI(parsedUri) {
    +		console.log(parsedUri);
    +	},
    +};
    +

    When Atom handles, for example, the URI atom://my-package/my/test/url?value=42&other=false, the package would log out something like the following:

    {
    +  protocol: 'atom:',
    +  slashes: true,
    +  auth: null,
    +  host: 'my-package',
    +  port: null,
    +  hostname: 'my-package',
    +  hash: null,
    +  search: '?value=true&other=false',
    +  query: { value: '42', other: 'false' },
    +  pathname: '/my/test/url',
    +  path: '/my/test/url?value=true&other=false',
    +  href: 'atom://my-package/my/test/url?value=true&other=false'
    +}
    +

    Notice that the query string arguments are available in the query property, but are strings — you'll have to convert to other native types yourself.

    Controlling Activation Deferral

    For performance reasons, adding a uriHandler entry to your package's package.json will enable deferred activation. This means that Atom will not activate your package until it has a URI for it to handle — it will then activate your package and then immediately call the URI handler method. If you want to disable the deferred activation, ensuring your package is activated upon startup, you can add "deferActivation": false to the URI handler config. For example,

    "uriHandler": {
    +  "method": "handleURI",
    +  "deferActivation": false
    +}
    +

    Before doing this, make sure your package actually needs to be activated immediately — disabling deferred activation means Atom takes longer to start since it has to activate all packages without deferred activation.

    Linux Support

    Because URI handling is different across operating systems and distributions, there is no built-in URI handler support for Atom on Linux. If you want to configure URI handling on your system yourself, then you should configure atom: protocol URI's to trigger atom with the --uri-handler flag; for example, the URI atom://test/uri should launch Atom via atom --uri-handler atom://test/uri.

    Core URIs

    Atom provides a core URI to handle opening files with the syntax atom://core/open/file?filename=<filepath>&line=<line>&column=<col>

    + + + diff --git a/docs/atom-archive/hacking-atom/sections/iconography.html b/docs/atom-archive/hacking-atom/sections/iconography.html new file mode 100644 index 0000000000..d931e92b7f --- /dev/null +++ b/docs/atom-archive/hacking-atom/sections/iconography.html @@ -0,0 +1,224 @@ + + + + + + + + + + + + + + +

    Less than 1 minute

    Iconography

    Atom comes bundled with the Octicons 4.4.0open in new window icon set. Use them to add icons to your packages.

    NOTE: Some older icons from version 2.1.2 are still kept for backwards compatibility.

    Overview

    In the Styleguide under the "Icons" section you'll find all the Octicons that are available.

    Octicons in the Styleguide

    Usage

    Octicons can be added with simple CSS classes in your markup. Prefix the icon names with icon icon-.

    As an example, to add a monitor icon (device-desktop), use the icon icon-device-desktop classes:

    <span class="icon icon-device-desktop"></span>
    +

    Size

    Octicons look best with a font-size of 16px. It's already used as the default, so you don't need to worry about it. In case you prefer a different icon size, try to use multiples of 16 (32px, 48px etc.) for the sharpest result. Sizes in between are ok too, but might look a bit blurry for icons with straight lines.

    Usability

    Although icons can make your UI visually appealing, when used without a text label, it can be hard to guess its meaning. In cases where space for a text label is insufficient, consider adding a tooltipopen in new window that appears on hover. Or a more subtle title="label" attribute would help as well.

    + + + diff --git a/docs/atom-archive/hacking-atom/sections/maintaining-a-fork-of-a-core-package-in-atom-atom.html b/docs/atom-archive/hacking-atom/sections/maintaining-a-fork-of-a-core-package-in-atom-atom.html new file mode 100644 index 0000000000..2ee546cfd5 --- /dev/null +++ b/docs/atom-archive/hacking-atom/sections/maintaining-a-fork-of-a-core-package-in-atom-atom.html @@ -0,0 +1,232 @@ + + + + + + + + + + + + + + +

    About 2 min

    Maintaining a Fork of a Core Package in atom/atom

    Originally, each of Atom's core packages resided in a separate repository. In 2018, in an effort to streamline the development of Atom by reducing overhead, the Atom team consolidated many core Atom packagesopen in new window into the atom/atom repositoryopen in new window. For example, the one-light-ui package was originally maintained in the atom/one-light-uiopen in new window repository, but it is now maintained in the packages/one-light-ui directory in the atom/atom repositoryopen in new window.

    If you forked one of the core packages before it was moved into the atom/atom repository, and you want to continue merging upstream changes into your fork, please follow the steps below.

    Step-by-step guide

    For the sake of this guide, let's assume that you forked the atom/one-light-uiopen in new window repository, renamed your fork to one-light-ui-plus, and made some customizations.

    Add atom/atom as a Remote

    Navigate to your local clone of your fork:

    $ cd path/to/your/fork
    +

    Add the atom/atom repositoryopen in new window as a git remote:

    $ git remote add upstream https://github.com/atom/atom.git
    +
    Get the Latest Changes for the Core Package

    Tip

    Tip: Follow these steps each time you want to merge upstream changes into your fork.

    Fetch the latest changes from the atom/atom repository:

    $ git fetch upstream
    +

    Identify recent changes to the core package. For example, if you're maintaining a fork of the one-light-ui package, then you'll want to identify recent changes in the packages/one-light-ui directory:

    $ git log upstream/master -- packages/one-light-ui
    +8ac9919a0 Bump up border size (Hugh Baht, 17 minutes ago)
    +3bf4d226e Remove obsolete build status link in one-light-ui README (Jason Rudolph, 3 days ago)
    +3edf64ad0 Merge pull request #42 from atom/sm-select-list (simurai, 2 weeks ago)
    +...
    +

    Look through the log and identify the commits that you want to merge into your fork.

    Merge Upstream Changes into Your Fork

    For each commit that you want to bring into your fork, use git format-patchopen in new window in conjunction with git amopen in new window. For example, to merge commit 8ac9919a0 into your fork:

    $ git format-patch -1 --stdout 8ac9919a0 | git am -p3
    +

    Repeat this step for each commit that you want to merge into your fork.

    + + + diff --git a/docs/atom-archive/hacking-atom/sections/package-active-editor-info.html b/docs/atom-archive/hacking-atom/sections/package-active-editor-info.html new file mode 100644 index 0000000000..52a131e8bd --- /dev/null +++ b/docs/atom-archive/hacking-atom/sections/package-active-editor-info.html @@ -0,0 +1,324 @@ + + + + + + + + + + + + + + +

    About 4 min

    Package: Active Editor Info

    We saw in our Word Count package how we could show information in a modal panel. However, panels aren't the only way to extend Atom's UI—you can also add items to the workspace. These items can be dragged to new locations (for example, one of the docks on the edges of the window), and Atom will restore them the next time you open the project. This system is used by Atom's tree view, as well as by third party packages like Nuclideopen in new window for its console, debugger, outline view, and diagnostics (linter results).

    For this package, we'll define a workspace item that tells us some information about our active text editor. The final package can be viewed at https://github.com/atom/active-editor-info.

    Create the Package

    To begin, press Cmd+Shift+PCtrl+Shift+P to bring up the Command Paletteopen in new window. Type "generate package" and select the "Package Generator: Generate Package" command, just as we did in the section on package generation. Enter active-editor-info as the name of the package.

    Add an Opener

    Now let's edit the package files to show our view in a workspace item instead of a modal panel. The way we do this is by registering an opener with Atom. Openers are just functions that accept a URI and return a view (if it's a URI that the opener knows about). When you call atom.workspace.open(), Atom will go through all of its openers until it finds one that can handle the URI you passed.

    Let's open lib/active-editor-info.js and edit our activate() method to register an opener:

    "use babel";
    +
    +import ActiveEditorInfoView from "./active-editor-info-view";
    +import { CompositeDisposable, Disposable } from "atom";
    +
    +export default {
    +	subscriptions: null,
    +
    +	activate(state) {
    +		this.subscriptions = new CompositeDisposable(
    +			// Add an opener for our view.
    +			atom.workspace.addOpener((uri) => {
    +				if (uri === "atom://active-editor-info") {
    +					return new ActiveEditorInfoView();
    +				}
    +			}),
    +
    +			// Register command that toggles this view
    +			atom.commands.add("atom-workspace", {
    +				"active-editor-info:toggle": () => this.toggle(),
    +			}),
    +
    +			// Destroy any ActiveEditorInfoViews when the package is deactivated.
    +			new Disposable(() => {
    +				atom.workspace.getPaneItems().forEach((item) => {
    +					if (item instanceof ActiveEditorInfoView) {
    +						item.destroy();
    +					}
    +				});
    +			})
    +		);
    +	},
    +
    +	deactivate() {
    +		this.subscriptions.dispose();
    +	},
    +
    +	toggle() {
    +		console.log("Toggle it!");
    +	},
    +};
    +

    You'll notice we also removed the activeEditorInfoView property and the serialize() method. That's because, with workspace items, it's possible to have more than one instance of a given view. Since each instance can have its own state, each should do its own serialization instead of relying on a package-level serialize() method. We'll come back to that later.

    You probably also noticed that our toggle() implementation just logs the text "Toggle it!" to the console. Let's make it actually toggle our view:

      toggle() {
    +    atom.workspace.toggle('atom://active-editor-info');
    +  }
    +

    Updating the View

    Atom uses the same view abstractions everywhere, so we can almost use the generated ActiveEditorInfoView class as-is. We just need to add two small methods:

      getTitle() {
    +    // Used by Atom for tab text
    +    return 'Active Editor Info';
    +  }
    +
    +  getURI() {
    +    // Used by Atom to identify the view when toggling.
    +    return 'atom://active-editor-info';
    +  }
    +

    Now reload the window and run the "Active Editor Info: Toggle" command from the command palette! Our view will appear in a new tab in the center of the workspace. If you want, you can drag it into one of the docks. Toggling it again will then hide that dock. If you close the tab and run the toggle command again, it will appear in the last place you had it.

    Note

    We've repeated the same URI three times now. That's okay, but it's probably a good idea to define the URL in one place and then import it from that module wherever you need it.

    Constraining Our Item's Locations

    The purpose of our view is to show information about the active text editor, so it doesn't really make sense to show our item in the center of the workspace (where the text editor will be). Let's add some methods to our view class to influence where its opened:

      getDefaultLocation() {
    +    // This location will be used if the user hasn't overridden it by dragging the item elsewhere.
    +    // Valid values are "left", "right", "bottom", and "center" (the default).
    +    return 'right';
    +  }
    +
    +  getAllowedLocations() {
    +    // The locations into which the item can be moved.
    +    return ['left', 'right', 'bottom'];
    +  }
    +

    Now our item will appear in the right dock initially and users will only be able to drag it to one of the other docks.

    Show Active Editor Info

    Now that we have our view all wired up, let's update it to show some information about the active text editor. Add this to the constructor:

    this.subscriptions = atom.workspace
    +	.getCenter()
    +	.observeActivePaneItem((item) => {
    +		if (!atom.workspace.isTextEditor(item)) {
    +			message.innerText = "Open a file to see important information about it.";
    +			return;
    +		}
    +		message.innerHTML = `
    +    <h2>${item.getFileName() || "untitled"}</h2>
    +    <ul>
    +      <li><b>Soft Wrap:</b> ${item.softWrapped}</li>
    +      <li><b>Tab Length:</b> ${item.getTabLength()}</li>
    +      <li><b>Encoding:</b> ${item.getEncoding()}</li>
    +      <li><b>Line Count:</b> ${item.getLineCount()}</li>
    +    </ul>
    +  `;
    +	});
    +

    Now whenever you open a text editor in the center, the view will update with some information about it.

    WARNING

    We use a template string here because it's simple and we have a lot of control over what's going into it, but this could easily result in the insertion of unwanted HTML if you're not careful. Sanitize your input and use the DOM API or a templating system when doing this for real.

    Also, don't forget to clean up the subscription in the destroy() method:

    destroy() {
    +  this.element.remove();
    +  this.subscriptions.dispose();
    +}
    +

    Serialization

    If you were to reload Atom now, you'd see that our item had disappeared. That's because we haven't told Atom how to serialize it yet. Let's do that now.

    The first step is to implement a serialize() method on our ActiveEditorInfoView class. Atom will call the serialize() method on every item in the workspace periodically to save its state.

      serialize() {
    +    return {
    +      // This is used to look up the deserializer function. It can be any string, but it needs to be
    +      // unique across all packages!
    +      deserializer: 'active-editor-info/ActiveEditorInfoView'
    +    };
    +  }
    +

    Note

    All of our view's state is derived from the active text editor so we only need the deserializer field. If we had other state that we wanted to preserve across reloads, we would just add things to the object we're returning. Just make sure that they're JSON serializable!

    Next we need to register a deserializer function that Atom can use to recreate the real object when it starts up. The best way to do that is to add a "deserializers" object to our package.json file:

    {
    +  "name": "active-editor-info",
    +  ...
    +  "deserializers": {
    +    "active-editor-info/ActiveEditorInfoView": "deserializeActiveEditorInfoView"
    +  }
    +}
    +

    Notice that the key ("active-editor-info/ActiveEditorInfoView") matches the string we used in our serialize() method above. The value ("deserializeActiveEditorInfoView") refers to a function in our main module, which we still need to add. Go back to active-editor-info.js and do that now:

      deserializeActiveEditorInfoView(serialized) {
    +    return new ActiveEditorInfoView();
    +  }
    +

    The value returned from our serialize() method will be passed to this function. Since our serialized object didn't include any state, we can just return a new ActiveEditorInfoView instance.

    Reload Atom and toggle the view with the "Active Editor Info: Toggle" command. Then reload Atom again. Your view should be just where you left it!

    Summary

    In this section, we've made a toggleable workspace item whose placement can be controlled by the user. This could be helpful when creating all sorts of visual tools for working with code!

    + + + diff --git a/docs/atom-archive/hacking-atom/sections/package-modifying-text.html b/docs/atom-archive/hacking-atom/sections/package-modifying-text.html new file mode 100644 index 0000000000..6038a48cac --- /dev/null +++ b/docs/atom-archive/hacking-atom/sections/package-modifying-text.html @@ -0,0 +1,285 @@ + + + + + + + + + + + + + + +

    About 4 min

    Package: Modifying Text

    Now that we have our first package written, let's go through examples of other types of packages we can make. This section will guide you though creating a simple command that replaces the selected text with ascii artopen in new window. When you run our new command with the word "cool" selected, it will be replaced with:

                                         o888
    +    ooooooo     ooooooo     ooooooo   888
    +  888     888 888     888 888     888 888
    +  888         888     888 888     888 888
    +    88ooo888    88ooo88     88ooo88  o888o
    +
    +

    This should demonstrate how to do basic text manipulation in the current text buffer and how to deal with selections.

    The final package can be viewed at https://github.com/atom/ascii-art.

    Basic Text Insertion

    To begin, press Cmd+Shift+PCtrl+Shift+P to bring up the Command Paletteopen in new window. Type "generate package" and select the "Package Generator: Generate Package" command, just as we did in the section on package generation. Enter ascii-art as the name of the package.

    Now let's edit the package files to make our ASCII Art package do something interesting. Since this package doesn't need any UI, we can remove all view-related code so go ahead and delete lib/ascii-art-view.js, spec/ascii-art-view-spec.js, and styles/.

    Next, open up lib/ascii-art.js and remove all view code, so it looks like this:

    const { CompositeDisposable } = require("atom");
    +
    +module.exports = {
    +	subscriptions: null,
    +
    +	activate() {
    +		this.subscriptions = new CompositeDisposable();
    +		this.subscriptions.add(
    +			atom.commands.add("atom-workspace", {
    +				"ascii-art:convert": () => this.convert(),
    +			})
    +		);
    +	},
    +
    +	deactivate() {
    +		this.subscriptions.dispose();
    +	},
    +
    +	convert() {
    +		console.log("Convert text!");
    +	},
    +};
    +
    Create a Command

    Now let's add a command. You should namespace your commands with the package name followed by a : and then the name of the command. As you can see in the code, we called our command ascii-art:convert and we will define it to call the convert() method when it's executed.

    So far, that will simply log to the console. Let's start by making it insert something into the text buffer.

    convert() {
    +  const editor = atom.workspace.getActiveTextEditor()
    +  if (editor) {
    +    editor.insertText('Hello, World!')
    +  }
    +}
    +

    As in Counting Words, we're using atom.workspace.getActiveTextEditor() to get the object that represents the active text editor. If this convert() method is called when not focused on a text editor, nothing will happen.

    Next we insert a string into the current text editor with the insertText()open in new window method. This will insert the text wherever the cursor currently is in the current editor. If there are selections, it will replace all selections with the "Hello, World!" text.

    Reload the Package

    Before we can trigger ascii-art:convert, we need to load the latest code for our package by reloading the window. Run the command "Window: Reload" from the Command Palette or by pressing Alt+Cmd+Ctrl+LCtrl+Shift+F5.

    Trigger the Command

    Now open the Command Palette and search for the "Ascii Art: Convert" command. But it's not there! To fix this, open package.json and find the property called activationCommands. Activation commands make Atom launch faster by allowing Atom to delay a package's activation until it's needed. So remove the existing command and use ascii-art:convert in activationCommands:

    "activationCommands": {
    +  "atom-workspace": "ascii-art:convert"
    +}
    +

    First, reload the window by running the command "Window: Reload" from the command palette. Now when you run the "Ascii Art: Convert" command it will insert "Hello, World!" into the active editor, if any.

    Add a Key Binding

    Now let's add a key binding to trigger the ascii-art:convert command. Open keymaps/ascii-art.json and add a key binding linking Alt+Ctrl+A to the ascii-art:convert command. You can delete the pre-existing key binding since you won't need it anymore.

    When finished, the file should look like this:

    {
    +  "atom-text-editor": {
    +    "ctrl-alt-a": "ascii-art:convert"
    +  }
    +}
    +
    +

    Now reload the window and verify that the key binding works.

    WARNING

    Warning: The Atom keymap system is case-sensitive. This means that there is a distinction between a and A when creating keybindings. a means that you want to trigger the keybinding when you press A. But A means that you want to trigger the keybinding when you press Shift+A. You can also write shift-a when you want to trigger the keybinding when you press Shift+A.

    We strongly recommend always using lowercase and explicitly spelling out when you want to include Shift in your keybindings.

    Add the ASCII Art

    Now we need to convert the selected text to ASCII art. To do this we will use the figletopen in new window Node module from npmopen in new window. Open package.json and add the latest version of figlet to the dependencies:

    "dependencies": {
    +  "figlet": "1.0.8"
    +}
    +

    After saving the file, run the command "Update Package Dependencies: Update" from the Command Palette. This will install the package's node module dependencies, only figlet in this case. You will need to run "Update Package Dependencies: Update" whenever you update the dependencies field in your package.json file.

    If for some reason this doesn't work, you'll see a message saying "Failed to update package dependencies" and you will find a new npm-debug.log file in your directory. That file should give you some idea as to what went wrong.

    Now require the figlet node module in lib/ascii-art.js and instead of inserting "Hello, World!", convert the selected text to ASCII art.

    convert () {
    +  const editor = atom.workspace.getActiveTextEditor()
    +  if (editor) {
    +    const selection = editor.getSelectedText()
    +
    +    const figlet = require('figlet')
    +    const font = 'o8'
    +    figlet(selection, {font}, function (error, art) {
    +      if (error) {
    +        console.error(error)
    +      } else {
    +        editor.insertText(`\n${art}\n`)
    +      }
    +    })
    +  }
    +}
    +

    Now reload the editor, select some text in an editor window and press Alt+Ctrl+A. It should be replaced with a ridiculous ASCII art version instead.

    There are a couple of new things in this example we should look at quickly. The first is the editor.getSelectedText()open in new window which, as you might guess, returns the text that is currently selected.

    We then call the Figlet code to convert that into something else and replace the current selection with it with the editor.insertText()open in new window call.

    Summary

    In this section, we've made a UI-less package that takes selected text and replaces it with a processed version. This could be helpful in creating linters or checkers for your code.

    + + + diff --git a/docs/atom-archive/hacking-atom/sections/package-word-count.html b/docs/atom-archive/hacking-atom/sections/package-word-count.html new file mode 100644 index 0000000000..769d1b6136 --- /dev/null +++ b/docs/atom-archive/hacking-atom/sections/package-word-count.html @@ -0,0 +1,434 @@ + + + + + + + + + + + + + + +

    About 13 min

    Package: Word Count

    Let's get started by writing a very simple package and looking at some of the tools needed to develop one effectively. We'll start by writing a package that tells you how many words are in the current buffer and display it in a small modal window.

    Package Generator

    The simplest way to start a package is to use the built-in package generator that ships with Atom. As you might expect by now, this generator is itself a separate package implemented in package-generatoropen in new window.

    You can run the generator by invoking the command palette and searching for "Generate Package". A dialog will appear asking you to name your new project. Name it your-name-word-count. Atom will then create that directory and fill it out with a skeleton project and link it into your ~/.atom/packages%USERPROFILE%\.atom\packages directory so it's loaded when you launch your editor next time.

    Note

    Note: You may encounter a situation where your package is not loaded. That is because a new package using the same name as an actual package hosted on atom.ioopen in new window (e.g. "wordcount" and "word-count") is not being loaded as you expected. If you follow our suggestion above of using the your-name-word-count package name, you should be safe 😀

    Basic generated Atom package

    You can see that Atom has created about a dozen files that make up the package. Let's take a look at each of them to get an idea of how a package is structured, then we can modify them to get our word count functionality.

    The basic package layout is as follows:

    my-package/
    +├─ grammars/
    +├─ keymaps/
    +├─ lib/
    +├─ menus/
    +├─ spec/
    +├─ snippets/
    +├─ styles/
    +├─ index.js
    +└─ package.json
    +

    Not every package will have (or need) all of these directories and the package generator doesn't create snippets or grammars. Let's see what some of these are so we can start messing with them.

    package.json

    Similar to Node modulesopen in new window, Atom packages contain a package.json file in their top-level directory. This file contains metadata about the package, such as the path to its "main" module, library dependencies, and manifests specifying the order in which its resources should be loaded.

    In addition to some of the regular Node package.json keysopen in new window available, Atom package.json files have their own additions.

    • main: the path to the JavaScript file that's the entry point to your package. If this is missing, Atom will default to looking for an index.coffee or index.js.
    • styles: an Array of Strings identifying the order of the style sheets your package needs to load. If not specified, style sheets in the styles directory are added alphabetically.
    • keymaps: an Array of Strings identifying the order of the key mappings your package needs to load. If not specified, mappings in the keymaps directory are added alphabetically.
    • menus: an Array of Strings identifying the order of the menu mappings your package needs to load. If not specified, mappings in the menus directory are added alphabetically.
    • snippets: an Array of Strings identifying the order of the snippets your package needs to load. If not specified, snippets in the snippets directory are added alphabetically.
    • activationCommands: an Object identifying commands that trigger your package's activation. The keys are CSS selectors, the values are Arrays of Strings identifying the command. The loading of your package is delayed until one of these events is triggered within the associated scope defined by the CSS selector. If not specified, the activate() method of your main export will be called when your package is loaded.
    • activationHooks: an Array of Strings identifying hooks that trigger your package's activation. The loading of your package is delayed until one of these hooks are triggered. Currently, there are three activation hooks:
      • core:loaded-shell-environment for when Atom has finished loading the shell environment variables
      • scope.name:root-scope-used for when a file is opened from the specified language (e.g. source.ruby:root-scope-used)
      • language-package-name:grammar-used for when a specific language package is used (e.g., my-special-language-javascript:grammar-used)
    • workspaceOpeners: An Array of Strings identifying URIs that trigger your package's activation. For example, say your package registers a custom opener for atom://my-custom-panel. By including that string in workspaceOpeners, your package will defer its activation until that URI is opened.

    The package.json in the package we've just generated looks like this currently:

    {
    +	"name": "your-name-word-count",
    +	"main": "./lib/your-name-word-count",
    +	"version": "0.0.0",
    +	"description": "A short description of your package",
    +	"activationCommands": {
    +		"atom-workspace": "your-name-word-count:toggle"
    +	},
    +	"repository": "https://github.com/atom/your-name-word-count",
    +	"license": "MIT",
    +	"engines": {
    +		"atom": ">=1.0.0 <2.0.0"
    +	},
    +	"dependencies": {}
    +}
    +

    If you wanted to use activationHooks, you might have:

    {
    +	"name": "your-name-word-count",
    +	"main": "./lib/your-name-word-count",
    +	"version": "0.0.0",
    +	"description": "A short description of your package",
    +	"activationHooks": [
    +		"language-javascript:grammar-used",
    +		"language-coffee-script:grammar-used"
    +	],
    +	"repository": "https://github.com/atom/your-name-word-count",
    +	"license": "MIT",
    +	"engines": {
    +		"atom": ">=1.0.0 <2.0.0"
    +	},
    +	"dependencies": {}
    +}
    +

    One of the first things you should do is ensure that this information is filled out. The name, description, repository URL the project will be at, and the license can all be filled out immediately. The other information we'll get into more detail on as we go.

    WARNING

    Warning: Do not forget to update the repository URL. The one generated for you is invalid by design and will prevent you from publishing your package until updated.

    Source Code

    If you want to extend Atom's behavior, your package should contain a single top-level module, which you export from whichever file is indicated by the main key in your package.json file. In the package we just generated, the main package file is lib/your-name-word-count.js. The remainder of your code should be placed in the lib directory, and required from your top-level file. If the main key is not in your package.json file, it will look for index.js or index.coffee as the main entry point.

    Your package's top-level module is a singleton object that manages the lifecycle of your extensions to Atom. Even if your package creates ten different views and appends them to different parts of the DOM, it's all managed from your top-level object.

    Your package's top-level module can implement the following basic methods:

    • activate(state): This optional method is called when your package is activated. It is passed the state data from the last time the window was serialized if your module implements the serialize() method. Use this to do initialization work when your package is started (like setting up DOM elements or binding events). If this method returns a promise the package will be considered loading until the promise resolves (or rejects).
    • initialize(state): (Available in Atom 1.14 and above) This optional method is similar to activate() but is called earlier. Whereas activation occurs after the workspace has been deserialized (and can therefore happen after your package's deserializers have been called), initialize() is guaranteed to be called before everything. Use activate() if you want to be sure that the workspace is ready; use initialize() if you need to do some setup prior to your deserializers or view providers being invoked.
    • serialize(): This optional method is called when the window is shutting down, allowing you to return JSON to represent the state of your component. When the window is later restored, the data you returned is passed to your module's activate method so you can restore your view to where the user left off.
    • deactivate(): This optional method is called when the window is shutting down and when the package is disabled. If your package is watching any files or holding external resources in any other way, release them here. You should also dispose of all subscriptions you're holding on to.
    Style Sheets

    Style sheets for your package should be placed in the styles directory. Any style sheets in this directory will be loaded and attached to the DOM when your package is activated. Style sheets can be written as CSS or Lessopen in new window, but Less is recommended.

    Ideally, you won't need much in the way of styling. Atom provides a standard set of components which define both the colors and UI elements for any package that fits into Atom seamlessly. You can view all of Atom's UI components by opening the styleguide: open the command palette Cmd+Shift+PCtrl+Shift+P and search for styleguide, or type Cmd+Ctrl+Shift+GCtrl+Shift+G.

    If you do need special styling, try to keep only structural styles in the package style sheets. If you must specify colors and sizing, these should be taken from the active theme's ui-variables.lessopen in new window.

    An optional styleSheets array in your package.json can list the style sheets by name to specify a loading order; otherwise, style sheets are loaded alphabetically.

    Keymaps

    You can provide key bindings for commonly used actions for your extension, especially if you're also adding a new command. In our new package, we have a keymap filled in for us already in the keymaps/your-name-word-count.json file:

    {
    +  "atom-workspace": {
    +    "ctrl-alt-o": "your-name-word-count:toggle"
    +  }
    +}
    +

    This means that if you press Alt+Ctrl+O, our package will run the your-name-word-count:toggle command. We'll look at that code next, but if you want to change the default key mapping, you can do that in this file.

    Keymaps are placed in the keymaps subdirectory. By default, all keymaps are loaded in alphabetical order. An optional keymaps array in your package.json can specify which keymaps to load and in what order.

    Keybindings are executed by determining which element the keypress occurred on. In the example above, the your-name-word-count:toggle command is executed when pressing Alt+Ctrl+O on the atom-workspace element. Because the atom-workspace element is the parent of the entire Atom UI, this means the key combination will work anywhere in the application.

    We'll cover more advanced keybinding stuff a bit later in Keymaps in Depth.

    Menus are placed in the menus subdirectory. This defines menu elements like what pops up when you right click a context-menu or would go in the application menu to trigger functionality in your plugin.

    By default, all menus are loaded in alphabetical order. An optional menus array in your package.json can specify which menus to load and in what order.

    Application Menu

    It's recommended that you create an application menu item under the Packages menu for common actions with your package that aren't tied to a specific element. If we look in the menus/your-name-word-count.json file that was generated for us, we'll see a section that looks like this:

    
    +"menu": [
    +  {
    +    "label": "Packages",
    +    "submenu": [
    +      {
    +        "label": "Word Count",
    +        "submenu": [
    +          {
    +            "label": "Toggle",
    +            "command": "your-name-word-count:toggle"
    +          }
    +        ]
    +      }
    +    ]
    +  }
    +]
    +
    +

    This section puts a "Toggle" menu item under a menu group named "Your Name Word Count" in the "Packages" menu.

    Application Menu Item

    When you select that menu item, it will run the your-name-word-count:toggle command, which we'll look at in a bit.

    The menu templates you specify are merged with all other templates provided by other packages in the order which they were loaded.

    Context Menu

    It's recommended to specify a context menu item for commands that are linked to specific parts of the interface. In our menus/your-name-word-count.json file, we can see an auto-generated section that looks like this:

    "context-menu": {
    +    "atom-text-editor": [
    +      {
    +        "label": "Toggle your-name-word-count",
    +        "command": "your-name-word-count:toggle"
    +      }
    +    ]
    +  }
    +

    This adds a "Toggle Word Count" menu option to the menu that pops up when you right-click in an Atom text editor pane.

    Context Menu Entry

    When you click that it will again run the your-name-word-count:toggle method in your code.

    Context menus are created by determining which element was selected and then adding all of the menu items whose selectors match that element (in the order which they were loaded). The process is then repeated for the elements until reaching the top of the DOM tree.

    You can also add separators and submenus to your context menus. To add a submenu, provide a submenu key instead of a command. To add a separator, add an item with a single type: 'separator' key/value pair. For instance, you could do something like this:

    {
    +  "context-menu": {
    +    "atom-workspace": [
    +      {
    +        "label": "Text",
    +        "submenu": [
    +          {
    +            "label": "Inspect Element",
    +            "command": "core:inspect"
    +          },
    +          {
    +            "type": "separator"
    +          },
    +          {
    +            "label": "Selector All",
    +            "command": "core:select-all"
    +          },
    +          {
    +            "type": "separator"
    +          },
    +          {
    +            "label": "Deleted Selected Text",
    +            "command": "core:delete"
    +          }
    +        ]
    +      }
    +    ]
    +  }
    +}
    +

    Developing Our Package

    Currently with the generated package we have, if we run that your-name-word-count:toggle command through the menu or the command palette, we'll get a dialog that says "The YourNameWordCount package is Alive! It's ALIVE!".

    Wordcount Package is Alive Dialog

    Understanding the Generated Code

    Let's take a look at the code in our lib directory and see what is happening.

    There are two files in our lib directory. One is the main file (lib/your-name-word-count.js), which is pointed to in the package.json file as the main file to execute for this package. This file handles the logic of the whole plugin.

    The second file is a View class, lib/your-name-word-count-view.js, which handles the UI elements of the package. Let's look at this file first, since it's pretty simple.

    export default class YourNameWordCountView {
    +	constructor(serializedState) {
    +		// Create root element
    +		this.element = document.createElement("div");
    +		this.element.classList.add("your-name-word-count");
    +
    +		// Create message element
    +		const message = document.createElement("div");
    +		message.textContent = "The YourNameWordCount package is Alive! It's ALIVE!";
    +		message.classList.add("message");
    +		this.element.appendChild(message);
    +	}
    +
    +	// Returns an object that can be retrieved when package is activated
    +	serialize() {}
    +
    +	// Tear down any state and detach
    +	destroy() {
    +		this.element.remove();
    +	}
    +
    +	getElement() {
    +		return this.element;
    +	}
    +}
    +

    Basically the only thing happening here is that when the View class is created, it creates a simple div element and adds the your-name-word-count class to it (so we can find or style it later) and then adds the "Your Name Word Count package is Alive!" text to it. There is also a getElement method which returns that div. The serialize and destroy methods don't do anything and we won't have to worry about that until another example.

    Notice that we're simply using the basic browser DOM methods: createElement() and appendChild().

    The second file we have is the main entry point to the package. Again, because it's referenced in the package.json file. Let's take a look at that file.

    import YourNameWordCountView from "./your-name-word-count-view";
    +import { CompositeDisposable } from "atom";
    +
    +export default {
    +	yourNameWordCountView: null,
    +	modalPanel: null,
    +	subscriptions: null,
    +
    +	activate(state) {
    +		this.yourNameWordCountView = new YourNameWordCountView(
    +			state.yourNameWordCountViewState
    +		);
    +		this.modalPanel = atom.workspace.addModalPanel({
    +			item: this.yourNameWordCountView.getElement(),
    +			visible: false,
    +		});
    +
    +		// Events subscribed to in atom's system can be easily cleaned up with a CompositeDisposable
    +		this.subscriptions = new CompositeDisposable();
    +
    +		// Register command that toggles this view
    +		this.subscriptions.add(
    +			atom.commands.add("atom-workspace", {
    +				"your-name-word-count:toggle": () => this.toggle(),
    +			})
    +		);
    +	},
    +
    +	deactivate() {
    +		this.modalPanel.destroy();
    +		this.subscriptions.dispose();
    +		this.yourNameWordCountView.destroy();
    +	},
    +
    +	serialize() {
    +		return {
    +			yourNameWordCountViewState: this.yourNameWordCountView.serialize(),
    +		};
    +	},
    +
    +	toggle() {
    +		console.log("YourNameWordCount was toggled!");
    +		return this.modalPanel.isVisible()
    +			? this.modalPanel.hide()
    +			: this.modalPanel.show();
    +	},
    +};
    +

    There is a bit more going on here. First of all we can see that we are defining four methods. The only required one is activate. The deactivate and serialize methods are expected by Atom but optional. The toggle method is one Atom is not looking for, so we'll have to invoke it somewhere for it to be called, which you may recall we do both in the activationCommands section of the package.json file and in the action we have in the menu file.

    The deactivate method simply destroys the various class instances we've created and the serialize method simply passes on the serialization to the View class. Nothing too exciting here.

    The activate command does a number of things. For one, it is not called automatically when Atom starts up, it is first called when one of the activationCommands as defined in the package.json file are called. In this case, activate is only called the first time the toggle command is called. If nobody ever invokes the menu item or hotkey, this code is never called.

    This method does two things. The first is that it creates an instance of the View class we have and adds the element that it creates to a hidden modal panel in the Atom workspace.

    this.yourNameWordCountView = new YourNameWordCountView(
    +	state.yourNameWordCountViewState
    +);
    +this.modalPanel = atom.workspace.addModalPanel({
    +	item: this.yourNameWordCountView.getElement(),
    +	visible: false,
    +});
    +

    We'll ignore the state stuff for now, since it's not important for this simple plugin. The rest should be fairly straightforward.

    The next thing this method does is create an instance of the CompositeDisposable class so it can register all the commands that can be called from the plugin so other plugins could subscribe to these events.

    // Events subscribed to in atom's system can be easily cleaned up with a CompositeDisposable
    +this.subscriptions = new CompositeDisposable();
    +
    +// Register command that toggles this view
    +this.subscriptions.add(
    +	atom.commands.add("atom-workspace", {
    +		"your-name-word-count:toggle": () => this.toggle(),
    +	})
    +);
    +

    Next we have the toggle method. This method simply toggles the visibility of the modal panel that we created in the activate method.

    toggle() {
    +  console.log('YourNameWordCount was toggled!');
    +  return (
    +    this.modalPanel.isVisible() ?
    +    this.modalPanel.hide() :
    +    this.modalPanel.show()
    +  );
    +}
    +

    This should be fairly simple to understand. We're looking to see if the modal element is visible and hiding or showing it depending on its current state.

    The Flow

    So, let's review the actual flow in this package.

    1. Atom starts up
    2. Atom starts loading packages
    3. Atom reads your package.json
    4. Atom loads keymaps, menus, styles and the main module
    5. Atom finishes loading packages
    6. At some point, the user executes your package command your-name-word-count:toggle
    7. Atom executes the activate method in your main module which sets up the UI by creating the hidden modal view
    8. Atom executes the package command your-name-word-count:toggle which reveals the hidden modal view
    9. At some point, the user executes the your-name-word-count:toggle command again
    10. Atom executes the command which hides the modal view
    11. Eventually, Atom is shut down which can trigger any serializations that your package has defined

    Tip

    Tip: Keep in mind that the flow will be slightly different if you choose not to use activationCommands in your package.

    Counting the Words

    So now that we understand what is happening, let's modify the code so that our little modal box shows us the current word count instead of static text.

    We'll do this in a very simple way. When the dialog is toggled, we'll count the words right before displaying the modal. So let's do this in the toggle command. If we add some code to count the words and ask the view to update itself, we'll have something like this:

    toggle() {
    +  if (this.modalPanel.isVisible()) {
    +    this.modalPanel.hide();
    +  } else {
    +    const editor = atom.workspace.getActiveTextEditor();
    +    const words = editor.getText().split(/\s+/).length;
    +    this.yourNameWordCountView.setCount(words);
    +    this.modalPanel.show();
    +  }
    +}
    +

    Let's look at the 3 lines we've added. First we get an instance of the current editor object (where our text to count is) by calling atom.workspace.getActiveTextEditor()open in new window.

    Next we get the number of words by calling getText()open in new window on our new editor object, then splitting that text on whitespace with a regular expression and then getting the length of that array.

    Finally, we tell our view to update the word count it displays by calling the setCount() method on our view and then showing the modal again. Since that method doesn't yet exist, let's create it now.

    We can add this code to the end of our your-name-word-count-view.js file:

    setCount(count) {
    +  const displayText = `There are ${count} words.`;
    +  this.element.children[0].textContent = displayText;
    +}
    +

    Pretty simple! We take the count number that was passed in and place it into a string that we then stick into the element that our view is controlling.

    Note

    Note: To see your changes, you'll need to reload the code. You can do this by reloading the window (The window:reload command in the Command Palette). A common practice is to have two Atom windows, one for developing your package, and one for testing and reloading.

    Word Count Working

    Basic Debugging

    You'll notice a few console.log statements in the code. One of the cool things about Atom being built on Chromium is that you can use some of the same debugging tools available to you that you have when doing web development.

    To open up the Developer Console, press Alt+Cmd+ICtrl+Shift+I, or choose the menu option View > Developer > Toggle Developer Tools.

    Developer Tools Debugging

    From here you can inspect objects, run code and view console output just as though you were debugging a web site.

    Testing

    Your package should have tests, and if they're placed in the spec directory, they can be run by Atom.

    Under the hood, Jasmine v1.3open in new window executes your tests, so you can assume that any DSL available there is also available to your package.

    Running Tests

    Once you've got your test suite written, you can run it by pressing Alt+Cmd+Ctrl+PAlt+Ctrl+P or via the View > Developer > Run Package Specs menu. Our generated package comes with an example test suite, so you can run this right now to see what happens.

    Spec Suite Results

    You can also use the atom --test spec command to run them from the command line. It prints the test output and results to the console and returns the proper status code depending on whether the tests passed or failed.

    Summary

    We've now generated, customized and tested our first plugin for Atom. Congratulations! Now let's go ahead and publish it so it's available to the world.

    + + + diff --git a/docs/atom-archive/hacking-atom/sections/publishing.html b/docs/atom-archive/hacking-atom/sections/publishing.html new file mode 100644 index 0000000000..e4efc7fa5f --- /dev/null +++ b/docs/atom-archive/hacking-atom/sections/publishing.html @@ -0,0 +1,226 @@ + + + + + + + + + + + + + + +

    About 2 min

    Publishing

    Atom bundles a command line utility called apm which we first used back in Command Line to search for and install packages via the command line. The apm command can also be used to publish Atom packages to the public registry and update them.

    Prepare Your Package

    There are a few things you should double check before publishing:

    • Your package.json file has name, description, and repository fields.
    • Your package.json file has a version field with a value of "0.0.0".
    • Your package.json file has an engines field that contains an entry for Atom such as: "engines": {"atom": ">=1.0.0 <2.0.0"}.
    • Your package has a README.md file at the root.
    • Your repository URL in the package.json file is the same as the URL of your repository.
    • Your package is in a Git repository that has been pushed to GitHubopen in new window. Follow this guideopen in new window if your package isn't already on GitHub.

    Publish Your Package

    Before you publish a package it is a good idea to check ahead of time if a package with the same name has already been published to the atom.io package registryopen in new window. You can do that by visiting https://atom.io/packages/your-package-name to see if the package already exists. If it does, update your package's name to something that is available before proceeding.

    Now let's review what the apm publish command does:

    1. Registers the package name on atom.io if it is being published for the first time.
    2. Updates the version field in the package.json file and commits it.
    3. Creates a new Git tagopen in new window for the version being published.
    4. Pushes the tag and current branch up to GitHub.
    5. Updates atom.io with the new version being published.

    Now run the following commands to publish your package:

    $ cd path-to-your-package
    +$ apm publish minor
    +

    If this is the first package you are publishing, the apm publish command may prompt you for your GitHub username and password. If you have two-factor authentication enabled, use a personal access tokenopen in new window in lieu of a password. This is required to publish and you only need to enter this information the first time you publish. The credentials are stored securely in your keychainopen in new window once you login.

    Your package is now published and available on atom.io. Head on over to https://atom.io/packages/your-package-name to see your package's page.

    With apm publish, you can bump the version and publish by using

    $ apm publish <em>version-type</em>
    +

    where version-type can be major, minor and patch.

    The major option to the publish command tells apm to increment the first number of the version before publishing so the published version will be 1.0.0 and the Git tag created will be v1.0.0.

    The minor option to the publish command tells apm to increment the second number of the version before publishing so the published version will be 0.1.0 and the Git tag created will be v0.1.0.

    The patch option to the publish command tells apm to increment the third number of the version before publishing so the published version will be 0.0.1 and the Git tag created will be v0.0.1.

    Use major when you make a change that breaks backwards compatibility, like changing defaults or removing features. Use minor when adding new functionality or options, but without breaking backwards compatibility. Use patch when you've changed the implementation of existing features, but without changing the behaviour or options of your package. Check out semantic versioningopen in new window to learn more about best practices for versioning your package releases.

    You can also run apm help publish to see all the available options and apm help to see all the other available commands.

    + + + diff --git a/docs/atom-archive/hacking-atom/sections/summary.html b/docs/atom-archive/hacking-atom/sections/summary.html new file mode 100644 index 0000000000..ffc1659e93 --- /dev/null +++ b/docs/atom-archive/hacking-atom/sections/summary.html @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + +

    Less than 1 minute

    Summary

    If you finished this chapter, you should be an Atom-hacking master. We've discussed how you should work with CoffeeScript, and how to put it to good use in creating packages. You should also be able to do this in your own created theme now.

    Even when something goes wrong, you should be able to debug this easily. But also fewer things should go wrong, because you are capable of writing great specs for Atom.

    In the next chapter, we’ll go into more of a deep dive on individual internal APIs and systems of Atom, even looking at some Atom source to see how things are really getting done.

    + + + diff --git a/docs/atom-archive/hacking-atom/sections/the-init-file.html b/docs/atom-archive/hacking-atom/sections/the-init-file.html new file mode 100644 index 0000000000..ede0d33885 --- /dev/null +++ b/docs/atom-archive/hacking-atom/sections/the-init-file.html @@ -0,0 +1,231 @@ + + + + + + + + + + + + + + +

    About 1 min

    The Init File

    When Atom finishes loading, it will evaluate init.coffee in your ~/.atom%USERPROFILE%\.atom directory, giving you a chance to run CoffeeScript code to make customizations. Code in this file has full access to Atom's APIopen in new window. If customizations become extensive, consider creating a package, which we will cover in Package: Word Count.

    You can open the init.coffee file in an editor from the Atom > Init ScriptFile > Init ScriptEdit > Init Script menu. This file can also be named init.js and contain JavaScript code.

    For example, if you have the Audio Beep configuration setting enabled, you could add the following code to your init.coffee file to have Atom greet you with an audio beep every time it loads:

    atom.beep()
    +

    Because init.coffee provides access to Atom's API, you can use it to implement useful commands without creating a new package or extending an existing one. Here's a command which uses the Selection APIopen in new window and Clipboard APIopen in new window to construct a Markdown link from the selected text and the clipboard contents as the URL:

    atom.commands.add 'atom-text-editor', 'markdown:paste-as-link', ->
    +  return unless editor = atom.workspace.getActiveTextEditor()
    +
    +  selection = editor.getLastSelection()
    +  clipboardText = atom.clipboard.read()
    +
    +  selection.insertText("[#{selection.getText()}](#{clipboardText})")
    +

    Now, reload Atom and use the Command Palette to execute the new command, "Markdown: Paste As Link", by name. And if you'd like to trigger the command via a keyboard shortcut, you can define a keybinding for the command.

    + + + diff --git a/docs/atom-archive/hacking-atom/sections/tools-of-the-trade.html b/docs/atom-archive/hacking-atom/sections/tools-of-the-trade.html new file mode 100644 index 0000000000..4d797f69cc --- /dev/null +++ b/docs/atom-archive/hacking-atom/sections/tools-of-the-trade.html @@ -0,0 +1,236 @@ + + + + + + + + + + + + + + +

    Less than 1 minute

    Tools of the Trade

    To begin, there are a few things we'll assume you know, at least to some degree. Since all of Atom is implemented using web technologies, we have to assume you know web technologies such as JavaScript and CSS. Specifically, we'll be using Less, which is a preprocessor for CSS.

    While much of Atom has been converted to JavaScript, a lot of older code has been left implemented in CoffeeScript because changing it would have been too risky. Additionally, Atom's default configuration language is CSON, which is based on CoffeeScript. If you don't know CoffeeScript, but you are familiar with JavaScript, you shouldn't have too much trouble. Here is an example of some simple CoffeeScript code:

    MyPackageView = require './my-package-view'
    +
    +module.exports =
    +  myPackageView: null
    +
    +  activate: (state) ->
    +    @myPackageView = new MyPackageView(state.myPackageViewState)
    +
    +  deactivate: ->
    +    @myPackageView.destroy()
    +
    +  serialize: ->
    +    myPackageViewState: @myPackageView.serialize()
    +

    We'll go over examples like this in a bit, but this is what the language looks like. Just about everything you can do with CoffeeScript in Atom is also doable in JavaScript. You can brush up on CoffeeScript at coffeescript.orgopen in new window.

    Less is an even simpler transition from CSS. It adds a number of useful things like variables and functions to CSS. You can learn about Less at lesscss.orgopen in new window. Our usage of Less won't get too complex in this book however, so as long as you know basic CSS you should be fine.

    + + + diff --git a/docs/atom-archive/hacking-atom/sections/writing-specs.html b/docs/atom-archive/hacking-atom/sections/writing-specs.html new file mode 100644 index 0000000000..8e1f31da17 --- /dev/null +++ b/docs/atom-archive/hacking-atom/sections/writing-specs.html @@ -0,0 +1,311 @@ + + + + + + + + + + + + + + +

    About 5 min

    Writing Specs

    We've looked at and written a few specs through the examples already. Now it's time to take a closer look at the spec framework itself. How exactly do you write tests in Atom?

    Atom uses Jasmineopen in new window as its spec framework. Any new functionality should have specs to guard against regressions.

    Create a New Spec

    Atom specsopen in new window and package specsopen in new window are added to their respective spec directory. The example below creates a spec for Atom core.

    Create a Spec File

    Spec files must end with -spec so add sample-spec.coffee to the spec directory.

    Add One or More describe Methods

    The describe method takes two arguments, a description and a function. If the description explains a behavior it typically begins with when; if it is more like a unit test it begins with the method name.

    describe("when a test is written", function () {
    +	// contents
    +});
    +

    or

    describe("Editor::moveUp", function () {
    +	// contents
    +});
    +
    Add One or More it Methods

    The it method also takes two arguments, a description and a function. Try and make the description flow with the it method. For example, a description of "this should work" doesn't read well as "it this should work". But a description of "should work" sounds great as "it should work".

    describe("when a test is written", function () {
    +	it("has some expectations that should pass", function () {
    +		// Expectations
    +	});
    +});
    +
    Add One or More Expectations

    The best way to learn about expectations is to read the Jasmine documentationopen in new window about them. Below is a simple example.

    describe("when a test is written", function () {
    +	it("has some expectations that should pass", function () {
    +		expect("apples").toEqual("apples");
    +		expect("oranges").not.toEqual("apples");
    +	});
    +});
    +
    Custom Matchers

    In addition to the Jasmine's built-in matchers, Atom includes the following:

    • jasmine-jqueryopen in new window
    • The toBeInstanceOf matcher is for the instanceof operator
    • The toHaveLength matcher compares against the .length property
    • The toExistOnDisk matcher checks if the file exists in the filesystem
    • The toHaveFocus matcher checks if the element currently has focus
    • The toShow matcher tests if the element is visible in the dom

    These are defined in spec/spec-helper.coffeeopen in new window.

    Asynchronous Specs

    Writing Asynchronous specs can be tricky at first. Some examples.

    Promises

    Working with promises is rather easy in Atom. You can use our waitsForPromise function.

    describe("when we open a file", function () {
    +	it("should be opened in an editor", function () {
    +		waitsForPromise(function () {
    +			atom.workspace
    +				.open("c.coffee")
    +				.then((editor) => expect(editor.getPath()).toContain("c.coffee"));
    +		});
    +	});
    +});
    +

    This method can be used in the describe, it, beforeEach and afterEach functions.

    describe("when we open a file", function () {
    +	beforeEach(function () {
    +		waitsForPromise(() => atom.workspace.open("c.coffee"));
    +	});
    +
    +	it("should be opened in an editor", function () {
    +		expect(atom.workspace.getActiveTextEditor().getPath()).toContain(
    +			"c.coffee"
    +		);
    +	});
    +});
    +

    If you need to wait for multiple promises use a new waitsForPromise function for each promise. (Caution: Without beforeEach this example will fail!)

    describe("waiting for the packages to load", function () {
    +	beforeEach(function () {
    +		waitsForPromise(() => atom.workspace.open("sample.js"));
    +
    +		waitsForPromise(() => atom.packages.activatePackage("tabs"));
    +
    +		waitsForPromise(() => atom.packages.activatePackage("tree-view"));
    +	});
    +
    +	it("should have waited long enough", function () {
    +		expect(atom.packages.isPackageActive("tabs")).toBe(true);
    +		expect(atom.packages.isPackageActive("tree-view")).toBe(true);
    +	});
    +});
    +

    waitsForPromise can take an additional object argument before the function. The object can have the following properties:

    • shouldReject Whether the promise should reject or resolve (default: false)
    • timeout The amount of time (in ms) to wait for the promise to be resolved or rejected (default: process.env.CI ? 60000 : 5000)
    • label The label to display if promise times out (default: 'promise to be resolved or rejected')
    describe("when we open a file", function () {
    +	it("should be opened in an editor", function () {
    +		waitsForPromise(
    +			{
    +				shouldReject: false,
    +				timeout: 5000,
    +				label: "promise to be resolved or rejected",
    +			},
    +			() =>
    +				atom.workspace
    +					.open("c.coffee")
    +					.then((editor) => expect(editor.getPath()).toContain("c.coffee"))
    +		);
    +	});
    +});
    +
    Asynchronous Functions with Callbacks

    Specs for asynchronous functions can be done using the waitsFor and runs functions. A simple example.

    describe("fs.readdir(path, cb)", function () {
    +	it("is async", function () {
    +		const spy = jasmine.createSpy("fs.readdirSpy");
    +		fs.readdir("/tmp/example", spy);
    +
    +		waitsFor(() => spy.callCount > 0);
    +
    +		runs(function () {
    +			const exp = [null, ["example.coffee"]];
    +
    +			expect(spy.mostRecentCall.args).toEqual(exp);
    +			expect(spy).toHaveBeenCalledWith(null, ["example.coffee"]);
    +		});
    +	});
    +});
    +

    For a more detailed documentation on asynchronous tests please visit the Jasmine documentationopen in new window.

    Running Specs

    Most of the time you'll want to run specs by triggering the window:run-package-specs command. This command is not only to run package specs, it can also be used to run Atom core specs when working on Atom itself. This will run all the specs in the current project's spec directory.

    To run a limited subset of specs use the fdescribe or fit methods. You can use those to focus a single spec or several specs. Modified from the example above, focusing an individual spec looks like this:

    describe("when a test is written", function () {
    +	fit("has some expectations that should pass", function () {
    +		expect("apples").toEqual("apples");
    +		expect("oranges").not.toEqual("apples");
    +	});
    +});
    +
    Running on CI

    It is now easy to run the specs in a CI environment like Travis and AppVeyor. See the Travis CI For Your Packagesopen in new window and AppVeyor CI For Your Packagesopen in new window posts for more details.

    Running via the Command Line

    To run tests on the command line, run Atom with the --test flag followed by one or more paths to test files or directories. You can also specify a --timeout option, which will force-terminate your tests after a certain number of seconds have passed.

    atom --test --timeout 60 ./test/test-1.js ./test/test-2.js
    +

    Customizing your test runner

    WARNING

    Warning: This API is available as of 1.2.0-beta0, and it is experimental and subject to change. Test runner authors should be prepared to test their code against future beta releases until it stabilizes.

    By default, package tests are run with Jasmine 1.3, which is outdated but can't be changed for compatibility reasons. You can specify your own custom test runner by including an atomTestRunner field in your package.json. Atom will require whatever module you specify in this field, so you can use a relative path or the name of a module in your package's dependencies.

    Your test runner module must export a single function, which Atom will call within a new window to run your package's tests. Your function will be called with the following parameters:

    • testPaths An array of paths to tests to run. Could be paths to files or directories.
    • buildAtomEnvironment A function that can be called to construct an instance of the atom global. No atom global will be explicitly assigned, but you can assign one in your runner if desired. This function should be called with the following parameters:
      • applicationDelegate An object responsible for Atom's interaction with the browser process and host OS. Use buildDefaultApplicationDelegate for a default instance. You can override specific methods on this object to prevent or test these interactions.
      • window A window global.
      • document A document global.
      • configDirPath A path to the configuration directory (usually ~/.atom).
      • enablePersistence A boolean indicating whether the Atom environment should save or load state from the file system. You probably want this to be false.
    • buildDefaultApplicationDelegate A function that builds a default instance of the application delegate, suitable to be passed as the applicationDelegate parameter to buildAtomEnvironment.
    • logFile An optional path to a log file to which test output should be logged.
    • headless A boolean indicating whether or not the tests are being run from the command line via atom --test.
    • legacyTestRunner This function can be invoked to run the legacy Jasmine runner, giving your package a chance to transition to a new test runner while maintaining a subset of its tests in the old environment.

    Your function should return a promise that resolves to an exit code when your tests are finish running. This exit code will be returned when running your tests via the command line.

    + + + diff --git a/docs/atom-archive/index.html b/docs/atom-archive/index.html new file mode 100644 index 0000000000..8ecd9e3849 --- /dev/null +++ b/docs/atom-archive/index.html @@ -0,0 +1,223 @@ + + + + + + + + Atom Documentation Archive | + + + + + + +

    Atom Documentation Archive

    Less than 1 minute

    STOP

    This is being kept for archival purposes only from the original Atom documentation. As this may no longer be relevant to Pulsar, use this at your own risk. Current Pulsar documentation is found at documentation home.

    This is an archive of the old Atom documentation as it appeared on their Flight Manualopen in new window. Anything here is just a historical reference of our past, when Atom existed as a GitHub supported project.

    This includes their original layout and formatting of how things were expected to appear. Anything listed here, may work, and it may not. This is taking into consideration their sunset and our picking up of their torch.

    Chapters

    + + + diff --git a/docs/atom-archive/resources/index.html b/docs/atom-archive/resources/index.html new file mode 100644 index 0000000000..0b02e0b6ca --- /dev/null +++ b/docs/atom-archive/resources/index.html @@ -0,0 +1,223 @@ + + + + + + + + Appendix A : Resources | + + + + + + +

    Appendix A : Resources

    Less than 1 minute

    STOP

    This is being kept for archival purposes only from the original Atom documentation. As this may no longer be relevant to Pulsar, use this at your own risk. Current Pulsar documentation is found at documentation home.

    Resources

    Collection of general resources for the rest of the manual.

    Glossary

    Below is a list of some useful terms we use with regard to Atom.

    Buffer

    A buffer is the text content of a file in Atom. It's basically the same as a file for most descriptions, but it's the version Atom has in memory. For instance, you can change the text of a buffer and it isn't written to its associated file until you save it.

    Command

    A command is a bit of functionality in Atom that can be triggered by the user either through a keybinding or a menu item.

    Dock

    Docks are collapsible pane containers that attach to the left, right, and bottom sides of the Atom window.

    Examples:

    • Tree View
    • Git
    • GitHub

    Key Combination

    A key combination is some combination or sequence of keys that are pressed to perform a task.

    Examples:

    • A
    • Ctrl+Enter
    • Ctrl+K Right

    Key Sequence

    A key sequence is a special case of a key combination. It is a key combination that consists of keys that must be pressed and released in sequence. Ctrl+K Down is a key sequence. Alt+S is not a key sequence because it is two keys that are pressed and released together rather than in succession.

    Keybinding

    A keybinding is the mapping of a key combination, such as Ctrl+Enter to an Atom command.

    Keymap

    A keymap is a collection of keybindings. It can also refer to a file or files containing keybindings for an Atom package or Atom itself.

    Package

    An Atom plugin. There is a bunch more information in the section on Atom Packages.

    Pane

    A pane is a visual section of the editor space. Each pane can hold multiple pane items. There is always at least one pane in each Atom window.

    Pane Container

    A section of the Atom UI that can contain multiple panes.

    Pane Item

    Some item, often an editor, that is displayed within a pane. In the default configuration of Atom, pane items are represented by tabs at the top of each pane.

    Note

    Note: The reason why we don't call them "tabs" is because you can disable the tabs packageopen in new window and then there aren't any tabs. For a similar reason, we don't call them files because some things can be shown in a pane that aren't files, like the Settings View.

    Panel

    A piece of the Atom UI that is outside the editor space.

    Examples:

    • Find and Replace
    • Keybinding Resolver
    + + + diff --git a/docs/atom-archive/resources/sections/glossary.html b/docs/atom-archive/resources/sections/glossary.html new file mode 100644 index 0000000000..2a2296aa5a --- /dev/null +++ b/docs/atom-archive/resources/sections/glossary.html @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + +

    About 2 min

    Glossary

    Below is a list of some useful terms we use with regard to Atom.

    Buffer

    A buffer is the text content of a file in Atom. It's basically the same as a file for most descriptions, but it's the version Atom has in memory. For instance, you can change the text of a buffer and it isn't written to its associated file until you save it.

    Command

    A command is a bit of functionality in Atom that can be triggered by the user either through a keybinding or a menu item.

    Dock

    Docks are collapsible pane containers that attach to the left, right, and bottom sides of the Atom window.

    Examples:

    • Tree View
    • Git
    • GitHub

    Key Combination

    A key combination is some combination or sequence of keys that are pressed to perform a task.

    Examples:

    • A
    • Ctrl+Enter
    • Ctrl+K Right

    Key Sequence

    A key sequence is a special case of a key combination. It is a key combination that consists of keys that must be pressed and released in sequence. Ctrl+K Down is a key sequence. Alt+S is not a key sequence because it is two keys that are pressed and released together rather than in succession.

    Keybinding

    A keybinding is the mapping of a key combination, such as Ctrl+Enter to an Atom command.

    Keymap

    A keymap is a collection of keybindings. It can also refer to a file or files containing keybindings for an Atom package or Atom itself.

    Package

    An Atom plugin. There is a bunch more information in the section on Atom Packages.

    Pane

    A pane is a visual section of the editor space. Each pane can hold multiple pane items. There is always at least one pane in each Atom window.

    Pane Container

    A section of the Atom UI that can contain multiple panes.

    Pane Item

    Some item, often an editor, that is displayed within a pane. In the default configuration of Atom, pane items are represented by tabs at the top of each pane.

    Note

    Note: The reason why we don't call them "tabs" is because you can disable the tabs packageopen in new window and then there aren't any tabs. For a similar reason, we don't call them files because some things can be shown in a pane that aren't files, like the Settings View.

    Panel

    A piece of the Atom UI that is outside the editor space.

    Examples:

    • Find and Replace
    • Keybinding Resolver
    + + + diff --git a/docs/atom-archive/shadow-dom/index.html b/docs/atom-archive/shadow-dom/index.html new file mode 100644 index 0000000000..15597a2fac --- /dev/null +++ b/docs/atom-archive/shadow-dom/index.html @@ -0,0 +1,276 @@ + + + + + + + + Appendix C : Shadow DOM | + + + + + + +

    Appendix C : Shadow DOM

    Less than 1 minute

    STOP

    This is being kept for archival purposes only from the original Atom documentation. As this may no longer be relevant to Pulsar, use this at your own risk. Current Pulsar documentation is found at documentation home.

    Shadow DOM

    In Atom 1.13 the Shadow DOM got removed from text editors. Find a guide how to migrate your theme or package in this appendix.

    Removing Shadow DOM styles

    In Atom 1.13 the Shadow DOM got removed from text editors. For more background on the reasoning, check out the Pull Requestopen in new window where it was removed. In this guide you will learn how to migrate your theme or package.

    Summary

    Here is a quick summary to see all the changes at a glance:

    Before+/-After
    atom-text-editor::shadow {}- ::shadowatom-text-editor {}
    .class /deep/ .class {}- /deep/.class .class {}
    atom-text-editor, :host {}- :hostatom-text-editor {}
    .comment {}+ .syntax--.syntax--comment {}

    Below you'll find more detailed examples.

    UI themes and packages

    ::shadow

    Remove the ::shadow pseudo-element selector from atom-text-editor.

    Before:

    atom-text-editor::shadow .cursor {
    +	border-color: hotpink;
    +}
    +

    After:

    atom-text-editor .cursor {
    +	border-color: hotpink;
    +}
    +
    /deep/

    Remove the /deep/ combinator selector. It didn't get used that often, mainly to customize the scrollbars.

    Before:

    .scrollbars-visible-always /deep/ ::-webkit-scrollbar {
    +	width: 8px;
    +	height: 8px;
    +}
    +

    After:

    .scrollbars-visible-always ::-webkit-scrollbar {
    +	width: 8px;
    +	height: 8px;
    +}
    +

    Syntax themes

    :host

    Remove the :host pseudo-element selector. To scope any styles to the text editor, using atom-text-editor is already enough.

    Before:

    atom-text-editor,
    +:host {
    +	.cursor {
    +		border-color: hotpink;
    +	}
    +}
    +

    After:

    atom-text-editor {
    +	.cursor {
    +		border-color: hotpink;
    +	}
    +}
    +
    syntax--

    Add a syntax-- prefix to all grammar selectors used by language packages. It looks a bit verbose, but it will protect all the grammar selectors from accidental style pollution.

    Before:

    .comment {
    +	color: @mono-3;
    +	font-style: italic;
    +
    +	.markup.link {
    +		color: @mono-3;
    +	}
    +}
    +

    After:

    .syntax--comment {
    +	color: @mono-3;
    +	font-style: italic;
    +
    +	.syntax--markup.syntax--link {
    +		color: @mono-3;
    +	}
    +}
    +

    Note: Selectors like the .gutter, .indent-guide, .cursor among others, that are also part of Syntax themes, don't need a prefix. Only grammar selectors that get used by language packages. For example .syntax--keyword, .syntax--keyword.syntax--operator.syntax--js.

    Context-Targeted Style Sheets

    Atom also allowed you to target a specific shadow DOM context with an entire style sheet by adding .atom-text-editor to the file name. This is now not necessary anymore and can be removed.

    Before:

    my-ui-theme/
    +  styles/
    +    index.atom-text-editor.less
    +

    After:

    my-ui-theme/
    +  styles/
    +    index.less
    +

    I followed the guide, but now my styling is broken!

    By replacing atom-text-editor::shadow with atom-text-editor.editor, specificity might have changed. This can cause the side effect that some of your properties won't be strong enough. To fix that, you can increase specificity on your selector. A simple way is to just repeat your class (in the example below it's .my-class):

    Before:

    atom-text-editor::shadow .my-class {
    +	color: hotpink;
    +}
    +

    After:

    atom-text-editor .my-class.my-class {
    +	color: hotpink;
    +}
    +

    When should I migrate my theme/package?

    • If you already want to test the migration on master or Beta channel, make sure to change your package.json file to "engines": { "atom": ">=1.13.0 <2.0.0" }. This will prevent Atom from updating your theme or package before the user also updates Atom to version 1.13.
    • Or you can wait until Atom 1.13 reaches Stable. Check blog.atom.ioopen in new window to see if 1.13 already has been released. Don't worry if you're a bit late, Atom will transform the deprecated selectors automatically to avoid breaking any themes or packages. But users will start to see a deprecation warning in deprecation-copopen in new window.
    + + + diff --git a/docs/atom-archive/shadow-dom/sections/removing-shadow-dom-styles.html b/docs/atom-archive/shadow-dom/sections/removing-shadow-dom-styles.html new file mode 100644 index 0000000000..9136870850 --- /dev/null +++ b/docs/atom-archive/shadow-dom/sections/removing-shadow-dom-styles.html @@ -0,0 +1,276 @@ + + + + + + + + + + + + + + +

    About 2 min

    Removing Shadow DOM styles

    In Atom 1.13 the Shadow DOM got removed from text editors. For more background on the reasoning, check out the Pull Requestopen in new window where it was removed. In this guide you will learn how to migrate your theme or package.

    Summary

    Here is a quick summary to see all the changes at a glance:

    Before+/-After
    atom-text-editor::shadow {}- ::shadowatom-text-editor {}
    .class /deep/ .class {}- /deep/.class .class {}
    atom-text-editor, :host {}- :hostatom-text-editor {}
    .comment {}+ .syntax--.syntax--comment {}

    Below you'll find more detailed examples.

    UI themes and packages

    ::shadow

    Remove the ::shadow pseudo-element selector from atom-text-editor.

    Before:

    atom-text-editor::shadow .cursor {
    +	border-color: hotpink;
    +}
    +

    After:

    atom-text-editor .cursor {
    +	border-color: hotpink;
    +}
    +
    /deep/

    Remove the /deep/ combinator selector. It didn't get used that often, mainly to customize the scrollbars.

    Before:

    .scrollbars-visible-always /deep/ ::-webkit-scrollbar {
    +	width: 8px;
    +	height: 8px;
    +}
    +

    After:

    .scrollbars-visible-always ::-webkit-scrollbar {
    +	width: 8px;
    +	height: 8px;
    +}
    +

    Syntax themes

    :host

    Remove the :host pseudo-element selector. To scope any styles to the text editor, using atom-text-editor is already enough.

    Before:

    atom-text-editor,
    +:host {
    +	.cursor {
    +		border-color: hotpink;
    +	}
    +}
    +

    After:

    atom-text-editor {
    +	.cursor {
    +		border-color: hotpink;
    +	}
    +}
    +
    syntax--

    Add a syntax-- prefix to all grammar selectors used by language packages. It looks a bit verbose, but it will protect all the grammar selectors from accidental style pollution.

    Before:

    .comment {
    +	color: @mono-3;
    +	font-style: italic;
    +
    +	.markup.link {
    +		color: @mono-3;
    +	}
    +}
    +

    After:

    .syntax--comment {
    +	color: @mono-3;
    +	font-style: italic;
    +
    +	.syntax--markup.syntax--link {
    +		color: @mono-3;
    +	}
    +}
    +

    Note: Selectors like the .gutter, .indent-guide, .cursor among others, that are also part of Syntax themes, don't need a prefix. Only grammar selectors that get used by language packages. For example .syntax--keyword, .syntax--keyword.syntax--operator.syntax--js.

    Context-Targeted Style Sheets

    Atom also allowed you to target a specific shadow DOM context with an entire style sheet by adding .atom-text-editor to the file name. This is now not necessary anymore and can be removed.

    Before:

    my-ui-theme/
    +  styles/
    +    index.atom-text-editor.less
    +

    After:

    my-ui-theme/
    +  styles/
    +    index.less
    +

    I followed the guide, but now my styling is broken!

    By replacing atom-text-editor::shadow with atom-text-editor.editor, specificity might have changed. This can cause the side effect that some of your properties won't be strong enough. To fix that, you can increase specificity on your selector. A simple way is to just repeat your class (in the example below it's .my-class):

    Before:

    atom-text-editor::shadow .my-class {
    +	color: hotpink;
    +}
    +

    After:

    atom-text-editor .my-class.my-class {
    +	color: hotpink;
    +}
    +

    When should I migrate my theme/package?

    • If you already want to test the migration on master or Beta channel, make sure to change your package.json file to "engines": { "atom": ">=1.13.0 <2.0.0" }. This will prevent Atom from updating your theme or package before the user also updates Atom to version 1.13.
    • Or you can wait until Atom 1.13 reaches Stable. Check blog.atom.ioopen in new window to see if 1.13 already has been released. Don't worry if you're a bit late, Atom will transform the deprecated selectors automatically to avoid breaking any themes or packages. But users will start to see a deprecation warning in deprecation-copopen in new window.
    + + + diff --git a/docs/atom-archive/upgrading-to-1-0-apis/index.html b/docs/atom-archive/upgrading-to-1-0-apis/index.html new file mode 100644 index 0000000000..0460ce3da4 --- /dev/null +++ b/docs/atom-archive/upgrading-to-1-0-apis/index.html @@ -0,0 +1,565 @@ + + + + + + + + Appendix D : Upgrading to 1.0 APIs | + + + + + + +

    Appendix D : Upgrading to 1.0 APIs

    Less than 1 minute

    STOP

    This is being kept for archival purposes only from the original Atom documentation. As this may no longer be relevant to Pulsar, use this at your own risk. Current Pulsar documentation is found at documentation home.

    Upgrading to 1.0 APIs

    Note

    Note: Atom has been at v1.x for over a year, so this appendix is mostly obsolete at this point. We're retaining it for historic and reference purposes.

    Atom is at 1.0! Much of the effort leading up to the 1.0 has been cleaning up APIs in an attempt to future proof, and make a more pleasant experience developing packages. If you have developed packages or syntaxes for Atom before the 1.0 API, you can find some tips on upgrading your work in this appendix.

    Upgrading Your Package

    This document will guide you through the large bits of upgrading your package to work with 1.0 APIs.

    TL;DR

    We've set deprecation messages and errors in strategic places to help make sure you don't miss anything. You should be able to get 95% of the way to an updated package just by fixing errors and deprecations. There are a couple of things you can do to get the full effect of all the errors and deprecations.

    Use atom-space-pen-views

    If you use any class from require 'atom' with a $ or View in the name, add the atom-space-pen-views module to your package's package.json file's dependencies:

    {
    +	"dependencies": {
    +		"atom-space-pen-views": "^2.0.3"
    +	}
    +}
    +

    Then run apm install in your package directory.

    Require views from atom-space-pen-views

    Anywhere you are requiring one of the following from atom you need to require them from atom-space-pen-views instead.

    # require these from 'atom-space-pen-views' rather than 'atom'
    +$
    +$$
    +$$$
    +View
    +TextEditorView
    +ScrollView
    +SelectListView
    +

    So this:

    # Old way
    +{$, TextEditorView, View, GitRepository} = require 'atom'
    +

    Would be replaced by this:

    # New way
    +{GitRepository} = require 'atom'
    +{$, TextEditorView, View} = require 'atom-space-pen-views'
    +
    Run specs and test your package

    You wrote specs, right!? Here's where they shine. Run them with cmd-shift-P, and search for run package specs. It will show all the deprecation messages and errors.

    Update the engines field

    When you are deprecation free and all done converting, upgrade the engines field in your package.json:

    {
    +	"engines": {
    +		"atom": ">=0.174.0 <2.0.0"
    +	}
    +}
    +
    Examples

    We have upgraded all the core packages. Please see this issueopen in new window for a link to all the upgrade PRs.

    Deprecations

    All of the methods in Atom core that have changes will emit deprecation messages when called. These messages are shown in two places: your package specs, and in Deprecation Cop.

    Specs

    Just run your specs, and all the deprecations will be displayed in yellow.

    Deprecations in Specs

    Note

    Note: Deprecations are only displayed when executing specs through the "Window: Run Package Specs" command in the Atom UI. Deprecations are not displayed when running specs at the terminal.

    Deprecation Cop

    Run Atom in Dev Mode, atom --dev, with your package loaded, and open Deprecation Cop (search for "deprecation" in the command palette). Deprecated methods will appear in Deprecation Cop only after they have been called.

    Deprecation Cop

    When Deprecation Cop is open, and deprecated methods are called, a Refresh button will appear in the top right of the Deprecation Cop interface. So exercise your package, then come back to Deprecation Cop and click the Refresh button.

    Upgrading your Views

    Previous to 1.0, views were baked into Atom core. These views were based on jQuery and space-pen. They looked something like this:

    # The old way: getting views from atom
    +{$, TextEditorView, View} = require 'atom'
    +
    +module.exports =
    +class SomeView extends View
    +  @content: ->
    +    @div class: 'find-and-replace', =>
    +      @div class: 'block', =>
    +        @subview 'myEditor', new TextEditorView(mini: true)
    +  #...
    +
    The New

    require 'atom' no longer provides view helpers or jQuery. Atom Core is now 'view agnostic'. The preexisting view system is available from a new Node module: atom-space-pen-views.

    atom-space-pen-views now provides jQuery, space-pen views, and Atom specific views:

    # These are now provided by atom-space-pen-views
    +$
    +$$
    +$$$
    +View
    +TextEditorView
    +ScrollView
    +SelectListView
    +
    Adding the module dependencies

    To use the new views, you need to specify the atom-space-pen-views module in your package's package.json file's dependencies:

    {
    +	"dependencies": {
    +		"atom-space-pen-views": "^2.0.3"
    +	}
    +}
    +

    space-pen bundles jQuery. If you do not need space-pen or any of the views, you can require jQuery directly.

    {
    +	"dependencies": {
    +		"jquery": "^2"
    +	}
    +}
    +
    Converting your views

    Sometimes it is as simple as converting the requires at the top of each view page. I assume you read the 'TL;DR' section and have updated all of your requires.

    Upgrading classes extending any space-pen View
    afterAttach and beforeRemove updated

    The afterAttach and beforeRemove hooks have been replaced with attached and detached and the semantics have changed.

    afterAttach was called whenever the node was attached to another DOM node, even if that parent node wasn't present in the DOM. afterAttach also was called with a boolean indicating whether or not the element and its parents were on the DOM. Now the attached hook is only called when the node and all of its parents are actually on the DOM, and is not called with a boolean.

    beforeRemove was only called when $.fn.remove was called, which was typically used when the node was completely removed from the DOM. The new detached hook is called whenever the DOM node is detached, which could happen if the node is being detached for reattachment later. In short, if beforeRemove is called the node is never coming back. With detached it might be attached again later.

    # Old way
    +{View} = require 'atom'
    +class MyView extends View
    +  afterAttach: (onDom) ->
    +    #...
    +
    +  beforeRemove: ->
    +    #...
    +
    # New way
    +{View} = require 'atom-space-pen-views'
    +class MyView extends View
    +  attached: ->
    +    # Always called with the equivalent of @afterAttach(true)!
    +    #...
    +
    +  detached: ->
    +    #...
    +
    subscribe and subscribeToCommand methods removed

    The subscribe and subscribeToCommand methods have been removed. See the Eventing and Disposables section for more info.

    Upgrading to the new TextEditorView

    All of the atom-specific methods available on the TextEditorView have been moved to the TextEditor, available via TextEditorView::getModel. See the TextEditorView docsopen in new window and TextEditor docsopen in new window for more info.

    Upgrading classes extending ScrollView

    The ScrollView has very minor changes.

    You can no longer use @off to remove default behavior for core:move-up, core:move-down, etc.

    # Old way to turn off default behavior
    +class ResultsView extends ScrollView
    +  initialize: (@model) ->
    +    super()
    +    # turn off default scrolling behavior from ScrollView
    +    @off 'core:move-up'
    +    @off 'core:move-down'
    +    @off 'core:move-left'
    +    @off 'core:move-right'
    +
    # New way to turn off default behavior
    +class ResultsView extends ScrollView
    +  initialize: (@model) ->
    +    disposable = super()
    +    # turn off default scrolling behavior from ScrollView
    +    disposable.dispose()
    +
    Upgrading classes extending SelectListView

    Your SelectListView might look something like this:

    # Old!
    +class CommandPaletteView extends SelectListView
    +  initialize: ->
    +    super()
    +    @addClass('command-palette overlay from-top')
    +    atom.workspaceView.command 'command-palette:toggle', => @toggle()
    +
    +  confirmed: ({name, jQuery}) ->
    +    @cancel()
    +    # do something with the result
    +
    +  toggle: ->
    +    if @hasParent()
    +      @cancel()
    +    else
    +      @attach()
    +
    +  attach: ->
    +    @storeFocusedElement()
    +
    +    items = [] # TODO: build items
    +    @setItems(items)
    +
    +    atom.workspaceView.append(this)
    +    @focusFilterEditor()
    +
    +  confirmed: ({name, jQuery}) ->
    +    @cancel()
    +

    This attaches and detaches itself from the DOM when toggled, canceling magically detaches it from the DOM, and it uses the classes overlay and from-top.

    The new SelectListView no longer automatically detaches itself from the DOM when cancelled. It's up to you to implement whatever cancel behavior you want. Using the new APIs to mimic the semantics of the old class, it should look like this:

    # New!
    +class CommandPaletteView extends SelectListView
    +  initialize: ->
    +    super()
    +    # no more need for the `overlay` and `from-top` classes
    +    @addClass('command-palette')
    +    atom.commands.add 'atom-workspace', 'command-palette:toggle', => @toggle()
    +
    +  # You need to implement the `cancelled` method and hide.
    +  cancelled: ->
    +    @hide()
    +
    +  confirmed: ({name, jQuery}) ->
    +    @cancel()
    +    # do something with the result
    +
    +  toggle: ->
    +    # Toggling now checks panel visibility,
    +    # and hides / shows rather than attaching to / detaching from the DOM.
    +    if @panel?.isVisible()
    +      @cancel()
    +    else
    +      @show()
    +
    +  show: ->
    +    # Now you will add your select list as a modal panel to the workspace
    +    @panel ?= atom.workspace.addModalPanel(item: this)
    +    @panel.show()
    +
    +    @storeFocusedElement()
    +
    +    items = [] # TODO: build items
    +    @setItems(items)
    +
    +    @focusFilterEditor()
    +
    +  hide: ->
    +    @panel?.hide()
    +

    Using the model layer rather than the view layer

    The API no longer exposes any specialized view objects or view classes. atom.workspaceView, and all the view classes: WorkspaceView, EditorView, PaneView, etc. have been globally deprecated.

    Nearly all of the atom-specific actions performed by the old view objects can now be managed via the model layer. For example, here's adding a panel to the interface using the atom.workspace model instead of the workspaceView:

    # Old!
    +div = document.createElement('div')
    +atom.workspaceView.appendToTop(div)
    +
    # New!
    +div = document.createElement('div')
    +atom.workspace.addTopPanel(item: div)
    +

    For actions that still require the view, such as dispatching commands or munging css classes, you'll access the view via the atom.views.getView() method. This will return a subclass of HTMLElement rather than a jQuery object or an instance of a deprecated view class (e.g. WorkspaceView).

    # Old!
    +workspaceView = atom.workspaceView
    +editorView = workspaceView.getActiveEditorView()
    +paneView = editorView.getPaneView()
    +
    # New!
    +# Generally, just use the models
    +workspace = atom.workspace
    +editor = workspace.getActiveTextEditor()
    +pane = editor.getPane()
    +
    +# If you need views, get them with `getView`
    +workspaceElement = atom.views.getView(atom.workspace)
    +editorElement = atom.views.getView(editor)
    +paneElement = atom.views.getView(pane)
    +

    Updating Specs

    atom.workspaceView, the WorkspaceView class and the EditorView class have been deprecated. These two objects are used heavily throughout specs, mostly to dispatch events and commands. This section will explain how to remove them while still retaining the ability to dispatch events and commands.

    Removing WorkspaceView references

    WorkspaceView has been deprecated. Everything you could do on the view, you can now do on the Workspace model.

    Requiring WorkspaceView from atom and accessing any methods on it will throw a deprecation warning. Many specs lean heavily on WorkspaceView to trigger commands and fetch EditorView objects.

    Your specs might contain something like this:

    # Old!
    +{WorkspaceView} = require 'atom'
    +describe 'FindView', ->
    +  beforeEach ->
    +    atom.workspaceView = new WorkspaceView()
    +

    Instead, we will use the atom.views.getView() method. This will return a plain HTMLElement, not a WorkspaceView or jQuery object.

    # New!
    +describe 'FindView', ->
    +  workspaceElement = null
    +  beforeEach ->
    +    workspaceElement = atom.views.getView(atom.workspace)
    +
    Attaching the workspace to the DOM

    The workspace needs to be attached to the DOM in some cases. For example, view hooks only work (attached() on View, attachedCallback() on custom elements) when there is a descendant attached to the DOM.

    You might see this in your specs:

    # Old!
    +atom.workspaceView.attachToDom()
    +

    Change it to:

    # New!
    +jasmine.attachToDOM(workspaceElement)
    +
    Removing EditorView references

    Like WorkspaceView, EditorView has been deprecated. Everything you needed to do on the view you are now able to do on the TextEditor model.

    In many cases, you will not even need to get the editor's view anymore. Any of those instances should be updated to use the TextEditor instance instead. You should really only need the editor's view when you plan on triggering a command on the view in a spec.

    Your specs might contain something like this:

    # Old!
    +describe 'Something', ->
    +  [editorView] = []
    +  beforeEach ->
    +    editorView = atom.workspaceView.getActiveView()
    +

    We're going to use atom.views.getView() again to get the editor element. As in the case of the workspaceElement, getView will return a subclass of HTMLElement rather than an EditorView or jQuery object.

    # New!
    +describe 'Something', ->
    +  [editor, editorElement] = []
    +  beforeEach ->
    +    editor = atom.workspace.getActiveTextEditor()
    +    editorElement = atom.views.getView(editor)
    +
    Dispatching commands

    Since the editorElement objects are no longer jQuery objects, they no longer support trigger(). Additionally, Atom has a new command dispatcher, atom.commands, that we use rather than commandeering jQuery's trigger method.

    From this:

    # Old!
    +workspaceView.trigger 'a-package:toggle'
    +editorView.trigger 'find-and-replace:show'
    +

    To this:

    # New!
    +atom.commands.dispatch workspaceElement, 'a-package:toggle'
    +atom.commands.dispatch editorElement, 'find-and-replace:show'
    +

    Eventing and Disposables

    A couple large things changed with respect to events:

    1. All model events are now exposed as event subscription methods that return Disposableopen in new window objects
    2. The subscribe() method is no longer available on space-pen View objects
    3. An Emitter is now provided from require 'atom'
    Consuming Events

    All events from the Atom API are now methods that return a Disposableopen in new window object, on which you can call dispose() to unsubscribe.

    # Old!
    +editor.on 'changed', ->
    +
    # New!
    +disposable = editor.onDidChange ->
    +
    +# You can unsubscribe at some point in the future via `dispose()`
    +disposable.dispose()
    +

    Deprecation warnings will guide you toward the correct methods.

    Using a CompositeDisposable

    You can group multiple disposables into a single disposable with a CompositeDisposable.

    {CompositeDisposable} = require 'atom'
    +
    +class Something
    +  constructor: ->
    +    editor = atom.workspace.getActiveTextEditor()
    +    @disposables = new CompositeDisposable
    +    @disposables.add editor.onDidChange ->
    +    @disposables.add editor.onDidChangePath ->
    +
    +  destroy: ->
    +    @disposables.dispose()
    +
    Removing View::subscribe and Subscriber::subscribe calls

    There were a couple permutations of subscribe(). In these examples, a CompositeDisposable is used as it will commonly be useful where conversion is necessary.

    subscribe(unsubscribable)

    This one is very straight forward.

    # Old!
    +@subscribe editor.on 'changed', ->
    +
    # New!
    +disposables = new CompositeDisposable
    +disposables.add editor.onDidChange ->
    +
    subscribe(modelObject, event, method)

    When the modelObject is an Atom model object, the change is very simple. Just use the correct event method, and add it to your CompositeDisposable.

    # Old!
    +@subscribe editor, 'changed', ->
    +
    # New!
    +disposables = new CompositeDisposable
    +disposables.add editor.onDidChange ->
    +
    subscribe(jQueryObject, selector(optional), event, method)

    Things are a little more complicated when subscribing to a DOM or jQuery element. Atom no longer provides helpers for subscribing to elements. You can use jQuery or the native DOM APIs, whichever you prefer.

    # Old!
    +@subscribe $(window), 'focus', ->
    +
    # New!
    +{Disposable, CompositeDisposable} = require 'atom'
    +disposables = new CompositeDisposable
    +
    +# New with jQuery
    +focusCallback = ->
    +$(window).on 'focus', focusCallback
    +disposables.add new Disposable ->
    +  $(window).off 'focus', focusCallback
    +
    +# New with native APIs
    +focusCallback = ->
    +window.addEventListener 'focus', focusCallback
    +disposables.add new Disposable ->
    +  window.removeEventListener 'focus', focusCallback
    +
    Providing Events: Using the Emitter

    You no longer need to require emissary to get an emitter. We now provide an Emitter class from require 'atom'. We have a specific pattern for use of the Emitter. Rather than mixing it in, we instantiate a member variable, and create explicit subscription methods. For more information see the Emitter docsopen in new window.

    # New!
    +{Emitter} = require 'atom'
    +
    +class Something
    +  constructor: ->
    +    @emitter = new Emitter
    +
    +  destroy: ->
    +    @emitter.dispose()
    +
    +  onDidChange: (callback) ->
    +    @emitter.on 'did-change', callback
    +
    +  methodThatFiresAChange: ->
    +    @emitter.emit 'did-change', {data: 2}
    +
    +# Using the evented class
    +something = new Something
    +something.onDidChange (eventObject) ->
    +  console.log eventObject.data # => 2
    +something.methodThatFiresAChange()
    +

    Subscribing To Commands

    $.fn.command and View::subscribeToCommand are no longer available. Now we use atom.commands.add, and collect the results in a CompositeDisposable. See the docsopen in new window for more info.

    # Old!
    +atom.workspaceView.command 'core:close core:cancel', ->
    +
    +# When inside a View class, you might see this
    +@subscribeToCommand 'core:close core:cancel', ->
    +
    # New!
    +@disposables.add atom.commands.add 'atom-workspace',
    +  'core:close': ->
    +  'core:cancel': ->
    +
    +# You can register commands directly on individual DOM elements in addition to
    +# using selectors. When in a View class, you should have a `@element` object
    +# available. `@element` is a plain HTMLElement object
    +@disposables.add atom.commands.add @element,
    +  'core:close': ->
    +  'core:cancel': ->
    +

    Upgrading your stylesheet's selectors

    Many selectors have changed, and we have introduced the Shadow DOMopen in new window to the editor. See the Upgrading Your UI Theme And Package Selectors guide for more information in upgrading your package stylesheets.

    Note

    Note: The Shadow DOM was removed in Atom 1.13. The ::shadow and /deep/ selectors and the context-targeted style sheets described below won't work and should not be used anymore.

    Upgrading Your UI Theme Or Package Selectors

    In addition to changes in Atom's scripting API, we'll also be making some breaking changes to Atom's DOM structure, requiring style sheets and keymaps in both packages and themes to be updated.

    Deprecation Cop

    Deprecation Cop will list usages of deprecated selector patterns to guide you. You can access it via the Command Palette (cmd-shift-p, then search for Deprecation). It breaks the deprecations down by package:

    Deprecation Cop

    Custom Tags

    Rather than adding classes to standard HTML elements to indicate their role, Atom now uses custom element names. For example, <div class="workspace"> has now been replaced with <atom-workspace>. Selectors should be updated accordingly. Note that tag names have lower specificity than classes in CSS, so you'll need to take care in converting things.

    Old SelectorNew Selector
    .editoratom-text-editor
    .editor.miniatom-text-editor[mini]
    .workspaceatom-workspace
    .horizontalatom-workspace-axis.horizontal
    .verticalatom-workspace-axis.vertical
    .pane-containeratom-pane-container
    .paneatom-pane
    .tool-panelatom-panel
    .panel-topatom-panel.top
    .panel-bottomatom-panel.bottom
    .panel-leftatom-panel.left
    .panel-rightatom-panel.right
    .overlayatom-panel.modal

    Supporting the Shadow DOM

    Text editor content is now rendered in the shadow DOM, which shields it from being styled by global style sheets to protect against accidental style pollution. For more background on the shadow DOM, check out the Shadow DOM 101open in new window on HTML 5 Rocks. If you need to style text editor content in a UI theme, you'll need to circumvent this protection for any rules that target the text editor's content. Some examples of the kinds of UI theme styles needing to be updated:

    • Highlight decorations
    • Gutter decorations
    • Line decorations
    • Scrollbar styling
    • Anything targeting a child selector of .editor

    During a transition phase, it will be possible to enable or disable the text editor's shadow DOM in the settings, so themes will need to be compatible with both approaches.

    Shadow DOM Selectors

    Chromium provides two tools for bypassing shadow boundaries, the ::shadow pseudo-element and the /deep/ combinator. For an in-depth explanation of styling the shadow DOM, see the Shadow DOM 201open in new window article on HTML 5 Rocks.

    ::shadow

    The ::shadow pseudo-element allows you to bypass a single shadow root. For example, say you want to update a highlight decoration for a linter package. Initially, the style looks as follows:

    // Without shadow DOM support
    +atom-text-editor .highlight.my-linter {
    +	background: hotpink;
    +}
    +

    In order for this style to apply with the shadow DOM enabled, you will need to add a second selector with the ::shadow pseudo-element. You should leave the original selector in place so your theme continues to work with the shadow DOM disabled during the transition period.

    // With shadow DOM support
    +atom-text-editor .highlight.my-linter,
    +atom-text-editor::shadow .highlight.my-linter {
    +	background: hotpink;
    +}
    +

    Check out the find-and-replaceopen in new window package for another example of using ::shadow to pierce the shadow DOM.

    /deep/

    The /deep/ combinator overrides all shadow boundaries, making it useful for rules you want to apply globally such as scrollbar styling. Here's a snippet containing scrollbar styling for the Atom Dark UI theme before shadow DOM support:

    // Without shadow DOM support
    +.scrollbars-visible-always {
    +	::-webkit-scrollbar {
    +		width: 8px;
    +		height: 8px;
    +	}
    +
    +	::-webkit-scrollbar-track,
    +	::-webkit-scrollbar-corner {
    +		background: @scrollbar-background-color;
    +	}
    +
    +	::-webkit-scrollbar-thumb {
    +		background: @scrollbar-color;
    +		border-radius: 5px;
    +		box-shadow: 0 0 1px black inset;
    +	}
    +}
    +

    To style scrollbars even inside of the shadow DOM, each rule needs to be prefixed with /deep/. We use /deep/ instead of ::shadow because we don't care about the selector of the host element in this case. We just want our styling to apply everywhere.

    // With shadow DOM support using /deep/
    +.scrollbars-visible-always {
    +	/deep/ ::-webkit-scrollbar {
    +		width: 8px;
    +		height: 8px;
    +	}
    +
    +	/deep/ ::-webkit-scrollbar-track,
    +	/deep/ ::-webkit-scrollbar-corner {
    +		background: @scrollbar-background-color;
    +	}
    +
    +	/deep/ ::-webkit-scrollbar-thumb {
    +		background: @scrollbar-color;
    +		border-radius: 5px;
    +		box-shadow: 0 0 1px black inset;
    +	}
    +}
    +
    Context-Targeted Style Sheets

    The selector features discussed above allow you to target shadow DOM content with specific selectors, but Atom also allows you to target a specific shadow DOM context with an entire style sheet. The context into which a style sheet is loaded is based on the file name. If you want to load a style sheet into the editor, name it with the .atom-text-editor.less or .atom-text-editor.css extensions.

    my-ui-theme/
    +  styles/
    +    index.less                   # loaded globally
    +    index.atom-text-editor.less  # loaded in the text editor shadow DOM
    +

    Check out this style sheetopen in new window from the decoration-example package for an example of context-targeting.

    Inside a context-targeted style sheet, there's no need to use the ::shadow or /deep/ expressions. If you want to refer to the element containing the shadow root, you can use the ::host pseudo-element.

    During the transition phase, style sheets targeting the atom-text-editor context will also be loaded globally. Make sure you update your selectors in a way that maintains compatibility with the shadow DOM being disabled. That means if you use a ::host pseudo element, you should also include the same style rule matches against atom-text-editor.

    Note

    Note: The Shadow DOM was removed in Atom 1.13. The :host selector described below won't work and should not be used anymore.

    Upgrading Your Syntax Theme

    Text editor content is now rendered in the shadow DOM, which shields it from being styled by global style sheets to protect against accidental style pollution. For more background on the shadow DOM, check out the Shadow DOM 101open in new window on HTML 5 Rocks.

    Syntax themes are specifically intended to style only text editor content, so they are automatically loaded directly into the text editor's shadow DOM when it is enabled. This happens automatically when the theme's package.json contains a theme: "syntax" declaration, so you don't need to change anything to target the appropriate context.

    When theme style sheets are loaded into the text editor's shadow DOM, selectors intended to target the editor from the outside no longer make sense. Styles targeting the .editor and .editor-colors classes instead need to target the :host pseudo-element, which matches against the containing atom-text-editor node. Check out the Shadow DOM 201open in new window article for more information about the :host pseudo-element.

    Here's an example from Atom's light syntax theme. Note that the atom-text-editor selector intended to target the editor from the outside has been retained to allow the theme to keep working during the transition phase when it is possible to disable the shadow DOM.

    atom-text-editor,
    +:host {
    +	/* :host added */
    +	background-color: @syntax-background-color;
    +	color: @syntax-text-color;
    +
    +	.invisible-character {
    +		color: @syntax-invisible-character-color;
    +	}
    +	/* more nested selectors... */
    +}
    +
    + + + diff --git a/docs/atom-archive/upgrading-to-1-0-apis/sections/upgrading-your-package.html b/docs/atom-archive/upgrading-to-1-0-apis/sections/upgrading-your-package.html new file mode 100644 index 0000000000..ea5c3ea3a2 --- /dev/null +++ b/docs/atom-archive/upgrading-to-1-0-apis/sections/upgrading-your-package.html @@ -0,0 +1,505 @@ + + + + + + + + + + + + + + +

    About 9 min

    Upgrading Your Package

    This document will guide you through the large bits of upgrading your package to work with 1.0 APIs.

    TL;DR

    We've set deprecation messages and errors in strategic places to help make sure you don't miss anything. You should be able to get 95% of the way to an updated package just by fixing errors and deprecations. There are a couple of things you can do to get the full effect of all the errors and deprecations.

    Use atom-space-pen-views

    If you use any class from require 'atom' with a $ or View in the name, add the atom-space-pen-views module to your package's package.json file's dependencies:

    {
    +	"dependencies": {
    +		"atom-space-pen-views": "^2.0.3"
    +	}
    +}
    +

    Then run apm install in your package directory.

    Require views from atom-space-pen-views

    Anywhere you are requiring one of the following from atom you need to require them from atom-space-pen-views instead.

    # require these from 'atom-space-pen-views' rather than 'atom'
    +$
    +$$
    +$$$
    +View
    +TextEditorView
    +ScrollView
    +SelectListView
    +

    So this:

    # Old way
    +{$, TextEditorView, View, GitRepository} = require 'atom'
    +

    Would be replaced by this:

    # New way
    +{GitRepository} = require 'atom'
    +{$, TextEditorView, View} = require 'atom-space-pen-views'
    +
    Run specs and test your package

    You wrote specs, right!? Here's where they shine. Run them with cmd-shift-P, and search for run package specs. It will show all the deprecation messages and errors.

    Update the engines field

    When you are deprecation free and all done converting, upgrade the engines field in your package.json:

    {
    +	"engines": {
    +		"atom": ">=0.174.0 <2.0.0"
    +	}
    +}
    +
    Examples

    We have upgraded all the core packages. Please see this issueopen in new window for a link to all the upgrade PRs.

    Deprecations

    All of the methods in Atom core that have changes will emit deprecation messages when called. These messages are shown in two places: your package specs, and in Deprecation Cop.

    Specs

    Just run your specs, and all the deprecations will be displayed in yellow.

    Deprecations in Specs

    Note

    Note: Deprecations are only displayed when executing specs through the "Window: Run Package Specs" command in the Atom UI. Deprecations are not displayed when running specs at the terminal.

    Deprecation Cop

    Run Atom in Dev Mode, atom --dev, with your package loaded, and open Deprecation Cop (search for "deprecation" in the command palette). Deprecated methods will appear in Deprecation Cop only after they have been called.

    Deprecation Cop

    When Deprecation Cop is open, and deprecated methods are called, a Refresh button will appear in the top right of the Deprecation Cop interface. So exercise your package, then come back to Deprecation Cop and click the Refresh button.

    Upgrading your Views

    Previous to 1.0, views were baked into Atom core. These views were based on jQuery and space-pen. They looked something like this:

    # The old way: getting views from atom
    +{$, TextEditorView, View} = require 'atom'
    +
    +module.exports =
    +class SomeView extends View
    +  @content: ->
    +    @div class: 'find-and-replace', =>
    +      @div class: 'block', =>
    +        @subview 'myEditor', new TextEditorView(mini: true)
    +  #...
    +
    The New

    require 'atom' no longer provides view helpers or jQuery. Atom Core is now 'view agnostic'. The preexisting view system is available from a new Node module: atom-space-pen-views.

    atom-space-pen-views now provides jQuery, space-pen views, and Atom specific views:

    # These are now provided by atom-space-pen-views
    +$
    +$$
    +$$$
    +View
    +TextEditorView
    +ScrollView
    +SelectListView
    +
    Adding the module dependencies

    To use the new views, you need to specify the atom-space-pen-views module in your package's package.json file's dependencies:

    {
    +	"dependencies": {
    +		"atom-space-pen-views": "^2.0.3"
    +	}
    +}
    +

    space-pen bundles jQuery. If you do not need space-pen or any of the views, you can require jQuery directly.

    {
    +	"dependencies": {
    +		"jquery": "^2"
    +	}
    +}
    +
    Converting your views

    Sometimes it is as simple as converting the requires at the top of each view page. I assume you read the 'TL;DR' section and have updated all of your requires.

    Upgrading classes extending any space-pen View
    afterAttach and beforeRemove updated

    The afterAttach and beforeRemove hooks have been replaced with attached and detached and the semantics have changed.

    afterAttach was called whenever the node was attached to another DOM node, even if that parent node wasn't present in the DOM. afterAttach also was called with a boolean indicating whether or not the element and its parents were on the DOM. Now the attached hook is only called when the node and all of its parents are actually on the DOM, and is not called with a boolean.

    beforeRemove was only called when $.fn.remove was called, which was typically used when the node was completely removed from the DOM. The new detached hook is called whenever the DOM node is detached, which could happen if the node is being detached for reattachment later. In short, if beforeRemove is called the node is never coming back. With detached it might be attached again later.

    # Old way
    +{View} = require 'atom'
    +class MyView extends View
    +  afterAttach: (onDom) ->
    +    #...
    +
    +  beforeRemove: ->
    +    #...
    +
    # New way
    +{View} = require 'atom-space-pen-views'
    +class MyView extends View
    +  attached: ->
    +    # Always called with the equivalent of @afterAttach(true)!
    +    #...
    +
    +  detached: ->
    +    #...
    +
    subscribe and subscribeToCommand methods removed

    The subscribe and subscribeToCommand methods have been removed. See the Eventing and Disposables section for more info.

    Upgrading to the new TextEditorView

    All of the atom-specific methods available on the TextEditorView have been moved to the TextEditor, available via TextEditorView::getModel. See the TextEditorView docsopen in new window and TextEditor docsopen in new window for more info.

    Upgrading classes extending ScrollView

    The ScrollView has very minor changes.

    You can no longer use @off to remove default behavior for core:move-up, core:move-down, etc.

    # Old way to turn off default behavior
    +class ResultsView extends ScrollView
    +  initialize: (@model) ->
    +    super()
    +    # turn off default scrolling behavior from ScrollView
    +    @off 'core:move-up'
    +    @off 'core:move-down'
    +    @off 'core:move-left'
    +    @off 'core:move-right'
    +
    # New way to turn off default behavior
    +class ResultsView extends ScrollView
    +  initialize: (@model) ->
    +    disposable = super()
    +    # turn off default scrolling behavior from ScrollView
    +    disposable.dispose()
    +
    Upgrading classes extending SelectListView

    Your SelectListView might look something like this:

    # Old!
    +class CommandPaletteView extends SelectListView
    +  initialize: ->
    +    super()
    +    @addClass('command-palette overlay from-top')
    +    atom.workspaceView.command 'command-palette:toggle', => @toggle()
    +
    +  confirmed: ({name, jQuery}) ->
    +    @cancel()
    +    # do something with the result
    +
    +  toggle: ->
    +    if @hasParent()
    +      @cancel()
    +    else
    +      @attach()
    +
    +  attach: ->
    +    @storeFocusedElement()
    +
    +    items = [] # TODO: build items
    +    @setItems(items)
    +
    +    atom.workspaceView.append(this)
    +    @focusFilterEditor()
    +
    +  confirmed: ({name, jQuery}) ->
    +    @cancel()
    +

    This attaches and detaches itself from the DOM when toggled, canceling magically detaches it from the DOM, and it uses the classes overlay and from-top.

    The new SelectListView no longer automatically detaches itself from the DOM when cancelled. It's up to you to implement whatever cancel behavior you want. Using the new APIs to mimic the semantics of the old class, it should look like this:

    # New!
    +class CommandPaletteView extends SelectListView
    +  initialize: ->
    +    super()
    +    # no more need for the `overlay` and `from-top` classes
    +    @addClass('command-palette')
    +    atom.commands.add 'atom-workspace', 'command-palette:toggle', => @toggle()
    +
    +  # You need to implement the `cancelled` method and hide.
    +  cancelled: ->
    +    @hide()
    +
    +  confirmed: ({name, jQuery}) ->
    +    @cancel()
    +    # do something with the result
    +
    +  toggle: ->
    +    # Toggling now checks panel visibility,
    +    # and hides / shows rather than attaching to / detaching from the DOM.
    +    if @panel?.isVisible()
    +      @cancel()
    +    else
    +      @show()
    +
    +  show: ->
    +    # Now you will add your select list as a modal panel to the workspace
    +    @panel ?= atom.workspace.addModalPanel(item: this)
    +    @panel.show()
    +
    +    @storeFocusedElement()
    +
    +    items = [] # TODO: build items
    +    @setItems(items)
    +
    +    @focusFilterEditor()
    +
    +  hide: ->
    +    @panel?.hide()
    +

    Using the model layer rather than the view layer

    The API no longer exposes any specialized view objects or view classes. atom.workspaceView, and all the view classes: WorkspaceView, EditorView, PaneView, etc. have been globally deprecated.

    Nearly all of the atom-specific actions performed by the old view objects can now be managed via the model layer. For example, here's adding a panel to the interface using the atom.workspace model instead of the workspaceView:

    # Old!
    +div = document.createElement('div')
    +atom.workspaceView.appendToTop(div)
    +
    # New!
    +div = document.createElement('div')
    +atom.workspace.addTopPanel(item: div)
    +

    For actions that still require the view, such as dispatching commands or munging css classes, you'll access the view via the atom.views.getView() method. This will return a subclass of HTMLElement rather than a jQuery object or an instance of a deprecated view class (e.g. WorkspaceView).

    # Old!
    +workspaceView = atom.workspaceView
    +editorView = workspaceView.getActiveEditorView()
    +paneView = editorView.getPaneView()
    +
    # New!
    +# Generally, just use the models
    +workspace = atom.workspace
    +editor = workspace.getActiveTextEditor()
    +pane = editor.getPane()
    +
    +# If you need views, get them with `getView`
    +workspaceElement = atom.views.getView(atom.workspace)
    +editorElement = atom.views.getView(editor)
    +paneElement = atom.views.getView(pane)
    +

    Updating Specs

    atom.workspaceView, the WorkspaceView class and the EditorView class have been deprecated. These two objects are used heavily throughout specs, mostly to dispatch events and commands. This section will explain how to remove them while still retaining the ability to dispatch events and commands.

    Removing WorkspaceView references

    WorkspaceView has been deprecated. Everything you could do on the view, you can now do on the Workspace model.

    Requiring WorkspaceView from atom and accessing any methods on it will throw a deprecation warning. Many specs lean heavily on WorkspaceView to trigger commands and fetch EditorView objects.

    Your specs might contain something like this:

    # Old!
    +{WorkspaceView} = require 'atom'
    +describe 'FindView', ->
    +  beforeEach ->
    +    atom.workspaceView = new WorkspaceView()
    +

    Instead, we will use the atom.views.getView() method. This will return a plain HTMLElement, not a WorkspaceView or jQuery object.

    # New!
    +describe 'FindView', ->
    +  workspaceElement = null
    +  beforeEach ->
    +    workspaceElement = atom.views.getView(atom.workspace)
    +
    Attaching the workspace to the DOM

    The workspace needs to be attached to the DOM in some cases. For example, view hooks only work (attached() on View, attachedCallback() on custom elements) when there is a descendant attached to the DOM.

    You might see this in your specs:

    # Old!
    +atom.workspaceView.attachToDom()
    +

    Change it to:

    # New!
    +jasmine.attachToDOM(workspaceElement)
    +
    Removing EditorView references

    Like WorkspaceView, EditorView has been deprecated. Everything you needed to do on the view you are now able to do on the TextEditor model.

    In many cases, you will not even need to get the editor's view anymore. Any of those instances should be updated to use the TextEditor instance instead. You should really only need the editor's view when you plan on triggering a command on the view in a spec.

    Your specs might contain something like this:

    # Old!
    +describe 'Something', ->
    +  [editorView] = []
    +  beforeEach ->
    +    editorView = atom.workspaceView.getActiveView()
    +

    We're going to use atom.views.getView() again to get the editor element. As in the case of the workspaceElement, getView will return a subclass of HTMLElement rather than an EditorView or jQuery object.

    # New!
    +describe 'Something', ->
    +  [editor, editorElement] = []
    +  beforeEach ->
    +    editor = atom.workspace.getActiveTextEditor()
    +    editorElement = atom.views.getView(editor)
    +
    Dispatching commands

    Since the editorElement objects are no longer jQuery objects, they no longer support trigger(). Additionally, Atom has a new command dispatcher, atom.commands, that we use rather than commandeering jQuery's trigger method.

    From this:

    # Old!
    +workspaceView.trigger 'a-package:toggle'
    +editorView.trigger 'find-and-replace:show'
    +

    To this:

    # New!
    +atom.commands.dispatch workspaceElement, 'a-package:toggle'
    +atom.commands.dispatch editorElement, 'find-and-replace:show'
    +

    Eventing and Disposables

    A couple large things changed with respect to events:

    1. All model events are now exposed as event subscription methods that return Disposableopen in new window objects
    2. The subscribe() method is no longer available on space-pen View objects
    3. An Emitter is now provided from require 'atom'
    Consuming Events

    All events from the Atom API are now methods that return a Disposableopen in new window object, on which you can call dispose() to unsubscribe.

    # Old!
    +editor.on 'changed', ->
    +
    # New!
    +disposable = editor.onDidChange ->
    +
    +# You can unsubscribe at some point in the future via `dispose()`
    +disposable.dispose()
    +

    Deprecation warnings will guide you toward the correct methods.

    Using a CompositeDisposable

    You can group multiple disposables into a single disposable with a CompositeDisposable.

    {CompositeDisposable} = require 'atom'
    +
    +class Something
    +  constructor: ->
    +    editor = atom.workspace.getActiveTextEditor()
    +    @disposables = new CompositeDisposable
    +    @disposables.add editor.onDidChange ->
    +    @disposables.add editor.onDidChangePath ->
    +
    +  destroy: ->
    +    @disposables.dispose()
    +
    Removing View::subscribe and Subscriber::subscribe calls

    There were a couple permutations of subscribe(). In these examples, a CompositeDisposable is used as it will commonly be useful where conversion is necessary.

    subscribe(unsubscribable)

    This one is very straight forward.

    # Old!
    +@subscribe editor.on 'changed', ->
    +
    # New!
    +disposables = new CompositeDisposable
    +disposables.add editor.onDidChange ->
    +
    subscribe(modelObject, event, method)

    When the modelObject is an Atom model object, the change is very simple. Just use the correct event method, and add it to your CompositeDisposable.

    # Old!
    +@subscribe editor, 'changed', ->
    +
    # New!
    +disposables = new CompositeDisposable
    +disposables.add editor.onDidChange ->
    +
    subscribe(jQueryObject, selector(optional), event, method)

    Things are a little more complicated when subscribing to a DOM or jQuery element. Atom no longer provides helpers for subscribing to elements. You can use jQuery or the native DOM APIs, whichever you prefer.

    # Old!
    +@subscribe $(window), 'focus', ->
    +
    # New!
    +{Disposable, CompositeDisposable} = require 'atom'
    +disposables = new CompositeDisposable
    +
    +# New with jQuery
    +focusCallback = ->
    +$(window).on 'focus', focusCallback
    +disposables.add new Disposable ->
    +  $(window).off 'focus', focusCallback
    +
    +# New with native APIs
    +focusCallback = ->
    +window.addEventListener 'focus', focusCallback
    +disposables.add new Disposable ->
    +  window.removeEventListener 'focus', focusCallback
    +
    Providing Events: Using the Emitter

    You no longer need to require emissary to get an emitter. We now provide an Emitter class from require 'atom'. We have a specific pattern for use of the Emitter. Rather than mixing it in, we instantiate a member variable, and create explicit subscription methods. For more information see the Emitter docsopen in new window.

    # New!
    +{Emitter} = require 'atom'
    +
    +class Something
    +  constructor: ->
    +    @emitter = new Emitter
    +
    +  destroy: ->
    +    @emitter.dispose()
    +
    +  onDidChange: (callback) ->
    +    @emitter.on 'did-change', callback
    +
    +  methodThatFiresAChange: ->
    +    @emitter.emit 'did-change', {data: 2}
    +
    +# Using the evented class
    +something = new Something
    +something.onDidChange (eventObject) ->
    +  console.log eventObject.data # => 2
    +something.methodThatFiresAChange()
    +

    Subscribing To Commands

    $.fn.command and View::subscribeToCommand are no longer available. Now we use atom.commands.add, and collect the results in a CompositeDisposable. See the docsopen in new window for more info.

    # Old!
    +atom.workspaceView.command 'core:close core:cancel', ->
    +
    +# When inside a View class, you might see this
    +@subscribeToCommand 'core:close core:cancel', ->
    +
    # New!
    +@disposables.add atom.commands.add 'atom-workspace',
    +  'core:close': ->
    +  'core:cancel': ->
    +
    +# You can register commands directly on individual DOM elements in addition to
    +# using selectors. When in a View class, you should have a `@element` object
    +# available. `@element` is a plain HTMLElement object
    +@disposables.add atom.commands.add @element,
    +  'core:close': ->
    +  'core:cancel': ->
    +

    Upgrading your stylesheet's selectors

    Many selectors have changed, and we have introduced the Shadow DOMopen in new window to the editor. See the Upgrading Your UI Theme And Package Selectors guide for more information in upgrading your package stylesheets.

    + + + diff --git a/docs/atom-archive/upgrading-to-1-0-apis/sections/upgrading-your-syntax-theme.html b/docs/atom-archive/upgrading-to-1-0-apis/sections/upgrading-your-syntax-theme.html new file mode 100644 index 0000000000..2b75acdb63 --- /dev/null +++ b/docs/atom-archive/upgrading-to-1-0-apis/sections/upgrading-your-syntax-theme.html @@ -0,0 +1,234 @@ + + + + + + + + + + + + + + +

    Less than 1 minute

    Note

    Note: The Shadow DOM was removed in Atom 1.13. The :host selector described below won't work and should not be used anymore.

    Upgrading Your Syntax Theme

    Text editor content is now rendered in the shadow DOM, which shields it from being styled by global style sheets to protect against accidental style pollution. For more background on the shadow DOM, check out the Shadow DOM 101open in new window on HTML 5 Rocks.

    Syntax themes are specifically intended to style only text editor content, so they are automatically loaded directly into the text editor's shadow DOM when it is enabled. This happens automatically when the theme's package.json contains a theme: "syntax" declaration, so you don't need to change anything to target the appropriate context.

    When theme style sheets are loaded into the text editor's shadow DOM, selectors intended to target the editor from the outside no longer make sense. Styles targeting the .editor and .editor-colors classes instead need to target the :host pseudo-element, which matches against the containing atom-text-editor node. Check out the Shadow DOM 201open in new window article for more information about the :host pseudo-element.

    Here's an example from Atom's light syntax theme. Note that the atom-text-editor selector intended to target the editor from the outside has been retained to allow the theme to keep working during the transition phase when it is possible to disable the shadow DOM.

    atom-text-editor,
    +:host {
    +	/* :host added */
    +	background-color: @syntax-background-color;
    +	color: @syntax-text-color;
    +
    +	.invisible-character {
    +		color: @syntax-invisible-character-color;
    +	}
    +	/* more nested selectors... */
    +}
    +
    + + + diff --git a/docs/atom-archive/upgrading-to-1-0-apis/sections/upgrading-your-ui-theme-or-package-selectors.html b/docs/atom-archive/upgrading-to-1-0-apis/sections/upgrading-your-ui-theme-or-package-selectors.html new file mode 100644 index 0000000000..fb77e713a6 --- /dev/null +++ b/docs/atom-archive/upgrading-to-1-0-apis/sections/upgrading-your-ui-theme-or-package-selectors.html @@ -0,0 +1,272 @@ + + + + + + + + + + + + + + +

    About 3 min

    Note

    Note: The Shadow DOM was removed in Atom 1.13. The ::shadow and /deep/ selectors and the context-targeted style sheets described below won't work and should not be used anymore.

    Upgrading Your UI Theme Or Package Selectors

    In addition to changes in Atom's scripting API, we'll also be making some breaking changes to Atom's DOM structure, requiring style sheets and keymaps in both packages and themes to be updated.

    Deprecation Cop

    Deprecation Cop will list usages of deprecated selector patterns to guide you. You can access it via the Command Palette (cmd-shift-p, then search for Deprecation). It breaks the deprecations down by package:

    Deprecation Cop

    Custom Tags

    Rather than adding classes to standard HTML elements to indicate their role, Atom now uses custom element names. For example, <div class="workspace"> has now been replaced with <atom-workspace>. Selectors should be updated accordingly. Note that tag names have lower specificity than classes in CSS, so you'll need to take care in converting things.

    Old SelectorNew Selector
    .editoratom-text-editor
    .editor.miniatom-text-editor[mini]
    .workspaceatom-workspace
    .horizontalatom-workspace-axis.horizontal
    .verticalatom-workspace-axis.vertical
    .pane-containeratom-pane-container
    .paneatom-pane
    .tool-panelatom-panel
    .panel-topatom-panel.top
    .panel-bottomatom-panel.bottom
    .panel-leftatom-panel.left
    .panel-rightatom-panel.right
    .overlayatom-panel.modal

    Supporting the Shadow DOM

    Text editor content is now rendered in the shadow DOM, which shields it from being styled by global style sheets to protect against accidental style pollution. For more background on the shadow DOM, check out the Shadow DOM 101open in new window on HTML 5 Rocks. If you need to style text editor content in a UI theme, you'll need to circumvent this protection for any rules that target the text editor's content. Some examples of the kinds of UI theme styles needing to be updated:

    • Highlight decorations
    • Gutter decorations
    • Line decorations
    • Scrollbar styling
    • Anything targeting a child selector of .editor

    During a transition phase, it will be possible to enable or disable the text editor's shadow DOM in the settings, so themes will need to be compatible with both approaches.

    Shadow DOM Selectors

    Chromium provides two tools for bypassing shadow boundaries, the ::shadow pseudo-element and the /deep/ combinator. For an in-depth explanation of styling the shadow DOM, see the Shadow DOM 201open in new window article on HTML 5 Rocks.

    ::shadow

    The ::shadow pseudo-element allows you to bypass a single shadow root. For example, say you want to update a highlight decoration for a linter package. Initially, the style looks as follows:

    // Without shadow DOM support
    +atom-text-editor .highlight.my-linter {
    +	background: hotpink;
    +}
    +

    In order for this style to apply with the shadow DOM enabled, you will need to add a second selector with the ::shadow pseudo-element. You should leave the original selector in place so your theme continues to work with the shadow DOM disabled during the transition period.

    // With shadow DOM support
    +atom-text-editor .highlight.my-linter,
    +atom-text-editor::shadow .highlight.my-linter {
    +	background: hotpink;
    +}
    +

    Check out the find-and-replaceopen in new window package for another example of using ::shadow to pierce the shadow DOM.

    /deep/

    The /deep/ combinator overrides all shadow boundaries, making it useful for rules you want to apply globally such as scrollbar styling. Here's a snippet containing scrollbar styling for the Atom Dark UI theme before shadow DOM support:

    // Without shadow DOM support
    +.scrollbars-visible-always {
    +	::-webkit-scrollbar {
    +		width: 8px;
    +		height: 8px;
    +	}
    +
    +	::-webkit-scrollbar-track,
    +	::-webkit-scrollbar-corner {
    +		background: @scrollbar-background-color;
    +	}
    +
    +	::-webkit-scrollbar-thumb {
    +		background: @scrollbar-color;
    +		border-radius: 5px;
    +		box-shadow: 0 0 1px black inset;
    +	}
    +}
    +

    To style scrollbars even inside of the shadow DOM, each rule needs to be prefixed with /deep/. We use /deep/ instead of ::shadow because we don't care about the selector of the host element in this case. We just want our styling to apply everywhere.

    // With shadow DOM support using /deep/
    +.scrollbars-visible-always {
    +	/deep/ ::-webkit-scrollbar {
    +		width: 8px;
    +		height: 8px;
    +	}
    +
    +	/deep/ ::-webkit-scrollbar-track,
    +	/deep/ ::-webkit-scrollbar-corner {
    +		background: @scrollbar-background-color;
    +	}
    +
    +	/deep/ ::-webkit-scrollbar-thumb {
    +		background: @scrollbar-color;
    +		border-radius: 5px;
    +		box-shadow: 0 0 1px black inset;
    +	}
    +}
    +
    Context-Targeted Style Sheets

    The selector features discussed above allow you to target shadow DOM content with specific selectors, but Atom also allows you to target a specific shadow DOM context with an entire style sheet. The context into which a style sheet is loaded is based on the file name. If you want to load a style sheet into the editor, name it with the .atom-text-editor.less or .atom-text-editor.css extensions.

    my-ui-theme/
    +  styles/
    +    index.less                   # loaded globally
    +    index.atom-text-editor.less  # loaded in the text editor shadow DOM
    +

    Check out this style sheetopen in new window from the decoration-example package for an example of context-targeting.

    Inside a context-targeted style sheet, there's no need to use the ::shadow or /deep/ expressions. If you want to refer to the element containing the shadow root, you can use the ::host pseudo-element.

    During the transition phase, style sheets targeting the atom-text-editor context will also be loaded globally. Make sure you update your selectors in a way that maintains compatibility with the shadow DOM being disabled. That means if you use a ::host pseudo element, you should also include the same style rule matches against atom-text-editor.

    + + + diff --git a/docs/atom-archive/using-atom/index.html b/docs/atom-archive/using-atom/index.html new file mode 100644 index 0000000000..d1dc487285 --- /dev/null +++ b/docs/atom-archive/using-atom/index.html @@ -0,0 +1,359 @@ + + + + + + + + Chapter 2 : Using Atom | + + + + + + +

    Chapter 2 : Using Atom

    Less than 1 minute

    STOP

    This is being kept for archival purposes only from the original Atom documentation. As this may no longer be relevant to Pulsar, you use this at your own risk. Current Pulsar documentation for this section is found at the documentation home.

    Using Atom

    Now that we've covered the very basics of Atom, we are ready to see how to really start getting the most out of using it. In this chapter we'll look at how to find and install new packages in order to add new functionality, how to find and install new themes, how to work with and manipulate text in a more advanced way, how to customize the editor in any way you wish, how to work with Git for version control and more.

    At the end of this chapter, you should have a customized environment that you're comfortable in and you should be able to change, create and move through your text like a master.

    Atom Packages

    First we'll start with the Atom package system. As we mentioned previously, Atom itself is a very basic core of functionality that ships with a number of useful packages that add new features like the Tree Viewopen in new window and the Settings Viewopen in new window.

    In fact, there are more than 80 packages that comprise all of the functionality that is available in Atom by default. For example, the Welcome screenopen in new window that you see when you first start Atom, the spell checkeropen in new window, the themesopen in new window and the Fuzzy Finderopen in new window are all packages that are separately maintained and all use the same APIs that you have access to, as we'll see in great detail in Hacking Atom.

    This means that packages can be incredibly powerful and can change everything from the very look and feel of the entire interface to the basic operation of even core functionality.

    In order to install a new package, you can use the Install tab in the now familiar Settings View. Open up the Settings View using Cmd+,Ctrl+,, click on the "Install" tab and type your search query into the box under Install Packages.

    The packages listed here have been published to https://atom.io/packages which is the official registry for Atom packages. Searching on the Settings View will go to the Atom package registry and pull in anything that matches your search terms.

    Package install screen

    All of the packages will come up with an "Install" button. Clicking that will download the package and install it. Your editor will now have the functionality that the package provides.

    Package Settings

    Once a package is installed in Atom, it will show up in the Settings View under the "Packages" tab, along with all the preinstalled packages that come with Atom. To filter the list in order to find one, you can type into search box directly under the "Installed Packages" heading.

    Package settings screen

    Clicking on the "Settings" button for a package will give you the settings screen for that package specifically. Here you have the option of changing some of the default variables for the package, seeing what all the command keybindings are, disabling the package temporarily, looking at the source code, seeing the current version of the package, reporting issues and uninstalling the package.

    If a new version of any of your packages is released, Atom will automatically detect it and you can upgrade the package from either this screen or from the "Updates" tab. This helps you easily keep all your installed packages up to date.

    Atom Themes

    You can also find and install new themes for Atom from the Settings View. These can be either UI themes or syntax themes and you can search for them from the "Install" tab, just like searching for new packages. Make sure to press the "Themes" toggle next to the search box.

    Theme search screen

    Clicking on the theme title will take you to a profile page for the theme on atom.io, which often has a screenshot of the theme. This way you can see what it looks like before installing it.

    Clicking on "Install" will install the theme and make it available in the Theme dropdowns as we saw in Changing the Theme.

    Example of the Unity UI theme with Monokai syntax theme

    Command Line

    You can also install packages or themes from the command line using apm.

    Tip

    Check that you have apm installed by running the following command in your terminal:

    $ apm help install
    +

    You should see a message print out with details about the apm install command.

    If you do not, see the Installing Atom section for instructions on how to install the atom and apm commands for your system.

    You can also install packages by using the apm install command:

    • apm install <package_name> to install the latest version.
    • apm install <package_name>@<package_version> to install a specific version.

    For example apm install emmet@0.1.5 installs the 0.1.5 release of the Emmetopen in new window package.

    You can also use apm to find new packages to install. If you run apm search, you can search the package registry for a search term.

    $ apm search coffee
    +> Search Results For 'coffee' (29)
    +> ├── build-coffee Atom Build provider for coffee, compiles CoffeeScript (1160 downloads, 2 stars)
    +> ├── scallahan-coffee-syntax A coffee inspired theme from the guys over at S.CALLAHAN (183 downloads, 0 stars)
    +> ├── coffee-paste Copy/Paste As : Js ➤ Coffee / Coffee ➤ Js (902 downloads, 4 stars)
    +> ├── atom-coffee-repl Coffee REPL for Atom Editor (894 downloads, 2 stars)
    +> ├── coffee-navigator Code navigation panel for Coffee Script (3493 downloads, 22 stars)
    +> ...
    +> ├── language-iced-coffeescript Iced coffeescript for atom (202 downloads, 1 star)
    +> └── slontech-syntax Dark theme for web developers ( HTML, CSS/LESS, PHP, MYSQL, javascript, AJAX, coffee, JSON ) (2018 downloads, 3 stars)
    +

    You can use apm view to see more information about a specific package.

    $ apm view build-coffee
    +> build-coffee
    +> ├── 0.6.4
    +> ├── https://github.com/idleberg/atom-build-coffee
    +> ├── Atom Build provider for coffee, compiles CoffeeScript
    +> ├── 1152 downloads
    +> └── 2 stars
    +>
    +> Run `apm install build-coffee` to install this package.
    +

    Moving in Atom

    While it's pretty easy to move around Atom by clicking with the mouse or using the arrow keys, there are some keybindings that may help you keep your hands on the keyboard and navigate around a little faster.

    • Alt+Left or Alt+BCtrl+Left - Move to the beginning of word
    • Alt+Right or Alt+FCtrl+Right - Move to the end of word
    • Cmd+Left or Ctrl+AHome - Move to the first character of the current line
    • Cmd+Right or Ctrl+EEnd - Move to the end of the line
    • Cmd+UpCtrl+Home - Move to the top of the file
    • Cmd+DownCtrl+End - Move to the bottom of the file

    You can also move directly to a specific line (and column) number with Ctrl+G. This will bring up a dialog that asks which line you would like to jump to. You can also use the row:column syntax to jump to a character in that line as well.

    Go directly to a line

    Additional Movement and Selection Commands

    Atom also has a few movement and selection commands that don't have keybindings by default. You can access these commands from the Command Palette, but if you find yourself using commands that don't have a keybinding often, have no fear! You can easily add an entry to your keymap.cson to create a key combination. You can open keymap.cson file in an editor from the Atom > KeymapFile > KeymapEdit > Keymap menu.

    For example, the command editor:move-to-beginning-of-screen-line is available in the command palette, but it's not bound to any key combination. To create a key combination you need to add an entry in your keymap.cson file. For editor:select-to-previous-word-boundary, you can add the following to your keymap.cson:

    This will bind the command editor:select-to-previous-word-boundary to Cmd+Shift+ECtrl+Shift+E. For more information on customizing your keybindings, see Customizing Keybindings.

    Here's a list of Movement and Selection Commands that do not have a keyboard shortcut by default:

    You can also jump around a little more informatively with the Symbols View. To jump to a symbol such as a method definition, press Cmd+RCtrl+R. This opens a list of all symbols in the current file, which you can fuzzy filter similarly to Cmd+TCtrl+T. You can also search for symbols across your project but it requires a tags file.

    Search by symbol across your project

    You can generate a tags file by using the ctags utilityopen in new window. Once it is installed, you can use it to generate a tags file by running a command to generate it. See the ctags documentationopen in new window for details.

    You can customize how tags are generated by creating your own .ctags file in your home directory, ~/.ctags%USERPROFILE%\.ctags. An example can be found hereopen in new window.

    The symbols navigation functionality is implemented in the symbols-viewopen in new window package.

    Bookmarks

    Atom also has a great way to bookmark specific lines in your project so you can jump back to them quickly.

    If you press Cmd+F2Alt+Ctrl+F2Ctrl+Shift+F2, Atom will toggle a "bookmark" on the current line. You can set these throughout your project and use them to quickly find and jump to important lines of your project. A small bookmark symbol is added to the line gutter, like on line 22 of the image below.

    If you hit F2, Atom will jump to the next bookmark in the file you currently have focused. If you use Shift+F2 it will cycle backwards through them instead.

    You can also see a list of all your project's current bookmarks and quickly filter them and jump to any of them by hitting Ctrl+F2.

    ![View and filter bookmarks](@images/atom/bookmarks.png "View and filter bookmarks")

    The bookmarks functionality is implemented in the bookmarksopen in new window package.

    Atom Selections

    Text selections in Atom support a number of actions, such as scoping deletion, indentation and search actions, and marking text for actions such as quoting and bracketing.

    Selections mirror many of the movement commands. They're actually exactly the same keybindings as the movement commands, but with a Shift key added in.

    • Shift+Up or Ctrl+Shift+P - Select up
    • Shift+Down or Ctrl+Shift+N - Select down
    • Shift+Left or Ctrl+Shift+B - Select previous character
    • Shift+Right or Ctrl+Shift+F - Select next character
    • Alt+Shift+LeftCtrl+Shift+Left or Alt+Shift+B - Select to beginning of word
    • Alt+Shift+RightCtrl+Shift+Right or Alt+Shift+F - Select to end of word
    • Cmd+Shift+RightShift+End or Ctrl+Shift+E - Select to end of line
    • Cmd+Shift+LeftShift+Home or Ctrl+Shift+A - Select to first character of line
    • Cmd+Shift+UpCtrl+Shift+Home - Select to top of file
    • Cmd+Shift+DownCtrl+Shift+End - Select to bottom of file

    In addition to the cursor movement selection commands, there are also a few commands that help with selecting specific areas of content.

    • Cmd+ACtrl+A - Select the entire contents of the file
    • Cmd+LCtrl+L - Select the entire line

    Editing and Deleting Text

    So far we've looked at a number of ways to move around and select regions of a file, so now let's actually change some of that text. Obviously you can type in order to insert characters, but there are also a number of ways to delete and manipulate text that could come in handy.

    Basic Manipulation

    There are a handful of cool keybindings for basic text manipulation that might come in handy. These range from moving around lines of text and duplicating lines to changing the case.

    • Cmd+JCtrl+J - Join the next line to the end of the current line
    • Cmd+Ctrl+Up/DownCtrl+Up/Down - Move the current line up or down
    • Cmd+Shift+DCtrl+Shift+D - Duplicate the current line
    • Cmd+K Cmd+UCtrl+K Ctrl+U - Upper case the current word
    • Cmd+K Cmd+LCtrl+K Ctrl+L - Lower case the current word

    Atom also has built in functionality to re-flow a paragraph to hard-wrap at a given maximum line length. You can format the current selection to have lines no longer than 80 (or whatever number editor.preferredLineLength is set to) characters using Alt+Cmd+QAlt+Ctrl+Q. If nothing is selected, the current paragraph will be reflowed.

    Deleting and Cutting

    You can also delete or cut text out of your buffer with some shortcuts. Be ruthless.

    • Ctrl+Shift+K - Delete current line
    • Alt+Backspace or Alt+HCtrl+Backspace - Delete to beginning of word
    • Alt+Delete or Alt+DCtrl+Delete - Delete to end of word

    Multiple Cursors and Selections

    One of the cool things that Atom can do out of the box is support multiple cursors. This can be incredibly helpful in manipulating long lists of text.

    • Cmd+ClickCtrl+Click - Add a new cursor at the clicked location
    • Ctrl+Shift+Up/DownAlt+Ctrl+Up/DownAlt+Shift+Up/Down - Add another cursor above/below the current cursor
    • Cmd+DCtrl+D - Select the next word in the document that is the same as the currently selected word
    • Cmd+Ctrl+GAlt+F3 - Select all words in the document that are the same as the currently selected word

    Using these commands you can place cursors in multiple places in your document and effectively execute the same commands in multiple places at once.

    Using multiple cursors

    This can be incredibly helpful in doing many type of repetitive tasks such as renaming variables or changing the format of some text. You can use this with almost any plugin or command - for example, changing case and moving or duplicating lines.

    You can also use the mouse to select text with the CmdCtrl key pressed down to select multiple regions of your text simultaneously.

    Whitespace

    Atom comes with several commands to help you manage the whitespace in your document. One very useful pair of commands converts leading spaces into tabs and converts leading tabs into spaces. If you're working with a document that has mixed whitespace, these commands are great for helping to normalize the file. There are no keybindings for the whitespace commands, so you will have to search your command palette for "Convert Spaces to Tabs" (or vice versa) to run one of these commands.

    The whitespace commands are implemented in the atom/whitespaceopen in new window package. The settings for the whitespace commands are managed on the page for the whitespace package.

    Managing your whitespace settings

    Note

    The "Remove Trailing Whitespace" option is on by default. This means that every time you save any file opened in Atom, it will strip all trailing whitespace from the file. If you want to disable this, go to the whitespace package in your settings panel and uncheck that option.

    Atom will also by default ensure that your file has a trailing newline. You can also disable this option on that screen.

    Brackets

    Atom ships with intelligent and easy to use bracket handling.

    It will by default highlight [], (), and {} style brackets when your cursor is over them. It will also highlight matching XML and HTML tags.

    Atom will also automatically autocomplete [], (), and {}, "", '', “”, ‘’, «», ‹›, and backticks when you type the leading one. If you have a selection and you type any of these opening brackets or quotes, Atom will enclose the selection with the opening and closing brackets or quotes.

    There are a few other interesting bracket related commands that you can use.

    • Ctrl+M - Jump to the bracket matching the one adjacent to the cursor. It jumps to the nearest enclosing bracket when there's no adjacent bracket.
    • Cmd+Ctrl+MAlt+Ctrl+, - Select all the text inside the current brackets
    • Alt+Cmd+.Alt+Ctrl+. - Close the current XML/HTML tag

    The brackets functionality is implemented in the bracket-matcheropen in new window package. Like all of these packages, to change defaults related to bracket handling, or to disable it entirely, you can navigate to this package in the Settings view.

    Encoding

    Atom also ships with some basic file encoding support should you find yourself working with non-UTF-8 encoded files, or should you wish to create one.

    • Ctrl+Shift+UAlt+U - Toggle menu to change file encoding

    If you pull up the file encoding dialog, you can choose an alternate file encoding to save your file in.

    When you open a file, Atom will try to auto-detect the encoding. If Atom can't identify the encoding, the encoding will default to UTF-8, which is also the default encoding for new files.

    Changing your file encoding

    If you pull up the encoding menu and change the active encoding to something else, the file will be written out in that encoding the next time you save the file.

    The encoding selector is implemented in the encoding-selectoropen in new window package.

    Find and Replace

    Finding and replacing text in your file or project is quick and easy in Atom.

    • Cmd+FCtrl+F - Search within a buffer
    • Cmd+Shift+FCtrl+Shift+F - Search the entire project

    If you launch either of those commands, you'll be greeted with the Find and Replace panel at the bottom of your screen.

    Find and replace text in the current file

    To search within your current file you can press Cmd+FCtrl+F, type in a search string and press Enter (or Cmd+GF3 or the "Find Next" button) multiple times to cycle through all the matches in that file. Alt+Enter will find all occurences of the search string. The Find and Replace panel also contains buttons for toggling case sensitivity, performing regular expression matching, scoping the search to selections, and performing whole word search.

    If you type a string in the replacement text box, you can replace matches with a different string. For example, if you wanted to replace every instance of the string "Scott" with the string "Dragon", you would enter those values in the two text boxes and press the "Replace All" button to perform the replacements.

    Note

    Note: Atom uses JavaScript regular expressions to perform regular expression searches.

    When doing a regular expression search, the replacement syntax to refer back to search groups is $1, $2, … $&. Refer to JavaScript's guide to regular expressionsopen in new window to learn more about regular expression syntax you can use in Atom.

    You can also find and replace throughout your entire project if you invoke the panel with Cmd+Shift+FCtrl+Shift+F.

    Find and replace text in your project

    This is a great way to find out where in your project a function is called, an anchor is linked to or a specific misspelling is located. Click on the matching line to jump to that location in that file.

    You can limit a search to a subset of the files in your project by entering a glob patternopen in new window into the "File/Directory pattern" text box. For example, the pattern src/*.js would restrict the search to JavaScript files in the src directory. The "globstar" pattern (**) can be used to match arbitrarily many subdirectories. For example, docs/**/*.md will match docs/a/foo.md, docs/a/b/foo.md, etc. You can enter multiple glob patterns separated by commas, which is useful for searching in multiple file types or subdirectories.

    When you have multiple project folders open, this feature can also be used to search in only one of those folders. For example, if you had the folders /path1/folder1 and /path2/folder2 open, you could enter a pattern starting with folder1 to search only in the first folder.

    Press Esc while focused on the Find and Replace panel to clear the pane from your workspace.

    The Find and Replace functionality is implemented in the find-and-replaceopen in new window package and uses the scandalopen in new window Node module to do the actual searching.

    Snippets

    Snippets are an incredibly powerful way to quickly generate commonly needed code syntax from a shortcut.

    The idea is that you can type something like habtm and then press the Tab key and it will expand into has_and_belongs_to_many.

    Many Core and Community packages come bundled with their own snippets that are specific to it. For example, the language-html package that provides support for HTML syntax highlighting and grammar comes with dozens of snippets to create many of the various HTML tags you might want to use. If you create a new HTML file in Atom, you can type html and then press Tab and it will expand to:

    <!DOCTYPE html>
    +<html lang="en" dir="ltr">
    +	<head>
    +		<meta charset="utf-8" />
    +		<title></title>
    +	</head>
    +	<body></body>
    +</html>
    +

    It will also position the cursor in the lang attribute value so you can edit it if necessary. Many snippets have multiple focus points that you can move through with the Tab key as well - for instance, in the case of this HTML snippet, after the cursor is placed in the lang attribute value, you can continue pressing Tab and the cursor will move to the dir attribute value, then to the middle of the title tag, then finally to the middle of the body tag.

    To see all the available snippets for the file type that you currently have open, choose "Snippets: Available" in the Command Palette.

    View all available snippets

    You can also use fuzzy search to filter this list down by typing in the selection box. Selecting one of them will execute the snippet where your cursor is (or multiple cursors are).

    Creating Your Own Snippets

    So that's pretty cool, but what if there is something the language package didn't include or something that is custom to the code you write? Luckily it's incredibly easy to add your own snippets.

    There is a text file in your ~/.atom%USERPROFILE%\.atom directory called snippets.cson that contains all your custom snippets that are loaded when you launch Atom. You can also easily open up that file by selecting the Atom > SnippetsEdit > SnippetsFile > Snippets menu.

    Snippet Format

    So let's look at how to write a snippet. The basic snippet format looks like this:

    '.source.js':
    +  'console.log':
    +    'prefix': 'log'
    +    'body': 'console.log(${1:"crash"});$2'
    +

    The leftmost keys are the selectors where these snippets should be active. The easiest way to determine what this should be is to go to the language package of the language you want to add a snippet for and look for the "Scope" string.

    For example, if we wanted to add a snippet that would work for Java files, we would look up the language-java package in our Settings view and we can see the Scope is source.java. Then the top level snippet key would be that prepended by a period (like a CSS class selector would do).

    Finding the selector scope for a snippet

    The next level of keys are the snippet names. These are used for describing the snippet in a more readable way in the snippet menu. You can name them whatever you want.

    Under each snippet name is a prefix that should trigger the snippet and a body to insert when the snippet is triggered.

    Each $ followed by a number is a tab stop. Tab stops are cycled through by pressing Tab once a snippet has been triggered.

    Tab stops with the same number will create multiple cursors.

    The above example adds a log snippet to JavaScript files that would expand to:

    console.log("crash");
    +

    The string "crash" would be initially selected and pressing tab again would place the cursor after the ;

    Warning

    Snippet keys, unlike CSS selectors, can only be repeated once per level. If there are duplicate keys at the same level, then only the last one will be read. See Configuring with CSON for more information.

    Multi-line Snippet Body

    You can also use CoffeeScript multi-line syntaxopen in new window using """ for larger templates:

    '.source.js':
    +  'if, else if, else':
    +    'prefix': 'ieie'
    +    'body': """
    +      if (${1:true}) {
    +        $2
    +      } else if (${3:false}) {
    +        $4
    +      } else {
    +        $5
    +      }
    +    """
    +

    As you might expect, there is a snippet to create snippets. If you open up a snippets file and type snip and then press Tab, you will get the following text inserted:

    '.source.js':
    +  'Snippet Name':
    +    'prefix': 'hello'
    +    'body': 'Hello World!'
    +

    💥 just fill that bad boy out and you have yourself a snippet. As soon as you save the file, Atom should reload the snippets and you will immediately be able to try it out.

    Multiple Snippets per Source

    You can see below the format for including multiple snippets for the same scope in your snippets.cson file. Just include the snippet name, prefix, and body keys for additional snippets inside the scope key:

    '.source.gfm':
    +  'Hello World':
    +    'prefix': 'hewo'
    +    'body': 'Hello World!'
    +
    +  'Github Hello':
    +    'prefix': 'gihe'
    +    'body': 'Octocat says Hi!'
    +
    +  'Octocat Image Link':
    +    'prefix': 'octopic'
    +    'body': '![GitHub Octocat](https://assets-cdn.github.com/images/modules/logos_page/Octocat.png)'
    +

    Again, see Configuring with CSON for more information on CSON key structure and non-repeatability.

    More Info

    The snippets functionality is implemented in the snippetsopen in new window package.

    For more examples, see the snippets in the language-htmlopen in new window and language-javascriptopen in new window packages.

    Autocomplete

    If you're still looking to save some typing time, Atom also ships with simple autocompletion functionality.

    The autocomplete system lets you view and insert possible completions in the editor using Tab or Enter.

    Autocomplete menu

    By default, the autocomplete system will look through the current open file for strings that match what you're starting to type.

    If you want more options, in the Settings panel for the autocomplete-plus package you can toggle a setting to make autocomplete-plus look for text in all your open buffers rather than just the current file.

    The Autocomplete functionality is implemented in the autocomplete-plusopen in new window package.

    Folding

    If you want to see an overview of the structure of the code file you're working on, folding can be a helpful tool. Folding hides blocks of code such as functions or looping blocks in order to simplify what is on your screen.

    You can fold blocks of code by clicking the arrows that appear when you hover your mouse cursor over the gutter. You can also fold and unfold from the keyboard with the Alt+Cmd+[Alt+Ctrl+[ and Alt+Cmd+]Alt+Ctrl+] keybindings.

    Code folding example

    To fold everything, use Alt+Cmd+Shift+[Alt+Ctrl+Shift+[ and to unfold everything use Alt+Cmd+Shift+]Alt+Ctrl+Shift+]. You can also fold at a specific indentation level with Cmd+KCtrl+K Cmd+0-9Ctrl+0-9 where the number is the indentation depth.

    Finally, you can fold arbitrary sections of your code or text by making a selection and then typing Alt+Cmd+Ctrl+FAlt+Ctrl+F or choosing "Fold Selection" in the Command Palette.

    Panes

    You can split any editor pane horizontally or vertically by using Cmd+KCtrl+K Up/Down/Left/Right where the direction key is the direction to split the pane. Once you have a split pane, you can switch between them with Cmd+KCtrl+K Cmd+Up/Down/Left/RightCtrl+Up/Down/Left/Right where the direction is the direction the focus should move to.

    Multiple panes

    Each pane has its own "pane items", which are represented by tabs. You can move the files from pane to pane by dragging them with the mouse and dropping them in the pane you want that file to be in.

    Tip

    If you don't like using tabs, you don't have to. You can disable the tabs packageopen in new window and each pane will still support multiple pane items. You just won't have tabs to use to click between them.

    To close a pane, you can close all pane items with Cmd+WCtrl+W. You can configure whether panes auto-close when empty in the Settings View with the "Remove Empty Panes" setting under Core Settings.

    Pending Pane Items

    "Pending Pane Items" were formerly referred to as "Preview Tabs"

    When you open a new file by single-clicking in the Tree View, it will open in a new tab with an italic title. This indicates that the file is "pending". When a file is pending, it will be replaced by the next pending file that is opened. This allows you to click through a bunch of files to find something without having to go back and close them all.

    You can confirm a pending file by doing any of the following:

    • Double-clicking the tab of the file
    • Double-clicking the file in the tree view
    • Editing the contents of the file
    • Saving the file

    You can also open a file already confirmed by double-clicking it in the tree view instead of single-clicking it.

    Disabling Pending Pane Items

    Allow Pending Pane Items setting

    If you would prefer to not have files open in pending form, you can disable this behavior by unchecking "Allow Pending Pane Items" in the Core Settings section of the Settings View. With pending pane items disabled, single-clicking a file in the Tree View will select the file but not open it. You will have to double-click the file to open it.

    Grammar

    The "grammar" of a file is what language Atom has associated with that file. Types of grammars would include "Java" or "GitHub-Flavored Markdown". We looked at this a bit when we created some snippets in Snippets.

    When you load a file, Atom does a little work to try to figure out what type of file it is. Largely this is accomplished by looking at its file extension (.md is generally a Markdown file, etc), though sometimes it has to inspect the content a bit to figure it out.

    When you open a file and Atom can't determine a grammar for the file, it will default to "Plain Text", which is the simplest one. If it does default to "Plain Text", picks the wrong grammar for the file, or if for any reason you wish to change the selected grammar, you can pull up the Grammar Selector with Ctrl+Shift+L.

    Grammar Selector

    When the grammar of a file is changed, Atom will remember that for the current session.

    The Grammar Selector functionality is implemented in the grammar-selectoropen in new window package.

    Version Control in Atom

    Version control is an important aspect of any project and Atom comes with basic Gitopen in new window and GitHubopen in new window integration built in.

    In order to use version control in Atom, the project root needs to contain the Git repository.

    Checkout HEAD revision

    The Alt+Cmd+ZAlt+Ctrl+Z keybinding checks out the HEAD revision of the file in the editor.

    This is a quick way to discard any saved and staged changes you've made and restore the file to the version in the HEAD commit. This is essentially the same as running git checkout HEAD -- <path> and git reset HEAD -- <path> from the command line for that path.

    Git checkout

    This command goes onto the undo stack so you can use Cmd+ZCtrl+Z afterwards to restore the previous contents.

    Git status list

    Atom ships with the fuzzy-finder packageopen in new window which provides Cmd+TCtrl+T to quickly open files in the project and Cmd+BCtrl+B to jump to any open editor. The package also provides Cmd+Shift+BCtrl+Shift+B which displays a list of all the untracked and modified files in the project. These will be the same files that you would see on the command line if you ran git status.

    Git status list

    An icon will appear to the right of each file letting you know whether it is untracked or modified.

    Commit editor

    Atom can be used as your Git commit editor and ships with the language-git packageopen in new window which adds syntax highlighting to edited commit, merge, and rebase messages.

    Git commit message highlighting

    You can configure Atom to be your Git commit editor with the following command:

    $ git config --global core.editor "atom --wait"
    +

    The language-gitopen in new window package will help remind you to be brief by colorizing the first lines of commit messages when they're longer than 50 or 65 characters.

    Status bar icons

    The status-baropen in new window package that ships with Atom includes several Git decorations that display on the right side of the status bar:

    Git Status Bar decorations

    The currently checked out branch name is shown with the number of commits the branch is ahead of or behind its upstream branch. An icon is added if the file is untracked, modified, or ignored. The number of lines added and removed since the file was last committed will be displayed as well.

    Line diffs

    The included git-diffopen in new window package colorizes the gutter next to lines that have been added, edited, or removed.

    Git line diff indications

    This package also adds Alt+G Down and Alt+G Up keybindings that allow you to move the cursor to the next or previous diff in the current editor.

    Open on GitHub

    If the project you're working on is on GitHub, there are also some very useful integrations you can use. Most of the commands will take the current file you're viewing and open a view of that file on GitHub - for instance, the blame or commit history of that file.

    • Alt+G O - Open file on GitHub
    • Alt+G B - Open Blame view of file on GitHub
    • Alt+G H - Open History view of file on GitHub
    • Alt+G C - Copy the URL of the current file on GitHub to the clipboard
    • Alt+G R - Branch compare on GitHub

    The branch comparison shows you the commits that are on the branch you're currently working on locally that are not on the mainline branch.

    Open Blame of file on GitHub

    GitHub package

    The github package brings Git and GitHub integration right inside Atom.

    Most of the functionality lives within the Git and GitHub dock items.

    The Git and GitHub panels

    There are different ways to access them, probably the most common way is through their keybindings:

    • Open the Git panel: Ctrl+9
    • Open the GitHub panel: Ctrl+8

    Another way is from the menu: Packages -> GitHub -> Toggle Git Tab and Toggle GitHub Tab

    Or you can also toggle the Git panel from the Status Bar by clicking on the changed files icon:

    Open Git panel


    Initialize repositories

    In case a project doesn't have a Git repository yet, you can create one from the Git panel.

    Initialize repositories

    Clone repositories

    To clone a repository, open the GitHub panel while you have no project folders open in Atom and click "Clone an existing GitHub repository". In the dialog, paste the URL of a repository and click "Clone". The new project will be added to the Tree View.

    GitHub panel

    Clone dialog

    Alternately, run the GitHub: Clone command to open the Clone dialog any time.

    Branch

    To open the branch tooltip, click the branch icon in the Status Bar. From there you can create or switch branches.

    Create or switch branches

    Stage

    After making some changes, stage anything you want to be part of the next commit. Choose between staging...

    • All changes: Click the "Stage All" button in the "Unstaged Changes" bar.
    • Files: Double-click a file or select a file and press Enter.
    • Hunk: Click on the "Stage Hunk" button or select a hunk and press Enter.
    • Lines: Click on a line (or drag on multiple lines) to select, then click on the "Stage Selection" button. Or use the Cmd-/Cmd-/ key to toggle from hunk mode to line mode, then press Cmd-EnterCtrl-Enter to stage just a single line.

    Use the Cmd-LeftCtrl-Left or Cmd-RightCtrl-Right arrow key to switch between file list and the diff view. Unstaging can be done in the same way.

    Stage changes

    Discard changes

    If you no longer want to keep some changes, you can discard them. It's similar to staging, but accessible behind a context menu.

    • All changes: Click the ... menu in the "Unstaged Changes" header and choose "Discard All Changes".
    • Files: Right-click a file (or multiple) and choose "Discard Changes".
    • Hunk: Click on the trash icon in the top bar of a hunk.
    • Lines: Right-click on a line (or multiple) and choose "Discard Selection".

    Discard changes

    Commit Preview

    To double check all changes that are going into your next commit, click the "See All Staged Changes" button above the commit message box. It lets you see all of your staged changes in a single pane. This "commit preview" can also serve as an inspiration for writing the commit message.

    Commit Preview

    Commit

    Once you've staged your changes, enter a commit message. Feel free to describe the commit in more detail after leaving an empty line. Finalize by clicking the Commit button. If you need more space, click the expand icon at the bottom right. It will open a commit editor in the center.

    Commit changes

    To add multiple co-authors to a commit, click the "👤➕" icon in the bottom left corner of the commit message editor. Now you can search by name, email or GitHub username to give credit to a co-author.

    Commit with co-authors

    Amend and undo

    In case you forgot to commit a change and would like to add it to your previous commit, right-click on the last commit, then choose "Amend" from the context menu.

    Amend previous commit

    If you want to edit the commit message of your last commit, or add/remove changes, click on the "Undo" button. It will roll back to the state just before you clicked on the commit button.

    Undo previous commit

    View commits

    Once you've made some commits, click on a commit message in the recent commit list to see the full diff and commit message associated with each:

    View commit detai

    Publish and push

    When you're ready to share your changes with your team members, click the Publish button in the Status Bar. It will push your local branch to the remote repository. After making more commits, you can Push them as well from the Status Bar.

    Publish and push commits

    Fetch and pull

    From time to time it's a good idea to click on the Fetch button to see if any other team member pushed changes. If so, click on Pull to merge the changes into your local branch.

    Fetch and pull commits

    If you prefer to rebase when pulling, you can configure Git to make it the default behavior:

    git config --global --bool pull.rebase true
    +

    Learn more about merge vs. rebaseopen in new window.

    Resolve conflicts

    Sometimes there can be conflicts when trying to merge. Files that have merge conflicts will show up in the "Merge Conflicts" list. Click on a file to open the editor. There you can resolve the conflict by picking a version or make further edits. Once done, stage the file and commit.

    Resolve conflicts

    Create a Pull Request

    When your changes are ready to be reviewed by your team members, open the "GitHub" panel Ctrl+8 and click on Open new pull request. It will open the browser where you can continue creating a pull request. If commits haven't been pushed or the branch isn't published yet, the GitHub package will do that automatically for you.

    Create a Pull Request

    View Pull Requests

    Once the pull request is created, it will appear under Current pull request at the top of the panel. Underneath is a list of Open pull requests. It lets you quickly find a pull request by avatar, title or PR number. It also lets you keep an eye on the CI status. Clicking on a pull request in the list opens a center pane with more details, the timeline and conversations.

    View Pull Requests

    Open any Issue or Pull Request

    You can open issues or pull requests from any repo on GitHub. To do so, run the GitHub: Open Issue Or Pull Request command and paste the URL from an issue or pull request. Then press the Open Issue or Pull Request button and it will open a center pane. This lets you keep an issue or pull request as a reference, when working in another repo.

    Open Issue or Pull Request

    Checkout a Pull Request

    To test a pull request locally, open it in the workspace center by clicking on the pull request in the "open pull requests" list from the GitHub tab, then click on the Checkout button. It will automatically create a local branch and pull all the changes. If you would like to contribute to that pull request, start making changes, commit and push. Your contribution is now part of that pull request.

    Checkout a pull request

    View Pull Request review comments

    To view review comments on a Pull Request, open the Reviews Tab from the See Reviews button from the footer of a Pull Request Pane. Alternatively, if the pull request has already been checked out, Reviews Tab can also be open from the same button on GitHub Tab.

    Open review tab from footer

    You can see all the review summaries and comments of a pull request in the Reviews Tab. The comment section has a progress bar to help you keep track of how close are you to finish addressing the Pull Request comments (i.e. marking all comment threads on a Pull Request as "resolved"). Comment threads are greyed out after they have been resolved.

    Review tab

    After the pull request branch has been checked out, you can click Jump To File to open the commented on file and make changes as per the review comment right in the editor. If you would like to get the full context of the review comment, click Open Diff to open the diff view with line highlighting.

    Jump to file from review tab

    Conversely, in-editor comments are indicated by the comment icon in the gutter. Clicking the icon, either from within the editor or the diff view, will take you back to the Reviews Tab.

    Open review tab from diff

    Respond to a Pull Request review comment

    To respond to a Pull Request review comment, type your message and click Comment; a single line comment will be created in the same thread as the comment you responded to. After addressing a Pull Request review comment, click Resolve conversation to mark the whole thread as "resolved". The progress bar in the "Comments" section will update accordingly.

    Respond to a Pull Request review comment

    Writing in Atom

    Though it is probably most common to use Atom to write software code, Atom can also be used to write prose quite effectively. Most often this is done in some sort of markup language such as Asciidoc or Markdownopen in new window (in which this manual is written). Here we'll quickly cover a few of the tools Atom provides for helping you write prose.

    In these docs, we'll concentrate on writing in Markdown; however, other prose markup languages like Asciidoc have packages that provide similar functionality.

    Spell Checking

    If you're working in text (which includes plain text files, GitHub markdown, and Git commit messages by default), Atom will automatically try to check your spelling.

    Any misspelled words will be highlighted (by default with a dashed red line beneath the word), and you can pull up a menu of possible corrections by hitting Cmd+Shift+;Ctrl+Shift+; (or by choosing "Correct Spelling" from the right-click context menu or from the Command Palette).

    Checking your spelling

    To add more types of files to the list of what Atom will try to spell check, go to the Spell Check package settings in your Settings view and add any grammars you want to spell check.

    The default grammars to spell check are text.plain, source.gfm, text.git-commit, source.asciidoc, source.rst, and text.restructuredtext but you can add other grammars if you wish to check those types of files too.

    The spell checking is implemented in the spell-checkopen in new window package.

    Previews

    When writing prose in a markup language, it's often very useful to get an idea of what the content will look like when it's rendered. Atom ships with a package for previewing Markdown by default.

    • Ctrl+Shift+M - Will toggle Preview mode for Markdown.

    Preview your prose

    As you edit the text, the preview will also update automatically. This makes it fairly easy to check your syntax as you type.

    You can also copy the rendered HTML from the preview pane into your system clipboard when the preview is focused and you press Cmd+CCtrl+CCtrl+Ins or if you right-click in the preview pane and choose "Copy as HTML".

    Markdown preview is implemented in the markdown-previewopen in new window package.

    Snippets

    There are also a number of great snippets available for writing Markdown quickly.

    If you type img and hit tab you get a Markdown-formatted image embed code like ![](). If you type table and hit tab you get a nice example table to fill out.

    | Header One | Header Two |
    +| :--------- | :--------- |
    +| Item One   | Item Two   |
    +

    Although there are only a handful of Markdown snippets (b for bold, i for italic, code for a code block, etc), they save you from having to look up the more obscure syntaxes. Again, you can easily see a list of all available snippets for the type of file you're currently in by choosing "Snippets: Available" in the Command Palette.

    Basic Customization

    Now that we are feeling comfortable with just about everything built into Atom, let's look at how to tweak it. Perhaps there is a keybinding that you use a lot but feels wrong or a color that isn't quite right for you. Atom is amazingly flexible, so let's go over some of the simpler flexes it can do.

    Configuring with CSON

    All of Atom's config files (with the exception of your style sheet and your Init Script) are written in CSON, short for CoffeeScript Object Notationopen in new window. Just like its namesake JSON, JavaScript Object Notationopen in new window, CSON is a text format for storing structured data in the form of simple objects made up of key-value pairs.

    key:
    +  key: value
    +  key: value
    +  key: [value, value]
    +

    Objects are the backbone of any CSON file, and are delineated by indentation (as in the above example). A key's value can either be a String, a Number, an Object, a Boolean, null, or an Array of any of these data types.

    Warning

    Just like the more common JSON, CSON's keys can only be repeated once per object. If there are duplicate keys, then the last usage of that key overwrites all others, as if they weren't there. The same holds true for Atom's config files.

    Don't do this:

    # Only the second snippet will be loaded
    +'.source.js':
    +  'console.log':
    +    'prefix': 'log'
    +    'body': 'console.log(${1:"crash"});$2'
    +'.source.js':
    +  'console.error':
    +    'prefix': 'error'
    +    'body': 'console.error(${1:"crash"});$2'
    +

    Use this instead:

    # Both snippets will be loaded
    +'.source.js':
    +  'console.log':
    +    'prefix': 'log'
    +    'body': 'console.log(${1:"crash"});$2'
    +  'console.error':
    +    'prefix': 'error'
    +    'body': 'console.error(${1:"crash"});$2'
    +

    Style Tweaks

    If you want to apply quick-and-dirty personal styling changes without creating an entire theme that you intend to publish, you can add styles to the styles.less file in your ~/.atom%USERPROFILE%\.atom directory. You can open this file in an editor from the Atom > StylesheetFile > StylesheetEdit > Stylesheet menu.

    For example, to change the colors of the Status Bar, you could add the following rule to your styles.less file:

    .status-bar {
    +	color: white;
    +	background-color: black;
    +}
    +

    The easiest way to see what classes are available to style is to inspect the DOM manually via the Developer Tools. We'll go over the Developer Tools in great detail in the next chapter, but for now let's take a simple look. You can open the Developer Tools by pressing Alt+Cmd+ICtrl+Shift+I, which will bring up the Chromium Developer Tools panel.

    Developer Tools

    With the Developer Tools, you can inspect all the elements in Atom. If you want to update the style of something, you can figure out what classes it has and add a Less rule to your stylesheet to modify it.

    Tip

    If you are unfamiliar with Less, it is a basic CSS preprocessor that makes some things in CSS a bit easier. You can learn more about it at lesscss.orgopen in new window.

    If you prefer to use CSS instead, you can do that in the same styles.less file, since CSS is also valid in Less.

    Customizing Keybindings

    Atom keymaps work similarly to stylesheets. Just as stylesheets use selectors to apply styles to elements, Atom keymaps use selectors to associate key combinations with events in specific contexts. Here's a small example, excerpted from Atom's built-in keymap:

    'atom-text-editor':
    +  'enter': 'editor:newline'
    +
    +'atom-text-editor[mini] input':
    +  'enter': 'core:confirm'
    +

    This keymap defines the meaning of Enter in two different contexts. In a normal editor, pressing Enter triggers the editor:newline command, which causes the editor to insert a newline. But if the same keystroke occurs inside a select list's mini-editor, it instead triggers the core:confirm command based on the binding in the more-specific selector.

    By default, keymap.cson is loaded when Atom is started. It will always be loaded last, giving you the chance to override bindings that are defined by Atom's core keymaps or third-party packages. You can open this file in an editor from the Atom > KeymapFile > KeymapEdit > Keymap menu.

    You can see all the keybindings that are currently configured in your installation of Atom in the Keybindings tab in the Settings View.

    If you run into problems with keybindings, the Keybinding Resolver is a huge help. It can be opened with the Cmd+.Ctrl+. key combination. It will show you what keys Atom saw you press and what command Atom executed because of that combination.

    Global Configuration Settings

    Atom loads configuration settings from the config.cson file in your ~/.atom%USERPROFILE%\.atom directory.

    '*':
    +  'core':
    +    'excludeVcsIgnoredPaths': true
    +  'editor':
    +    'fontSize': 18
    +

    The configuration is grouped into global settings under the * key and language-specific settings under scope named keys like .python.source or .html.text. Underneath that, you'll find configuration settings grouped by package name or one of the two core namespaces: core or editor.

    You can open this file in an editor from the Atom > ConfigFile > ConfigEdit > Config menu.

    Configuration Key Reference
    • core
      • customFileTypes: Associations of language scope to file extensions (see Customizing Language Recognition)
      • disabledPackages: An array of package names to disable
      • excludeVcsIgnoredPaths: Don't search within files specified by .gitignore
      • ignoredNames: File names to ignore across all of Atom
      • projectHome: The directory where projects are assumed to be located
      • themes: An array of theme names to load, in cascading order
    • editor
      • autoIndent: Enable/disable basic auto-indent (defaults to true)
      • nonWordCharacters: A string of non-word characters to define word boundaries
      • fontSize: The editor font size
      • fontFamily: The editor font family
      • invisibles: A hash of characters Atom will use to render whitespace characters. Keys are whitespace character types, values are rendered characters (use value false to turn off individual whitespace character types)
        • tab: Hard tab characters
        • cr: Carriage return (for Microsoft-style line endings)
        • eol: \n characters
        • space: Leading and trailing space characters
      • lineHeight: Height of editor lines, as a multiplier of font size
      • preferredLineLength: Identifies the length of a line (defaults to 80)
      • showInvisibles: Whether to render placeholders for invisible characters (defaults to false)
      • showIndentGuide: Show/hide indent indicators within the editor
      • showLineNumbers: Show/hide line numbers within the gutter
      • softWrap: Enable/disable soft wrapping of text within the editor
      • softWrapAtPreferredLineLength: Enable/disable soft line wrapping at preferredLineLength
      • tabLength: Number of spaces within a tab (defaults to 2)
    • fuzzyFinder
      • ignoredNames: Files to ignore only in the fuzzy-finder
    • whitespace
      • ensureSingleTrailingNewline: Whether to reduce multiple newlines to one at the end of files
      • removeTrailingWhitespace: Enable/disable stripping of whitespace at the end of lines (defaults to true)
    • wrap-guide
      • columns: Array of hashes with a pattern and column key to match the path of the current editor to a column position.

    Language Specific Configuration Settings

    You can also set several configuration settings differently for different file types. For example, you may want Atom to soft wrap markdown files, have two-space tabs for ruby files, and four-space tabs for python files.

    There are several settings now scoped to an editor's language. Here is the current list:

    editor.autoIndent
    +editor.autoIndentOnPaste
    +editor.invisibles
    +editor.nonWordCharacters
    +editor.preferredLineLength
    +editor.scrollPastEnd
    +editor.showIndentGuide
    +editor.showInvisibles
    +editor.softWrap
    +editor.softWrapAtPreferredLineLength
    +editor.softWrapHangingIndent
    +editor.tabLength
    +
    Language-specific Settings in the Settings View

    You can edit these config settings in the Settings View on a per-language basis. Click on "Packages" tab in the navigation bar on the left, search for the language of your choice, select it, and edit away!

    Python-specific settings

    Language-specific Settings in your Config File

    You can also edit the config.cson directly. To open your configuration file via the Command Palette, press Cmd+Shift+PCtrl+Shift+P type open config, and press Enter.

    Global settings are under the * key, and each language can have its own top-level key. This key is the language's scope. Language-specific settings take precedence over anything set in the global section for that language only.

    '*': # all languages unless overridden
    +  'editor':
    +    'softWrap': false
    +    'tabLength': 8
    +
    +'.source.gfm': # markdown overrides
    +  'editor':
    +    'softWrap': true
    +
    +'.source.ruby': # ruby overrides
    +  'editor':
    +    'tabLength': 2
    +
    +'.source.python': # python overrides
    +  'editor':
    +    'tabLength': 4
    +
    Finding a Language's Scope Name

    In order to write these overrides effectively, you'll need to know the scope name for the language. We've already done this for finding a scope for writing a snippet in Snippet Format, but we can quickly cover it again.

    The scope name is shown in the settings view for each language. Click on "Packages" in the navigation on the left, search for the language of your choice, select it, and you should see the scope name under the language name heading:

    Finding a language grammar

    Another way to find the scope for a specific language is to open a file of its kind and press Alt+Cmd+Pchoose "Editor: Log Cursor Scope" in the Command Palette to show all scopes for the current position of the cursor. The scope mentioned top most is always the language for this kind of file, the scopes following are specific to the cursor position:

    Finding a language grammar with cursor scope

    These scopes can be especially useful to style the editor, since they can also be used as class names in your stylesheet.

    Customizing Language Recognition

    If you want Atom to always recognize certain file types as a specific grammar, you'll need to manually edit your config.cson file. You can open it using the Application: Open Your Config command from the Command Palette. For example, if you wanted to add the foo extension to the CoffeeScript language, you could add this to your configuration file under the *.core section:

    '*':
    +  core:
    +    customFileTypes:
    +      'source.coffee': [
    +        'foo'
    +      ]
    +

    In the example above, source.coffee is the language's scope name (see Finding a Language's Scope Name for more information) and foo is the file extension to match without the period. Adding a period to the beginning of either of these will not work.

    Controlling Where Customization is Stored to Simplify Your Workflow

    The CSON configuration files for Atom are stored on disk on your machine. The location for this storage is customizable. The default is to use the home directory of the user executing the application. The Atom Home directory will, by default, be called .atom and will be located in the root of the home directory of the user.

    Custom home location with an environment variable

    An environment variable can be used to make Atom use a different location. This can be useful for several reasons. One of these may be that multiple user accounts on a machine want to use the same Atom Home. The environment variable used to specify an alternate location is called ATOM_HOME. If this environment variable exists, the location specified will be used to load and store Atom settings.

    Taking your customization with you with Portable Mode

    In addition to using the ATOM_HOME environment variable, Atom can also be set to use "Portable Mode".

    Portable Mode is most useful for taking Atom with you, with all your custom setting and packages, from machine to machine. This may take the form of keeping Atom on a USB drive or a cloud storage platform that syncs folders to different machines, like Dropbox. Atom is in Portable Mode when there is a directory named .atom sibling to the directory in which the atom executable file lives. For example, the installed Atom directory can be placed into a Dropbox folder next to a .atom folder.

    Portable mode directory structure

    With such a setup, Atom will use the same Home directory with the same settings for any machine with this directory syncronized/plugged in.

    Moving to Portable Mode

    Atom provides a command-line parameter option for setting Portable Mode.

    $ atom --portable
    +

    Executing atom with the --portable option will take the .atom directory you have in the default location (~/.atom) and copy the relevant contents for your configuration to a new home directory in the Portable Mode location. This enables easily moving from the default location to a portable operation without losing the customization you have already set up.

    Summary

    At this point you should be something of an Atom master user. You should be able to navigate and manipulate your text and files like a wizard. You should also be able to customize Atom backwards and forwards to make it look and act just how you want it to.

    In the next chapter, we're going to kick it up a notch: we'll take a look at changing and adding new functionality to the core of Atom itself. We're going to start creating packages for Atom. If you can dream it, you can build it.

    + + + diff --git a/docs/atom-archive/using-atom/sections/atom-packages.html b/docs/atom-archive/using-atom/sections/atom-packages.html new file mode 100644 index 0000000000..9a01d1275a --- /dev/null +++ b/docs/atom-archive/using-atom/sections/atom-packages.html @@ -0,0 +1,243 @@ + + + + + + + + + + + + + + +

    About 3 min

    Atom Packages

    First we'll start with the Atom package system. As we mentioned previously, Atom itself is a very basic core of functionality that ships with a number of useful packages that add new features like the Tree Viewopen in new window and the Settings Viewopen in new window.

    In fact, there are more than 80 packages that comprise all of the functionality that is available in Atom by default. For example, the Welcome screenopen in new window that you see when you first start Atom, the spell checkeropen in new window, the themesopen in new window and the Fuzzy Finderopen in new window are all packages that are separately maintained and all use the same APIs that you have access to, as we'll see in great detail in Hacking Atom.

    This means that packages can be incredibly powerful and can change everything from the very look and feel of the entire interface to the basic operation of even core functionality.

    In order to install a new package, you can use the Install tab in the now familiar Settings View. Open up the Settings View using Cmd+,Ctrl+,, click on the "Install" tab and type your search query into the box under Install Packages.

    The packages listed here have been published to https://atom.io/packages which is the official registry for Atom packages. Searching on the Settings View will go to the Atom package registry and pull in anything that matches your search terms.

    Package install screen

    All of the packages will come up with an "Install" button. Clicking that will download the package and install it. Your editor will now have the functionality that the package provides.

    Package Settings

    Once a package is installed in Atom, it will show up in the Settings View under the "Packages" tab, along with all the preinstalled packages that come with Atom. To filter the list in order to find one, you can type into search box directly under the "Installed Packages" heading.

    Package settings screen

    Clicking on the "Settings" button for a package will give you the settings screen for that package specifically. Here you have the option of changing some of the default variables for the package, seeing what all the command keybindings are, disabling the package temporarily, looking at the source code, seeing the current version of the package, reporting issues and uninstalling the package.

    If a new version of any of your packages is released, Atom will automatically detect it and you can upgrade the package from either this screen or from the "Updates" tab. This helps you easily keep all your installed packages up to date.

    Atom Themes

    You can also find and install new themes for Atom from the Settings View. These can be either UI themes or syntax themes and you can search for them from the "Install" tab, just like searching for new packages. Make sure to press the "Themes" toggle next to the search box.

    Theme search screen

    Clicking on the theme title will take you to a profile page for the theme on atom.io, which often has a screenshot of the theme. This way you can see what it looks like before installing it.

    Clicking on "Install" will install the theme and make it available in the Theme dropdowns as we saw in Changing the Theme.

    Example of the Unity UI theme with Monokai syntax theme

    Command Line

    You can also install packages or themes from the command line using apm.

    Tip

    Check that you have apm installed by running the following command in your terminal:

    $ apm help install
    +

    You should see a message print out with details about the apm install command.

    If you do not, see the Installing Atom section for instructions on how to install the atom and apm commands for your system.

    You can also install packages by using the apm install command:

    • apm install <package_name> to install the latest version.
    • apm install <package_name>@<package_version> to install a specific version.

    For example apm install emmet@0.1.5 installs the 0.1.5 release of the Emmetopen in new window package.

    You can also use apm to find new packages to install. If you run apm search, you can search the package registry for a search term.

    $ apm search coffee
    +> Search Results For 'coffee' (29)
    +> ├── build-coffee Atom Build provider for coffee, compiles CoffeeScript (1160 downloads, 2 stars)
    +> ├── scallahan-coffee-syntax A coffee inspired theme from the guys over at S.CALLAHAN (183 downloads, 0 stars)
    +> ├── coffee-paste Copy/Paste As : Js ➤ Coffee / Coffee ➤ Js (902 downloads, 4 stars)
    +> ├── atom-coffee-repl Coffee REPL for Atom Editor (894 downloads, 2 stars)
    +> ├── coffee-navigator Code navigation panel for Coffee Script (3493 downloads, 22 stars)
    +> ...
    +> ├── language-iced-coffeescript Iced coffeescript for atom (202 downloads, 1 star)
    +> └── slontech-syntax Dark theme for web developers ( HTML, CSS/LESS, PHP, MYSQL, javascript, AJAX, coffee, JSON ) (2018 downloads, 3 stars)
    +

    You can use apm view to see more information about a specific package.

    $ apm view build-coffee
    +> build-coffee
    +> ├── 0.6.4
    +> ├── https://github.com/idleberg/atom-build-coffee
    +> ├── Atom Build provider for coffee, compiles CoffeeScript
    +> ├── 1152 downloads
    +> └── 2 stars
    +>
    +> Run `apm install build-coffee` to install this package.
    +
    + + + diff --git a/docs/atom-archive/using-atom/sections/atom-selections.html b/docs/atom-archive/using-atom/sections/atom-selections.html new file mode 100644 index 0000000000..fa6de1666e --- /dev/null +++ b/docs/atom-archive/using-atom/sections/atom-selections.html @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + +

    About 1 min

    Atom Selections

    Text selections in Atom support a number of actions, such as scoping deletion, indentation and search actions, and marking text for actions such as quoting and bracketing.

    Selections mirror many of the movement commands. They're actually exactly the same keybindings as the movement commands, but with a Shift key added in.

    • Shift+Up or Ctrl+Shift+P - Select up
    • Shift+Down or Ctrl+Shift+N - Select down
    • Shift+Left or Ctrl+Shift+B - Select previous character
    • Shift+Right or Ctrl+Shift+F - Select next character
    • Alt+Shift+LeftCtrl+Shift+Left or Alt+Shift+B - Select to beginning of word
    • Alt+Shift+RightCtrl+Shift+Right or Alt+Shift+F - Select to end of word
    • Cmd+Shift+RightShift+End or Ctrl+Shift+E - Select to end of line
    • Cmd+Shift+LeftShift+Home or Ctrl+Shift+A - Select to first character of line
    • Cmd+Shift+UpCtrl+Shift+Home - Select to top of file
    • Cmd+Shift+DownCtrl+Shift+End - Select to bottom of file

    In addition to the cursor movement selection commands, there are also a few commands that help with selecting specific areas of content.

    • Cmd+ACtrl+A - Select the entire contents of the file
    • Cmd+LCtrl+L - Select the entire line
    + + + diff --git a/docs/atom-archive/using-atom/sections/autocomplete.html b/docs/atom-archive/using-atom/sections/autocomplete.html new file mode 100644 index 0000000000..50eaced394 --- /dev/null +++ b/docs/atom-archive/using-atom/sections/autocomplete.html @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + +

    Less than 1 minute

    Autocomplete

    If you're still looking to save some typing time, Atom also ships with simple autocompletion functionality.

    The autocomplete system lets you view and insert possible completions in the editor using Tab or Enter.

    Autocomplete menu

    By default, the autocomplete system will look through the current open file for strings that match what you're starting to type.

    If you want more options, in the Settings panel for the autocomplete-plus package you can toggle a setting to make autocomplete-plus look for text in all your open buffers rather than just the current file.

    The Autocomplete functionality is implemented in the autocomplete-plusopen in new window package.

    + + + diff --git a/docs/atom-archive/using-atom/sections/basic-customization.html b/docs/atom-archive/using-atom/sections/basic-customization.html new file mode 100644 index 0000000000..0551085489 --- /dev/null +++ b/docs/atom-archive/using-atom/sections/basic-customization.html @@ -0,0 +1,293 @@ + + + + + + + + + + + + + + +

    About 7 min

    Basic Customization

    Now that we are feeling comfortable with just about everything built into Atom, let's look at how to tweak it. Perhaps there is a keybinding that you use a lot but feels wrong or a color that isn't quite right for you. Atom is amazingly flexible, so let's go over some of the simpler flexes it can do.

    Configuring with CSON

    All of Atom's config files (with the exception of your style sheet and your Init Script) are written in CSON, short for CoffeeScript Object Notationopen in new window. Just like its namesake JSON, JavaScript Object Notationopen in new window, CSON is a text format for storing structured data in the form of simple objects made up of key-value pairs.

    key:
    +  key: value
    +  key: value
    +  key: [value, value]
    +

    Objects are the backbone of any CSON file, and are delineated by indentation (as in the above example). A key's value can either be a String, a Number, an Object, a Boolean, null, or an Array of any of these data types.

    Warning

    Just like the more common JSON, CSON's keys can only be repeated once per object. If there are duplicate keys, then the last usage of that key overwrites all others, as if they weren't there. The same holds true for Atom's config files.

    Don't do this:

    # Only the second snippet will be loaded
    +'.source.js':
    +  'console.log':
    +    'prefix': 'log'
    +    'body': 'console.log(${1:"crash"});$2'
    +'.source.js':
    +  'console.error':
    +    'prefix': 'error'
    +    'body': 'console.error(${1:"crash"});$2'
    +

    Use this instead:

    # Both snippets will be loaded
    +'.source.js':
    +  'console.log':
    +    'prefix': 'log'
    +    'body': 'console.log(${1:"crash"});$2'
    +  'console.error':
    +    'prefix': 'error'
    +    'body': 'console.error(${1:"crash"});$2'
    +

    Style Tweaks

    If you want to apply quick-and-dirty personal styling changes without creating an entire theme that you intend to publish, you can add styles to the styles.less file in your ~/.atom%USERPROFILE%\.atom directory. You can open this file in an editor from the Atom > StylesheetFile > StylesheetEdit > Stylesheet menu.

    For example, to change the colors of the Status Bar, you could add the following rule to your styles.less file:

    .status-bar {
    +	color: white;
    +	background-color: black;
    +}
    +

    The easiest way to see what classes are available to style is to inspect the DOM manually via the Developer Tools. We'll go over the Developer Tools in great detail in the next chapter, but for now let's take a simple look. You can open the Developer Tools by pressing Alt+Cmd+ICtrl+Shift+I, which will bring up the Chromium Developer Tools panel.

    Developer Tools

    With the Developer Tools, you can inspect all the elements in Atom. If you want to update the style of something, you can figure out what classes it has and add a Less rule to your stylesheet to modify it.

    Tip

    If you are unfamiliar with Less, it is a basic CSS preprocessor that makes some things in CSS a bit easier. You can learn more about it at lesscss.orgopen in new window.

    If you prefer to use CSS instead, you can do that in the same styles.less file, since CSS is also valid in Less.

    Customizing Keybindings

    Atom keymaps work similarly to stylesheets. Just as stylesheets use selectors to apply styles to elements, Atom keymaps use selectors to associate key combinations with events in specific contexts. Here's a small example, excerpted from Atom's built-in keymap:

    'atom-text-editor':
    +  'enter': 'editor:newline'
    +
    +'atom-text-editor[mini] input':
    +  'enter': 'core:confirm'
    +

    This keymap defines the meaning of Enter in two different contexts. In a normal editor, pressing Enter triggers the editor:newline command, which causes the editor to insert a newline. But if the same keystroke occurs inside a select list's mini-editor, it instead triggers the core:confirm command based on the binding in the more-specific selector.

    By default, keymap.cson is loaded when Atom is started. It will always be loaded last, giving you the chance to override bindings that are defined by Atom's core keymaps or third-party packages. You can open this file in an editor from the Atom > KeymapFile > KeymapEdit > Keymap menu.

    You can see all the keybindings that are currently configured in your installation of Atom in the Keybindings tab in the Settings View.

    If you run into problems with keybindings, the Keybinding Resolver is a huge help. It can be opened with the Cmd+.Ctrl+. key combination. It will show you what keys Atom saw you press and what command Atom executed because of that combination.

    Global Configuration Settings

    Atom loads configuration settings from the config.cson file in your ~/.atom%USERPROFILE%\.atom directory.

    '*':
    +  'core':
    +    'excludeVcsIgnoredPaths': true
    +  'editor':
    +    'fontSize': 18
    +

    The configuration is grouped into global settings under the * key and language-specific settings under scope named keys like .python.source or .html.text. Underneath that, you'll find configuration settings grouped by package name or one of the two core namespaces: core or editor.

    You can open this file in an editor from the Atom > ConfigFile > ConfigEdit > Config menu.

    Configuration Key Reference
    • core
      • customFileTypes: Associations of language scope to file extensions (see Customizing Language Recognition)
      • disabledPackages: An array of package names to disable
      • excludeVcsIgnoredPaths: Don't search within files specified by .gitignore
      • ignoredNames: File names to ignore across all of Atom
      • projectHome: The directory where projects are assumed to be located
      • themes: An array of theme names to load, in cascading order
    • editor
      • autoIndent: Enable/disable basic auto-indent (defaults to true)
      • nonWordCharacters: A string of non-word characters to define word boundaries
      • fontSize: The editor font size
      • fontFamily: The editor font family
      • invisibles: A hash of characters Atom will use to render whitespace characters. Keys are whitespace character types, values are rendered characters (use value false to turn off individual whitespace character types)
        • tab: Hard tab characters
        • cr: Carriage return (for Microsoft-style line endings)
        • eol: \n characters
        • space: Leading and trailing space characters
      • lineHeight: Height of editor lines, as a multiplier of font size
      • preferredLineLength: Identifies the length of a line (defaults to 80)
      • showInvisibles: Whether to render placeholders for invisible characters (defaults to false)
      • showIndentGuide: Show/hide indent indicators within the editor
      • showLineNumbers: Show/hide line numbers within the gutter
      • softWrap: Enable/disable soft wrapping of text within the editor
      • softWrapAtPreferredLineLength: Enable/disable soft line wrapping at preferredLineLength
      • tabLength: Number of spaces within a tab (defaults to 2)
    • fuzzyFinder
      • ignoredNames: Files to ignore only in the fuzzy-finder
    • whitespace
      • ensureSingleTrailingNewline: Whether to reduce multiple newlines to one at the end of files
      • removeTrailingWhitespace: Enable/disable stripping of whitespace at the end of lines (defaults to true)
    • wrap-guide
      • columns: Array of hashes with a pattern and column key to match the path of the current editor to a column position.

    Language Specific Configuration Settings

    You can also set several configuration settings differently for different file types. For example, you may want Atom to soft wrap markdown files, have two-space tabs for ruby files, and four-space tabs for python files.

    There are several settings now scoped to an editor's language. Here is the current list:

    editor.autoIndent
    +editor.autoIndentOnPaste
    +editor.invisibles
    +editor.nonWordCharacters
    +editor.preferredLineLength
    +editor.scrollPastEnd
    +editor.showIndentGuide
    +editor.showInvisibles
    +editor.softWrap
    +editor.softWrapAtPreferredLineLength
    +editor.softWrapHangingIndent
    +editor.tabLength
    +
    Language-specific Settings in the Settings View

    You can edit these config settings in the Settings View on a per-language basis. Click on "Packages" tab in the navigation bar on the left, search for the language of your choice, select it, and edit away!

    Python-specific settings

    Language-specific Settings in your Config File

    You can also edit the config.cson directly. To open your configuration file via the Command Palette, press Cmd+Shift+PCtrl+Shift+P type open config, and press Enter.

    Global settings are under the * key, and each language can have its own top-level key. This key is the language's scope. Language-specific settings take precedence over anything set in the global section for that language only.

    '*': # all languages unless overridden
    +  'editor':
    +    'softWrap': false
    +    'tabLength': 8
    +
    +'.source.gfm': # markdown overrides
    +  'editor':
    +    'softWrap': true
    +
    +'.source.ruby': # ruby overrides
    +  'editor':
    +    'tabLength': 2
    +
    +'.source.python': # python overrides
    +  'editor':
    +    'tabLength': 4
    +
    Finding a Language's Scope Name

    In order to write these overrides effectively, you'll need to know the scope name for the language. We've already done this for finding a scope for writing a snippet in Snippet Format, but we can quickly cover it again.

    The scope name is shown in the settings view for each language. Click on "Packages" in the navigation on the left, search for the language of your choice, select it, and you should see the scope name under the language name heading:

    Finding a language grammar

    Another way to find the scope for a specific language is to open a file of its kind and press Alt+Cmd+Pchoose "Editor: Log Cursor Scope" in the Command Palette to show all scopes for the current position of the cursor. The scope mentioned top most is always the language for this kind of file, the scopes following are specific to the cursor position:

    Finding a language grammar with cursor scope

    These scopes can be especially useful to style the editor, since they can also be used as class names in your stylesheet.

    Customizing Language Recognition

    If you want Atom to always recognize certain file types as a specific grammar, you'll need to manually edit your config.cson file. You can open it using the Application: Open Your Config command from the Command Palette. For example, if you wanted to add the foo extension to the CoffeeScript language, you could add this to your configuration file under the *.core section:

    '*':
    +  core:
    +    customFileTypes:
    +      'source.coffee': [
    +        'foo'
    +      ]
    +

    In the example above, source.coffee is the language's scope name (see Finding a Language's Scope Name for more information) and foo is the file extension to match without the period. Adding a period to the beginning of either of these will not work.

    Controlling Where Customization is Stored to Simplify Your Workflow

    The CSON configuration files for Atom are stored on disk on your machine. The location for this storage is customizable. The default is to use the home directory of the user executing the application. The Atom Home directory will, by default, be called .atom and will be located in the root of the home directory of the user.

    Custom home location with an environment variable

    An environment variable can be used to make Atom use a different location. This can be useful for several reasons. One of these may be that multiple user accounts on a machine want to use the same Atom Home. The environment variable used to specify an alternate location is called ATOM_HOME. If this environment variable exists, the location specified will be used to load and store Atom settings.

    Taking your customization with you with Portable Mode

    In addition to using the ATOM_HOME environment variable, Atom can also be set to use "Portable Mode".

    Portable Mode is most useful for taking Atom with you, with all your custom setting and packages, from machine to machine. This may take the form of keeping Atom on a USB drive or a cloud storage platform that syncs folders to different machines, like Dropbox. Atom is in Portable Mode when there is a directory named .atom sibling to the directory in which the atom executable file lives. For example, the installed Atom directory can be placed into a Dropbox folder next to a .atom folder.

    Portable mode directory structure

    With such a setup, Atom will use the same Home directory with the same settings for any machine with this directory syncronized/plugged in.

    Moving to Portable Mode

    Atom provides a command-line parameter option for setting Portable Mode.

    $ atom --portable
    +

    Executing atom with the --portable option will take the .atom directory you have in the default location (~/.atom) and copy the relevant contents for your configuration to a new home directory in the Portable Mode location. This enables easily moving from the default location to a portable operation without losing the customization you have already set up.

    + + + diff --git a/docs/atom-archive/using-atom/sections/editing-and-deleting-text.html b/docs/atom-archive/using-atom/sections/editing-and-deleting-text.html new file mode 100644 index 0000000000..3180797856 --- /dev/null +++ b/docs/atom-archive/using-atom/sections/editing-and-deleting-text.html @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + +

    About 5 min

    Editing and Deleting Text

    So far we've looked at a number of ways to move around and select regions of a file, so now let's actually change some of that text. Obviously you can type in order to insert characters, but there are also a number of ways to delete and manipulate text that could come in handy.

    Basic Manipulation

    There are a handful of cool keybindings for basic text manipulation that might come in handy. These range from moving around lines of text and duplicating lines to changing the case.

    • Cmd+JCtrl+J - Join the next line to the end of the current line
    • Cmd+Ctrl+Up/DownCtrl+Up/Down - Move the current line up or down
    • Cmd+Shift+DCtrl+Shift+D - Duplicate the current line
    • Cmd+K Cmd+UCtrl+K Ctrl+U - Upper case the current word
    • Cmd+K Cmd+LCtrl+K Ctrl+L - Lower case the current word

    Atom also has built in functionality to re-flow a paragraph to hard-wrap at a given maximum line length. You can format the current selection to have lines no longer than 80 (or whatever number editor.preferredLineLength is set to) characters using Alt+Cmd+QAlt+Ctrl+Q. If nothing is selected, the current paragraph will be reflowed.

    Deleting and Cutting

    You can also delete or cut text out of your buffer with some shortcuts. Be ruthless.

    • Ctrl+Shift+K - Delete current line
    • Alt+Backspace or Alt+HCtrl+Backspace - Delete to beginning of word
    • Alt+Delete or Alt+DCtrl+Delete - Delete to end of word

    Multiple Cursors and Selections

    One of the cool things that Atom can do out of the box is support multiple cursors. This can be incredibly helpful in manipulating long lists of text.

    • Cmd+ClickCtrl+Click - Add a new cursor at the clicked location
    • Ctrl+Shift+Up/DownAlt+Ctrl+Up/DownAlt+Shift+Up/Down - Add another cursor above/below the current cursor
    • Cmd+DCtrl+D - Select the next word in the document that is the same as the currently selected word
    • Cmd+Ctrl+GAlt+F3 - Select all words in the document that are the same as the currently selected word

    Using these commands you can place cursors in multiple places in your document and effectively execute the same commands in multiple places at once.

    Using multiple cursors

    This can be incredibly helpful in doing many type of repetitive tasks such as renaming variables or changing the format of some text. You can use this with almost any plugin or command - for example, changing case and moving or duplicating lines.

    You can also use the mouse to select text with the CmdCtrl key pressed down to select multiple regions of your text simultaneously.

    Whitespace

    Atom comes with several commands to help you manage the whitespace in your document. One very useful pair of commands converts leading spaces into tabs and converts leading tabs into spaces. If you're working with a document that has mixed whitespace, these commands are great for helping to normalize the file. There are no keybindings for the whitespace commands, so you will have to search your command palette for "Convert Spaces to Tabs" (or vice versa) to run one of these commands.

    The whitespace commands are implemented in the atom/whitespaceopen in new window package. The settings for the whitespace commands are managed on the page for the whitespace package.

    Managing your whitespace settings

    Note

    The "Remove Trailing Whitespace" option is on by default. This means that every time you save any file opened in Atom, it will strip all trailing whitespace from the file. If you want to disable this, go to the whitespace package in your settings panel and uncheck that option.

    Atom will also by default ensure that your file has a trailing newline. You can also disable this option on that screen.

    Brackets

    Atom ships with intelligent and easy to use bracket handling.

    It will by default highlight [], (), and {} style brackets when your cursor is over them. It will also highlight matching XML and HTML tags.

    Atom will also automatically autocomplete [], (), and {}, "", '', “”, ‘’, «», ‹›, and backticks when you type the leading one. If you have a selection and you type any of these opening brackets or quotes, Atom will enclose the selection with the opening and closing brackets or quotes.

    There are a few other interesting bracket related commands that you can use.

    • Ctrl+M - Jump to the bracket matching the one adjacent to the cursor. It jumps to the nearest enclosing bracket when there's no adjacent bracket.
    • Cmd+Ctrl+MAlt+Ctrl+, - Select all the text inside the current brackets
    • Alt+Cmd+.Alt+Ctrl+. - Close the current XML/HTML tag

    The brackets functionality is implemented in the bracket-matcheropen in new window package. Like all of these packages, to change defaults related to bracket handling, or to disable it entirely, you can navigate to this package in the Settings view.

    Encoding

    Atom also ships with some basic file encoding support should you find yourself working with non-UTF-8 encoded files, or should you wish to create one.

    • Ctrl+Shift+UAlt+U - Toggle menu to change file encoding

    If you pull up the file encoding dialog, you can choose an alternate file encoding to save your file in.

    When you open a file, Atom will try to auto-detect the encoding. If Atom can't identify the encoding, the encoding will default to UTF-8, which is also the default encoding for new files.

    Changing your file encoding

    If you pull up the encoding menu and change the active encoding to something else, the file will be written out in that encoding the next time you save the file.

    The encoding selector is implemented in the encoding-selectoropen in new window package.

    + + + diff --git a/docs/atom-archive/using-atom/sections/find-and-replace.html b/docs/atom-archive/using-atom/sections/find-and-replace.html new file mode 100644 index 0000000000..4aa66ec8ed --- /dev/null +++ b/docs/atom-archive/using-atom/sections/find-and-replace.html @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + +

    About 2 min

    Find and Replace

    Finding and replacing text in your file or project is quick and easy in Atom.

    • Cmd+FCtrl+F - Search within a buffer
    • Cmd+Shift+FCtrl+Shift+F - Search the entire project

    If you launch either of those commands, you'll be greeted with the Find and Replace panel at the bottom of your screen.

    Find and replace text in the current file

    To search within your current file you can press Cmd+FCtrl+F, type in a search string and press Enter (or Cmd+GF3 or the "Find Next" button) multiple times to cycle through all the matches in that file. Alt+Enter will find all occurences of the search string. The Find and Replace panel also contains buttons for toggling case sensitivity, performing regular expression matching, scoping the search to selections, and performing whole word search.

    If you type a string in the replacement text box, you can replace matches with a different string. For example, if you wanted to replace every instance of the string "Scott" with the string "Dragon", you would enter those values in the two text boxes and press the "Replace All" button to perform the replacements.

    Note

    Note: Atom uses JavaScript regular expressions to perform regular expression searches.

    When doing a regular expression search, the replacement syntax to refer back to search groups is $1, $2, … $&. Refer to JavaScript's guide to regular expressionsopen in new window to learn more about regular expression syntax you can use in Atom.

    You can also find and replace throughout your entire project if you invoke the panel with Cmd+Shift+FCtrl+Shift+F.

    Find and replace text in your project

    This is a great way to find out where in your project a function is called, an anchor is linked to or a specific misspelling is located. Click on the matching line to jump to that location in that file.

    You can limit a search to a subset of the files in your project by entering a glob patternopen in new window into the "File/Directory pattern" text box. For example, the pattern src/*.js would restrict the search to JavaScript files in the src directory. The "globstar" pattern (**) can be used to match arbitrarily many subdirectories. For example, docs/**/*.md will match docs/a/foo.md, docs/a/b/foo.md, etc. You can enter multiple glob patterns separated by commas, which is useful for searching in multiple file types or subdirectories.

    When you have multiple project folders open, this feature can also be used to search in only one of those folders. For example, if you had the folders /path1/folder1 and /path2/folder2 open, you could enter a pattern starting with folder1 to search only in the first folder.

    Press Esc while focused on the Find and Replace panel to clear the pane from your workspace.

    The Find and Replace functionality is implemented in the find-and-replaceopen in new window package and uses the scandalopen in new window Node module to do the actual searching.

    + + + diff --git a/docs/atom-archive/using-atom/sections/folding.html b/docs/atom-archive/using-atom/sections/folding.html new file mode 100644 index 0000000000..6adb9ec7b6 --- /dev/null +++ b/docs/atom-archive/using-atom/sections/folding.html @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + +

    Less than 1 minute

    Folding

    If you want to see an overview of the structure of the code file you're working on, folding can be a helpful tool. Folding hides blocks of code such as functions or looping blocks in order to simplify what is on your screen.

    You can fold blocks of code by clicking the arrows that appear when you hover your mouse cursor over the gutter. You can also fold and unfold from the keyboard with the Alt+Cmd+[Alt+Ctrl+[ and Alt+Cmd+]Alt+Ctrl+] keybindings.

    Code folding example

    To fold everything, use Alt+Cmd+Shift+[Alt+Ctrl+Shift+[ and to unfold everything use Alt+Cmd+Shift+]Alt+Ctrl+Shift+]. You can also fold at a specific indentation level with Cmd+KCtrl+K Cmd+0-9Ctrl+0-9 where the number is the indentation depth.

    Finally, you can fold arbitrary sections of your code or text by making a selection and then typing Alt+Cmd+Ctrl+FAlt+Ctrl+F or choosing "Fold Selection" in the Command Palette.

    + + + diff --git a/docs/atom-archive/using-atom/sections/github-package.html b/docs/atom-archive/using-atom/sections/github-package.html new file mode 100644 index 0000000000..7ec5905bb4 --- /dev/null +++ b/docs/atom-archive/using-atom/sections/github-package.html @@ -0,0 +1,224 @@ + + + + + + + + + + + + + + +

    About 6 min

    GitHub package

    The github package brings Git and GitHub integration right inside Atom.

    Most of the functionality lives within the Git and GitHub dock items.

    The Git and GitHub panels

    There are different ways to access them, probably the most common way is through their keybindings:

    • Open the Git panel: Ctrl+9
    • Open the GitHub panel: Ctrl+8

    Another way is from the menu: Packages -> GitHub -> Toggle Git Tab and Toggle GitHub Tab

    Or you can also toggle the Git panel from the Status Bar by clicking on the changed files icon:

    Open Git panel


    Initialize repositories

    In case a project doesn't have a Git repository yet, you can create one from the Git panel.

    Initialize repositories

    Clone repositories

    To clone a repository, open the GitHub panel while you have no project folders open in Atom and click "Clone an existing GitHub repository". In the dialog, paste the URL of a repository and click "Clone". The new project will be added to the Tree View.

    GitHub panel

    Clone dialog

    Alternately, run the GitHub: Clone command to open the Clone dialog any time.

    Branch

    To open the branch tooltip, click the branch icon in the Status Bar. From there you can create or switch branches.

    Create or switch branches

    Stage

    After making some changes, stage anything you want to be part of the next commit. Choose between staging...

    • All changes: Click the "Stage All" button in the "Unstaged Changes" bar.
    • Files: Double-click a file or select a file and press Enter.
    • Hunk: Click on the "Stage Hunk" button or select a hunk and press Enter.
    • Lines: Click on a line (or drag on multiple lines) to select, then click on the "Stage Selection" button. Or use the Cmd-/Cmd-/ key to toggle from hunk mode to line mode, then press Cmd-EnterCtrl-Enter to stage just a single line.

    Use the Cmd-LeftCtrl-Left or Cmd-RightCtrl-Right arrow key to switch between file list and the diff view. Unstaging can be done in the same way.

    Stage changes

    Discard changes

    If you no longer want to keep some changes, you can discard them. It's similar to staging, but accessible behind a context menu.

    • All changes: Click the ... menu in the "Unstaged Changes" header and choose "Discard All Changes".
    • Files: Right-click a file (or multiple) and choose "Discard Changes".
    • Hunk: Click on the trash icon in the top bar of a hunk.
    • Lines: Right-click on a line (or multiple) and choose "Discard Selection".

    Discard changes

    Commit Preview

    To double check all changes that are going into your next commit, click the "See All Staged Changes" button above the commit message box. It lets you see all of your staged changes in a single pane. This "commit preview" can also serve as an inspiration for writing the commit message.

    Commit Preview

    Commit

    Once you've staged your changes, enter a commit message. Feel free to describe the commit in more detail after leaving an empty line. Finalize by clicking the Commit button. If you need more space, click the expand icon at the bottom right. It will open a commit editor in the center.

    Commit changes

    To add multiple co-authors to a commit, click the "👤➕" icon in the bottom left corner of the commit message editor. Now you can search by name, email or GitHub username to give credit to a co-author.

    Commit with co-authors

    Amend and undo

    In case you forgot to commit a change and would like to add it to your previous commit, right-click on the last commit, then choose "Amend" from the context menu.

    Amend previous commit

    If you want to edit the commit message of your last commit, or add/remove changes, click on the "Undo" button. It will roll back to the state just before you clicked on the commit button.

    Undo previous commit

    View commits

    Once you've made some commits, click on a commit message in the recent commit list to see the full diff and commit message associated with each:

    View commit detai

    Publish and push

    When you're ready to share your changes with your team members, click the Publish button in the Status Bar. It will push your local branch to the remote repository. After making more commits, you can Push them as well from the Status Bar.

    Publish and push commits

    Fetch and pull

    From time to time it's a good idea to click on the Fetch button to see if any other team member pushed changes. If so, click on Pull to merge the changes into your local branch.

    Fetch and pull commits

    If you prefer to rebase when pulling, you can configure Git to make it the default behavior:

    git config --global --bool pull.rebase true
    +

    Learn more about merge vs. rebaseopen in new window.

    Resolve conflicts

    Sometimes there can be conflicts when trying to merge. Files that have merge conflicts will show up in the "Merge Conflicts" list. Click on a file to open the editor. There you can resolve the conflict by picking a version or make further edits. Once done, stage the file and commit.

    Resolve conflicts

    Create a Pull Request

    When your changes are ready to be reviewed by your team members, open the "GitHub" panel Ctrl+8 and click on Open new pull request. It will open the browser where you can continue creating a pull request. If commits haven't been pushed or the branch isn't published yet, the GitHub package will do that automatically for you.

    Create a Pull Request

    View Pull Requests

    Once the pull request is created, it will appear under Current pull request at the top of the panel. Underneath is a list of Open pull requests. It lets you quickly find a pull request by avatar, title or PR number. It also lets you keep an eye on the CI status. Clicking on a pull request in the list opens a center pane with more details, the timeline and conversations.

    View Pull Requests

    Open any Issue or Pull Request

    You can open issues or pull requests from any repo on GitHub. To do so, run the GitHub: Open Issue Or Pull Request command and paste the URL from an issue or pull request. Then press the Open Issue or Pull Request button and it will open a center pane. This lets you keep an issue or pull request as a reference, when working in another repo.

    Open Issue or Pull Request

    Checkout a Pull Request

    To test a pull request locally, open it in the workspace center by clicking on the pull request in the "open pull requests" list from the GitHub tab, then click on the Checkout button. It will automatically create a local branch and pull all the changes. If you would like to contribute to that pull request, start making changes, commit and push. Your contribution is now part of that pull request.

    Checkout a pull request

    View Pull Request review comments

    To view review comments on a Pull Request, open the Reviews Tab from the See Reviews button from the footer of a Pull Request Pane. Alternatively, if the pull request has already been checked out, Reviews Tab can also be open from the same button on GitHub Tab.

    Open review tab from footer

    You can see all the review summaries and comments of a pull request in the Reviews Tab. The comment section has a progress bar to help you keep track of how close are you to finish addressing the Pull Request comments (i.e. marking all comment threads on a Pull Request as "resolved"). Comment threads are greyed out after they have been resolved.

    Review tab

    After the pull request branch has been checked out, you can click Jump To File to open the commented on file and make changes as per the review comment right in the editor. If you would like to get the full context of the review comment, click Open Diff to open the diff view with line highlighting.

    Jump to file from review tab

    Conversely, in-editor comments are indicated by the comment icon in the gutter. Clicking the icon, either from within the editor or the diff view, will take you back to the Reviews Tab.

    Open review tab from diff

    Respond to a Pull Request review comment

    To respond to a Pull Request review comment, type your message and click Comment; a single line comment will be created in the same thread as the comment you responded to. After addressing a Pull Request review comment, click Resolve conversation to mark the whole thread as "resolved". The progress bar in the "Comments" section will update accordingly.

    Respond to a Pull Request review comment

    + + + diff --git a/docs/atom-archive/using-atom/sections/grammar.html b/docs/atom-archive/using-atom/sections/grammar.html new file mode 100644 index 0000000000..07bf49234c --- /dev/null +++ b/docs/atom-archive/using-atom/sections/grammar.html @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + +

    Less than 1 minute

    Grammar

    The "grammar" of a file is what language Atom has associated with that file. Types of grammars would include "Java" or "GitHub-Flavored Markdown". We looked at this a bit when we created some snippets in Snippets.

    When you load a file, Atom does a little work to try to figure out what type of file it is. Largely this is accomplished by looking at its file extension (.md is generally a Markdown file, etc), though sometimes it has to inspect the content a bit to figure it out.

    When you open a file and Atom can't determine a grammar for the file, it will default to "Plain Text", which is the simplest one. If it does default to "Plain Text", picks the wrong grammar for the file, or if for any reason you wish to change the selected grammar, you can pull up the Grammar Selector with Ctrl+Shift+L.

    Grammar Selector

    When the grammar of a file is changed, Atom will remember that for the current session.

    The Grammar Selector functionality is implemented in the grammar-selectoropen in new window package.

    + + + diff --git a/docs/atom-archive/using-atom/sections/moving-in-atom.html b/docs/atom-archive/using-atom/sections/moving-in-atom.html new file mode 100644 index 0000000000..ad3bb8033a --- /dev/null +++ b/docs/atom-archive/using-atom/sections/moving-in-atom.html @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + +

    About 6 min

    Moving in Atom

    While it's pretty easy to move around Atom by clicking with the mouse or using the arrow keys, there are some keybindings that may help you keep your hands on the keyboard and navigate around a little faster.

    • Alt+Left or Alt+BCtrl+Left - Move to the beginning of word
    • Alt+Right or Alt+FCtrl+Right - Move to the end of word
    • Cmd+Left or Ctrl+AHome - Move to the first character of the current line
    • Cmd+Right or Ctrl+EEnd - Move to the end of the line
    • Cmd+UpCtrl+Home - Move to the top of the file
    • Cmd+DownCtrl+End - Move to the bottom of the file

    You can also move directly to a specific line (and column) number with Ctrl+G. This will bring up a dialog that asks which line you would like to jump to. You can also use the row:column syntax to jump to a character in that line as well.

    Go directly to a line

    Additional Movement and Selection Commands

    Atom also has a few movement and selection commands that don't have keybindings by default. You can access these commands from the Command Palette, but if you find yourself using commands that don't have a keybinding often, have no fear! You can easily add an entry to your keymap.cson to create a key combination. You can open keymap.cson file in an editor from the Atom > KeymapFile > KeymapEdit > Keymap menu.

    For example, the command editor:move-to-beginning-of-screen-line is available in the command palette, but it's not bound to any key combination. To create a key combination you need to add an entry in your keymap.cson file. For editor:select-to-previous-word-boundary, you can add the following to your keymap.cson:

    This will bind the command editor:select-to-previous-word-boundary to Cmd+Shift+ECtrl+Shift+E. For more information on customizing your keybindings, see Customizing Keybindings.

    Here's a list of Movement and Selection Commands that do not have a keyboard shortcut by default:

    You can also jump around a little more informatively with the Symbols View. To jump to a symbol such as a method definition, press Cmd+RCtrl+R. This opens a list of all symbols in the current file, which you can fuzzy filter similarly to Cmd+TCtrl+T. You can also search for symbols across your project but it requires a tags file.

    Search by symbol across your project

    You can generate a tags file by using the ctags utilityopen in new window. Once it is installed, you can use it to generate a tags file by running a command to generate it. See the ctags documentationopen in new window for details.

    You can customize how tags are generated by creating your own .ctags file in your home directory, ~/.ctags%USERPROFILE%\.ctags. An example can be found hereopen in new window.

    The symbols navigation functionality is implemented in the symbols-viewopen in new window package.

    Bookmarks

    Atom also has a great way to bookmark specific lines in your project so you can jump back to them quickly.

    If you press Cmd+F2Alt+Ctrl+F2Ctrl+Shift+F2, Atom will toggle a "bookmark" on the current line. You can set these throughout your project and use them to quickly find and jump to important lines of your project. A small bookmark symbol is added to the line gutter, like on line 22 of the image below.

    If you hit F2, Atom will jump to the next bookmark in the file you currently have focused. If you use Shift+F2 it will cycle backwards through them instead.

    You can also see a list of all your project's current bookmarks and quickly filter them and jump to any of them by hitting Ctrl+F2.

    ![View and filter bookmarks](@images/atom/bookmarks.png "View and filter bookmarks")

    The bookmarks functionality is implemented in the bookmarksopen in new window package.

    + + + diff --git a/docs/atom-archive/using-atom/sections/panes.html b/docs/atom-archive/using-atom/sections/panes.html new file mode 100644 index 0000000000..928661ddd1 --- /dev/null +++ b/docs/atom-archive/using-atom/sections/panes.html @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + +

    Less than 1 minute

    Panes

    You can split any editor pane horizontally or vertically by using Cmd+KCtrl+K Up/Down/Left/Right where the direction key is the direction to split the pane. Once you have a split pane, you can switch between them with Cmd+KCtrl+K Cmd+Up/Down/Left/RightCtrl+Up/Down/Left/Right where the direction is the direction the focus should move to.

    Multiple panes

    Each pane has its own "pane items", which are represented by tabs. You can move the files from pane to pane by dragging them with the mouse and dropping them in the pane you want that file to be in.

    Tip

    If you don't like using tabs, you don't have to. You can disable the tabs packageopen in new window and each pane will still support multiple pane items. You just won't have tabs to use to click between them.

    To close a pane, you can close all pane items with Cmd+WCtrl+W. You can configure whether panes auto-close when empty in the Settings View with the "Remove Empty Panes" setting under Core Settings.

    + + + diff --git a/docs/atom-archive/using-atom/sections/pending-pane-items.html b/docs/atom-archive/using-atom/sections/pending-pane-items.html new file mode 100644 index 0000000000..ea65d79df4 --- /dev/null +++ b/docs/atom-archive/using-atom/sections/pending-pane-items.html @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + +

    Less than 1 minute

    Pending Pane Items

    "Pending Pane Items" were formerly referred to as "Preview Tabs"

    When you open a new file by single-clicking in the Tree View, it will open in a new tab with an italic title. This indicates that the file is "pending". When a file is pending, it will be replaced by the next pending file that is opened. This allows you to click through a bunch of files to find something without having to go back and close them all.

    You can confirm a pending file by doing any of the following:

    • Double-clicking the tab of the file
    • Double-clicking the file in the tree view
    • Editing the contents of the file
    • Saving the file

    You can also open a file already confirmed by double-clicking it in the tree view instead of single-clicking it.

    Disabling Pending Pane Items

    Allow Pending Pane Items setting

    If you would prefer to not have files open in pending form, you can disable this behavior by unchecking "Allow Pending Pane Items" in the Core Settings section of the Settings View. With pending pane items disabled, single-clicking a file in the Tree View will select the file but not open it. You will have to double-click the file to open it.

    + + + diff --git a/docs/atom-archive/using-atom/sections/snippets.html b/docs/atom-archive/using-atom/sections/snippets.html new file mode 100644 index 0000000000..3b530e0355 --- /dev/null +++ b/docs/atom-archive/using-atom/sections/snippets.html @@ -0,0 +1,264 @@ + + + + + + + + + + + + + + +

    About 3 min

    Snippets

    Snippets are an incredibly powerful way to quickly generate commonly needed code syntax from a shortcut.

    The idea is that you can type something like habtm and then press the Tab key and it will expand into has_and_belongs_to_many.

    Many Core and Community packages come bundled with their own snippets that are specific to it. For example, the language-html package that provides support for HTML syntax highlighting and grammar comes with dozens of snippets to create many of the various HTML tags you might want to use. If you create a new HTML file in Atom, you can type html and then press Tab and it will expand to:

    <!DOCTYPE html>
    +<html lang="en" dir="ltr">
    +	<head>
    +		<meta charset="utf-8" />
    +		<title></title>
    +	</head>
    +	<body></body>
    +</html>
    +

    It will also position the cursor in the lang attribute value so you can edit it if necessary. Many snippets have multiple focus points that you can move through with the Tab key as well - for instance, in the case of this HTML snippet, after the cursor is placed in the lang attribute value, you can continue pressing Tab and the cursor will move to the dir attribute value, then to the middle of the title tag, then finally to the middle of the body tag.

    To see all the available snippets for the file type that you currently have open, choose "Snippets: Available" in the Command Palette.

    View all available snippets

    You can also use fuzzy search to filter this list down by typing in the selection box. Selecting one of them will execute the snippet where your cursor is (or multiple cursors are).

    Creating Your Own Snippets

    So that's pretty cool, but what if there is something the language package didn't include or something that is custom to the code you write? Luckily it's incredibly easy to add your own snippets.

    There is a text file in your ~/.atom%USERPROFILE%\.atom directory called snippets.cson that contains all your custom snippets that are loaded when you launch Atom. You can also easily open up that file by selecting the Atom > SnippetsEdit > SnippetsFile > Snippets menu.

    Snippet Format

    So let's look at how to write a snippet. The basic snippet format looks like this:

    '.source.js':
    +  'console.log':
    +    'prefix': 'log'
    +    'body': 'console.log(${1:"crash"});$2'
    +

    The leftmost keys are the selectors where these snippets should be active. The easiest way to determine what this should be is to go to the language package of the language you want to add a snippet for and look for the "Scope" string.

    For example, if we wanted to add a snippet that would work for Java files, we would look up the language-java package in our Settings view and we can see the Scope is source.java. Then the top level snippet key would be that prepended by a period (like a CSS class selector would do).

    Finding the selector scope for a snippet

    The next level of keys are the snippet names. These are used for describing the snippet in a more readable way in the snippet menu. You can name them whatever you want.

    Under each snippet name is a prefix that should trigger the snippet and a body to insert when the snippet is triggered.

    Each $ followed by a number is a tab stop. Tab stops are cycled through by pressing Tab once a snippet has been triggered.

    Tab stops with the same number will create multiple cursors.

    The above example adds a log snippet to JavaScript files that would expand to:

    console.log("crash");
    +

    The string "crash" would be initially selected and pressing tab again would place the cursor after the ;

    Warning

    Snippet keys, unlike CSS selectors, can only be repeated once per level. If there are duplicate keys at the same level, then only the last one will be read. See Configuring with CSON for more information.

    Multi-line Snippet Body

    You can also use CoffeeScript multi-line syntaxopen in new window using """ for larger templates:

    '.source.js':
    +  'if, else if, else':
    +    'prefix': 'ieie'
    +    'body': """
    +      if (${1:true}) {
    +        $2
    +      } else if (${3:false}) {
    +        $4
    +      } else {
    +        $5
    +      }
    +    """
    +

    As you might expect, there is a snippet to create snippets. If you open up a snippets file and type snip and then press Tab, you will get the following text inserted:

    '.source.js':
    +  'Snippet Name':
    +    'prefix': 'hello'
    +    'body': 'Hello World!'
    +

    💥 just fill that bad boy out and you have yourself a snippet. As soon as you save the file, Atom should reload the snippets and you will immediately be able to try it out.

    Multiple Snippets per Source

    You can see below the format for including multiple snippets for the same scope in your snippets.cson file. Just include the snippet name, prefix, and body keys for additional snippets inside the scope key:

    '.source.gfm':
    +  'Hello World':
    +    'prefix': 'hewo'
    +    'body': 'Hello World!'
    +
    +  'Github Hello':
    +    'prefix': 'gihe'
    +    'body': 'Octocat says Hi!'
    +
    +  'Octocat Image Link':
    +    'prefix': 'octopic'
    +    'body': '![GitHub Octocat](https://assets-cdn.github.com/images/modules/logos_page/Octocat.png)'
    +

    Again, see Configuring with CSON for more information on CSON key structure and non-repeatability.

    More Info

    The snippets functionality is implemented in the snippetsopen in new window package.

    For more examples, see the snippets in the language-htmlopen in new window and language-javascriptopen in new window packages.

    + + + diff --git a/docs/atom-archive/using-atom/sections/summary.html b/docs/atom-archive/using-atom/sections/summary.html new file mode 100644 index 0000000000..599aae5724 --- /dev/null +++ b/docs/atom-archive/using-atom/sections/summary.html @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + +

    Less than 1 minute

    Summary

    At this point you should be something of an Atom master user. You should be able to navigate and manipulate your text and files like a wizard. You should also be able to customize Atom backwards and forwards to make it look and act just how you want it to.

    In the next chapter, we're going to kick it up a notch: we'll take a look at changing and adding new functionality to the core of Atom itself. We're going to start creating packages for Atom. If you can dream it, you can build it.

    + + + diff --git a/docs/atom-archive/using-atom/sections/version-control-in-atom.html b/docs/atom-archive/using-atom/sections/version-control-in-atom.html new file mode 100644 index 0000000000..0b93458f1a --- /dev/null +++ b/docs/atom-archive/using-atom/sections/version-control-in-atom.html @@ -0,0 +1,224 @@ + + + + + + + + + + + + + + +

    About 3 min

    Version Control in Atom

    Version control is an important aspect of any project and Atom comes with basic Gitopen in new window and GitHubopen in new window integration built in.

    In order to use version control in Atom, the project root needs to contain the Git repository.

    Checkout HEAD revision

    The Alt+Cmd+ZAlt+Ctrl+Z keybinding checks out the HEAD revision of the file in the editor.

    This is a quick way to discard any saved and staged changes you've made and restore the file to the version in the HEAD commit. This is essentially the same as running git checkout HEAD -- <path> and git reset HEAD -- <path> from the command line for that path.

    Git checkout

    This command goes onto the undo stack so you can use Cmd+ZCtrl+Z afterwards to restore the previous contents.

    Git status list

    Atom ships with the fuzzy-finder packageopen in new window which provides Cmd+TCtrl+T to quickly open files in the project and Cmd+BCtrl+B to jump to any open editor. The package also provides Cmd+Shift+BCtrl+Shift+B which displays a list of all the untracked and modified files in the project. These will be the same files that you would see on the command line if you ran git status.

    Git status list

    An icon will appear to the right of each file letting you know whether it is untracked or modified.

    Commit editor

    Atom can be used as your Git commit editor and ships with the language-git packageopen in new window which adds syntax highlighting to edited commit, merge, and rebase messages.

    Git commit message highlighting

    You can configure Atom to be your Git commit editor with the following command:

    $ git config --global core.editor "atom --wait"
    +

    The language-gitopen in new window package will help remind you to be brief by colorizing the first lines of commit messages when they're longer than 50 or 65 characters.

    Status bar icons

    The status-baropen in new window package that ships with Atom includes several Git decorations that display on the right side of the status bar:

    Git Status Bar decorations

    The currently checked out branch name is shown with the number of commits the branch is ahead of or behind its upstream branch. An icon is added if the file is untracked, modified, or ignored. The number of lines added and removed since the file was last committed will be displayed as well.

    Line diffs

    The included git-diffopen in new window package colorizes the gutter next to lines that have been added, edited, or removed.

    Git line diff indications

    This package also adds Alt+G Down and Alt+G Up keybindings that allow you to move the cursor to the next or previous diff in the current editor.

    Open on GitHub

    If the project you're working on is on GitHub, there are also some very useful integrations you can use. Most of the commands will take the current file you're viewing and open a view of that file on GitHub - for instance, the blame or commit history of that file.

    • Alt+G O - Open file on GitHub
    • Alt+G B - Open Blame view of file on GitHub
    • Alt+G H - Open History view of file on GitHub
    • Alt+G C - Copy the URL of the current file on GitHub to the clipboard
    • Alt+G R - Branch compare on GitHub

    The branch comparison shows you the commits that are on the branch you're currently working on locally that are not on the mainline branch.

    Open Blame of file on GitHub

    + + + diff --git a/docs/atom-archive/using-atom/sections/writing-in-atom.html b/docs/atom-archive/using-atom/sections/writing-in-atom.html new file mode 100644 index 0000000000..111422333c --- /dev/null +++ b/docs/atom-archive/using-atom/sections/writing-in-atom.html @@ -0,0 +1,226 @@ + + + + + + + + + + + + + + +

    About 2 min

    Writing in Atom

    Though it is probably most common to use Atom to write software code, Atom can also be used to write prose quite effectively. Most often this is done in some sort of markup language such as Asciidoc or Markdownopen in new window (in which this manual is written). Here we'll quickly cover a few of the tools Atom provides for helping you write prose.

    In these docs, we'll concentrate on writing in Markdown; however, other prose markup languages like Asciidoc have packages that provide similar functionality.

    Spell Checking

    If you're working in text (which includes plain text files, GitHub markdown, and Git commit messages by default), Atom will automatically try to check your spelling.

    Any misspelled words will be highlighted (by default with a dashed red line beneath the word), and you can pull up a menu of possible corrections by hitting Cmd+Shift+;Ctrl+Shift+; (or by choosing "Correct Spelling" from the right-click context menu or from the Command Palette).

    Checking your spelling

    To add more types of files to the list of what Atom will try to spell check, go to the Spell Check package settings in your Settings view and add any grammars you want to spell check.

    The default grammars to spell check are text.plain, source.gfm, text.git-commit, source.asciidoc, source.rst, and text.restructuredtext but you can add other grammars if you wish to check those types of files too.

    The spell checking is implemented in the spell-checkopen in new window package.

    Previews

    When writing prose in a markup language, it's often very useful to get an idea of what the content will look like when it's rendered. Atom ships with a package for previewing Markdown by default.

    • Ctrl+Shift+M - Will toggle Preview mode for Markdown.

    Preview your prose

    As you edit the text, the preview will also update automatically. This makes it fairly easy to check your syntax as you type.

    You can also copy the rendered HTML from the preview pane into your system clipboard when the preview is focused and you press Cmd+CCtrl+CCtrl+Ins or if you right-click in the preview pane and choose "Copy as HTML".

    Markdown preview is implemented in the markdown-previewopen in new window package.

    Snippets

    There are also a number of great snippets available for writing Markdown quickly.

    If you type img and hit tab you get a Markdown-formatted image embed code like ![](). If you type table and hit tab you get a nice example table to fill out.

    | Header One | Header Two |
    +| :--------- | :--------- |
    +| Item One   | Item Two   |
    +

    Although there are only a handful of Markdown snippets (b for bold, i for italic, code for a code block, etc), they save you from having to look up the more obscure syntaxes. Again, you can easily see a list of all available snippets for the type of file you're currently in by choosing "Snippets: Available" in the Command Palette.

    + + + diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000000..c0782d9d49 --- /dev/null +++ b/docs/index.html @@ -0,0 +1,223 @@ + + + + + + + + Documentation Home | + + + + + + +

    Documentation Home

    Less than 1 minute

    Welcome to Pulsar

    Here you will find everything you need to know about Pulsar, from how to install it all the way to hacking away at its core to make it your own.

    The documentation has been broken down into various sections which you can find below.

    Launch Manual

    The Launch Manual contains everything you need to know about Pulsar. It covers topics all the way from the initial installation through to developing and publishing your own packages.

    Packages

    This section is dedicated to info and documentation around Pulsar's numerous packages which make up both the core editor and community ecosystem.

    Resources

    There are a number of miscellaneous resources here that do not necessarily fit into other topics. This is where you will find information about things like the tools used by the team and contributors, a glossary to translate any confusing jargon and details of the various APIs that make up Pulsar.

    Atom Archive

    This is an archive of the old Atom documentation as it appeared on their Flight Manualopen in new window.

    Anything here is just a historical reference of our past, when Atom existed as a GitHub supported project.

    This includes their original layout and formatting of how things were expected to appear. Anything listed here, may work, and it may not. This is taking into consideration their sunset and our picking up of their torch.

    + + + diff --git a/docs/launch-manual/index.html b/docs/launch-manual/index.html new file mode 100644 index 0000000000..e227e6f48c --- /dev/null +++ b/docs/launch-manual/index.html @@ -0,0 +1,223 @@ + + + + + + + + Launch Manual | + + + + + + +

    Launch Manual

    Less than 1 minute

    Getting Started

    Here you will learn everything you need to know about actually using Pulsar. This introduces what Pulsar is, how to install it and how to use its basic features.

    Using Pulsar

    This section is all about the various functions built right into Pulsar and how you can use them to get the most out of it.

    It will teach you everything from cursor movement all the way to basic customization of the application.

    Hacking the Core

    This is really what Pulsar is all about, here you will get to grips with what lies under the skin of Pulsar so you can begin hacking on it to extend and customize its functionality.

    Behind Pulsar

    Here you will get into the real heart of Pulsar, where you get into the details of various internal APIs and full details of how to maintain packages.

    FAQ

    Frequently asked questions. This is where you can find some of the more common questions and answers regarding Pulsar.

    + + + diff --git a/docs/launch-manual/sections/behind-pulsar/index.html b/docs/launch-manual/sections/behind-pulsar/index.html new file mode 100644 index 0000000000..52a8d68989 --- /dev/null +++ b/docs/launch-manual/sections/behind-pulsar/index.html @@ -0,0 +1,444 @@ + + + + + + + + Behind Pulsar | + + + + + + +

    Behind Pulsar

    Less than 1 minute

    Behind Pulsar

    Under Construction

    This document is under construction, please check back soon for updates. Please see our socials and feel free to ask for assistance or inquire as to the status of this document.

    Now that we've written a number of packages and themes, let's take minute to take a closer look at some of the ways that Pulsar works in greater depth. Here we'll go into more of a deep dive on individual internal APIs and systems of Pulsar, even looking at some Atom source to see how things are really getting done.

    Configuration API

    Reading Config Settings

    If you are writing a package that you want to make configurable, you'll need to read config settings via the atom.config global. You can read the current value of a namespaced config key with atom.config.get:

    // read a value with `config.get`
    +if (atom.config.get("editor.showInvisibles")) {
    +	this.showInvisibles();
    +}
    +

    Or you can subscribe via atom.config.observe to track changes from any view object.

    const {View} = require('space-pen')
    +
    +class MyView extends View {
    +  function attached() {
    +    this.fontSizeObserveSubscription =
    +      atom.config.observe('editor.fontSize', (newValue, {previous}) => {
    +        this.adjustFontSize(newValue)
    +      })
    +  }
    +
    +  function detached() {
    +    this.fontSizeObserveSubscription.dispose()
    +  }
    +}
    +

    The atom.config.observe method will call the given callback immediately with the current value for the specified key path, and it will also call it in the future whenever the value of that key path changes. If you only want to invoke the callback the next time the value changes, use atom.config.onDidChange instead.

    Subscription methods return Disposableopen in new window objects that can be used to unsubscribe. Note in the example above how we save the subscription to the @fontSizeObserveSubscription instance variable and dispose of it when the view is detached. To group multiple subscriptions together, you can add them all to a CompositeDisposableopen in new window that you dispose when the view is detached.

    Writing Config Settings

    The atom.config database is populated on startup from LNX/MAC: ~/.pulsar/config.cson - WIN: %USERPROFILE%\.pulsar\config.cson but you can programmatically write to it with atom.config.set:

    // basic key update
    +atom.config.set("core.showInvisibles", true);
    +

    If you're exposing package configuration via specific key paths, you'll want to associate them with a schema in your package's main module. Read more about schemas in the Config API documentationopen in new window.

    Keymaps In-Depth

    Structure of a Keymap File

    Keymap files are encoded as JSON or CSON files containing nested hashes. They work much like style sheets, but instead of applying style properties to elements matching the selector, they specify the meaning of keystrokes on elements matching the selector. Here is an example of some bindings that apply when keystrokes pass through atom-text-editor elements:

    Beneath the first selector are several keybindings, mapping specific key combinations to commands. When an element with the atom-text-editor class is focused and LNX/WIN: Ctrl+Backspace - MAC: Alt+Backspace is pressed, a custom DOM event called editor:delete-to-beginning-of-word is emitted on the atom-text-editor element.

    The second selector group also targets editors, but only if they don't have the mini attribute. In this example, the commands for code folding don't really make sense on mini-editors, so the selector restricts them to regular editors.

    Key Combinations

    Key combinations express one or more keys combined with optional modifier keys. For example: ctrl-w v, or cmd-shift-up. A key combination is composed of the following symbols, separated by a -. A key sequence can be expressed as key combinations separated by spaces.

    TypeExamples
    Character literalsa 4 $
    Modifier keyscmd ctrl alt shift
    Special keysenter escape backspace delete tab home end pageup pagedown left right up down space

    Commands

    Commands are custom DOM events that are triggered when a key combination or sequence matches a binding. This allows user interface code to listen for named commands without specifying the specific keybinding that triggers it. For example, the following code creates a command to insert the current date in an editor:

    atom.commands.add("atom-text-editor", {
    +	"user:insert-date": function (event) {
    +		const editor = this.getModel();
    +		return editor.insertText(new Date().toLocaleString());
    +	},
    +});
    +

    atom.commands refers to the global CommandRegistry instance where all commands are set and consequently picked up by the command palette.

    When you are looking to bind new keys, it is often useful to use the Command Palette (LNX/WIN: Ctrl+Shift+P - MAC: Cmd+Shift+P) to discover what commands are being listened for in a given focus context. Commands are "humanized" following a simple algorithm, so a command like editor:fold-current-row would appear as "Editor: Fold Current Row".

    "Composed" Commands

    A common question is, "How do I make a single keybinding execute two or more commands?" There isn't any direct support for this in Pulsar, but it can be achieved by creating a custom command that performs the multiple actions you desire and then creating a keybinding for that command. For example, let's say I want to create a "composed" command that performs a Select Line followed by Cut. You could add the following to your init.js:

    atom.commands.add("atom-text-editor", "custom:cut-line", function () {
    +	const editor = this.getModel();
    +	editor.selectLinesContainingCursors();
    +	editor.cutSelectedText();
    +});
    +

    Then let's say we want to map this custom command to alt-ctrl-z, you could add the following to your keymap:

    'atom-text-editor':
    +  'alt-ctrl-z': 'custom:cut-line'
    +

    Specificity and Cascade Order

    As is the case with CSS applying styles, when multiple bindings match for a single element, the conflict is resolved by choosing the most specific selector. If two matching selectors have the same specificity, the binding for the selector appearing later in the cascade takes precedence.

    Currently, there's no way to specify selector ordering within a single keymap, because JSON objects do not preserve order. We handle cases where selector ordering is critical by breaking the keymap into separate files, such as snippets-1.cson and snippets-2.cson.

    Selectors and Custom Packages

    If a keybinding should only apply to a specific grammar, you can limit bindings to that grammar using the data-grammar attribute on the atom-text-editor element:

    "atom-text-editor[data-grammar='source example']":
    +  'ctrl-.': 'custom:custom-command'
    +

    While selectors can be applied to the entire editor by what grammar is associated with it, they cannot be applied to scopes defined within the grammar or to sub-elements of atom-text-editor.

    Removing Bindings

    When the keymap system encounters a binding with the unset! directive as its command, it will treat the current element as if it had no key bindings matching the current keystroke sequence and continue searching from its parent. For example, the following code removes the keybinding for a in the Tree View, which is normally used to trigger the tree-view:add-file command:

    '.tree-view':
    +  'a': 'unset!'
    +

    Keybinding Resolver

    But if some element above the Tree View had a keybinding for a, that keybinding would still execute even when the focus is inside the Tree View.

    When the keymap system encounters a binding with the abort! directive as its command, it will stop searching for a keybinding. For example, the following code removes the keybinding for LNX/WIN: Ctrl+O - MAC: Cmd+O when the selection is inside an editor pane:

    But if you click inside the Tree View and press LNX/WIN: Ctrl+O - MAC: Cmd+O , it will work.

    Forcing Chromium's Native Keystroke Handling

    If you want to force the native browser behavior for a given keystroke, use the native! directive as the command of a binding. This can be useful to enable the correct behavior in native input elements. If you apply the .native-key-bindings class to an element, all the keystrokes typically handled by the browser will be assigned the native! directive.

    Tips

    Tip: Components and input elements may not correctly handle backspace and arrow keys without forcing this behavior. If your backspace isn't working correctly inside of a component, add either the directive or the .native-key-bindings class.

    Overloading Key Bindings

    Occasionally, it makes sense to layer multiple actions on top of the same key binding. An example of this is the snippets package. Snippets are inserted by typing a snippet prefix such as for and then pressing Tab. Every time Tab is pressed, we want to execute code attempting to expand a snippet if one exists for the text preceding the cursor. If a snippet doesn't exist, we want Tab to actually insert whitespace.

    To achieve this, the snippets package makes use of the .abortKeyBinding() method on the event object representing the snippets:expand command.

    // pseudo-code
    +editor.command("snippets:expand", (e) => {
    +	if (this.cursorFollowsValidPrefix()) {
    +		this.expandSnippet();
    +	} else {
    +		e.abortKeyBinding();
    +	}
    +});
    +

    When the event handler observes that the cursor does not follow a valid prefix, it calls e.abortKeyBinding(), telling the keymap system to continue searching for another matching binding.

    Step-by-Step: How Keydown Events are Mapped to Commands

    • A keydown event occurs on a focused element.
    • Starting at the focused element, the keymap walks upward towards the root of the document, searching for the most specific CSS selector that matches the current DOM element and also contains a keystroke pattern matching the keydown event.
    • When a matching keystroke pattern is found, the search is terminated and the pattern's corresponding command is triggered on the current element.
    • If .abortKeyBinding() is called on the triggered event object, the search is resumed, triggering a binding on the next-most-specific CSS selector for the same element or continuing upward to parent elements.
    • If no bindings are found, the event is handled by Chromium normally.

    Overriding Pulsar's Keyboard Layout Recognition

    Sometimes the problem isn't mapping the command to a key combination, the problem is that Pulsar doesn't recognize properly what keys you're pressing. This is due to some limitations in how Chromium reports keyboard eventsopen in new window. But even this can be customized now.

    You can add the following to your init.js to send Ctrl+@ when you press Ctrl+Alt+G:

    atom.keymaps.addKeystrokeResolver(({ event }) => {
    +	if (
    +		event.code === "KeyG" &&
    +		event.altKey &&
    +		event.ctrlKey &&
    +		event.type !== "keyup"
    +	) {
    +		return "ctrl-@";
    +	}
    +});
    +

    Or if you are still using the init.coffee file:

    atom.keymaps.addKeystrokeResolver ({event}) ->
    +  if event.code is 'KeyG' and event.altKey and event.ctrlKey and event.type isnt 'keyup'
    +    return 'ctrl-@'
    +

    If you want to know the event for the keystroke you pressed you can paste the following script to your developer tools consoleopen in new window

    document.addEventListener("keydown", (e) => console.log(e), true);
    +

    This will print every keypress event in Pulsar to the console so you can inspect KeyboardEvent.key and KeyboardEvent.code.

    Scoped Settings, Scopes and Scope Descriptors

    Pulsar supports language-specific settings. You can soft wrap only Markdown files, or set the tab length to 4 in Python files.

    Language-specific settings are a subset of something more general we call "scoped settings". Scoped settings allow targeting down to a specific syntax token type. For example, you could conceivably set a setting to target only Ruby comments, only code inside Markdown files, or even only JavaScript function names.

    Scope Names in Syntax Tokens

    Each token in the editor has a collection of scope names. For example, the aforementioned JavaScript function name might have the scope names function and name. An open paren might have the scope names punctuation, parameters, begin.

    Scope names work just like CSS classes. In fact, in the editor, scope names are attached to a token's DOM node as CSS classes.

    Take this piece of JavaScript:

    function functionName() {
    +	console.log("Log it out");
    +}
    +

    In the dev tools, the first line's markup looks like this.

    Markup

    All the class names on the spans are scope names. Any scope name can be used to target a setting's value.

    Scope Selectors

    Scope selectors allow you to target specific tokens just like a CSS selector targets specific nodes in the DOM. Some examples:

    '.source.js' # selects all javascript tokens
    +'.source.js .function.name' # selects all javascript function names
    +'.function.name' # selects all function names in any language
    +

    Config::setopen in new window accepts a scopeSelector. If you'd like to set a setting for JavaScript function names, you can give it the JavaScript function name scopeSelector:

    atom.config.set("my-package.my-setting", "special value", {
    +	scopeSelector: ".source.js .function.name",
    +});
    +

    Scope Descriptors

    A scope descriptor is an Objectopen in new window that wraps an Array of Strings. The Array describes a path from the root of the syntax tree to a token including all scope names for the entire path.

    In our JavaScript example above, a scope descriptor for the function name token would be:

    ["source.js", "meta.function.js", "entity.name.function.js"];
    +

    Config::getopen in new window accepts a scopeDescriptor. You can get the value for your setting scoped to JavaScript function names via:

    const scopeDescriptor = [
    +	"source.js",
    +	"meta.function.js",
    +	"entity.name.function.js",
    +];
    +const value = atom.config.get("my-package.my-setting", {
    +	scope: scopeDescriptor,
    +});
    +

    But, you do not need to generate scope descriptors by hand. There are a couple methods available to get the scope descriptor from the editor:

    Let's revisit our example using these methods:

    const editor = atom.workspace.getActiveTextEditor();
    +const cursor = editor.getLastCursor();
    +const valueAtCursor = atom.config.get("my-package.my-setting", {
    +	scope: cursor.getScopeDescriptor(),
    +});
    +const valueForLanguage = atom.config.get("my-package.my-setting", {
    +	scope: editor.getRootScopeDescriptor(),
    +});
    +

    Serialization in Pulsar

    When a window is refreshed or restored from a previous session, the view and its associated objects are deserialized from a JSON representation that was stored during the window's previous shutdown. For your own views and objects to be compatible with refreshing, you'll need to make them play nicely with the serializing and deserializing.

    Package Serialization Hook

    Your package's main module can optionally include a serialize method, which will be called before your package is deactivated. You should return a JSON-serializable object, which will be handed back to you as an object argument to activate next time it is called. In the following example, the package keeps an instance of MyObject in the same state across refreshes.

    module.exports = {
    +	activate(state) {
    +		this.myObject = state
    +			? atom.deserializers.deserialize(state)
    +			: new MyObject("Hello");
    +	},
    +
    +	serialize() {
    +		return this.myObject.serialize();
    +	},
    +};
    +

    Serialization Methods

    class MyObject {
    +	constructor(data) {
    +		this.data = data;
    +	}
    +
    +	serialize() {
    +		return {
    +			deserializer: "MyObject",
    +			data: this.data,
    +		};
    +	}
    +}
    +

    serialize()

    Objects that you want to serialize should implement .serialize(). This method should return a serializable object, and it must contain a key named deserializer whose value is the name of a registered deserializer that can convert the rest of the data to an object. It's usually just the name of the class itself.

    Registering Deserializers

    The other side of the coin is deserializers, whose job is to convert a state object returned from a previous call to serialize back into a genuine object.

    deserializers in package.json

    The preferred way to register deserializers is via your package's package.json file:

    {
    +  "name": "wordcount",
    +  ...
    +  "deserializers": {
    +    "MyObject": "deserializeMyObject"
    +  }
    +}
    +

    Here, the key ("MyObject") is the name of the deserializer—the same string used by the deserializer field in the object returned by your serialize() method. The value ("deserializeMyObject") is the name of a function in your main module that'll be passed the serialized data and will return a genuine object. For example, your main module might look like this:

    module.exports = {
    +	deserializeMyObject({ data }) {
    +		return new MyObject(data);
    +	},
    +};
    +

    Now you can call the global deserialize method with state returned from serialize, and your class's deserialize method will be selected automatically.

    atom.deserializers.add(klass)

    An alternative is to use the atom.deserializers.add method with your class in order to make it available to the deserialization system. Usually this is used in conjunction with a class-level deserialize method:

    class MyObject {
    +	static initClass() {
    +		atom.deserializers.add(this);
    +	}
    +
    +	static deserialize({ data }) {
    +		return new MyObject(data);
    +	}
    +
    +	constructor(data) {
    +		this.data = data;
    +	}
    +
    +	serialize() {
    +		return {
    +			deserializer: "MyObject",
    +			data: this.data,
    +		};
    +	}
    +}
    +
    +MyObject.initClass();
    +

    While this used to be the standard method of registering a deserializer, the package.json method is now preferred since it allows Pulsar to defer loading and executing your code until it's actually needed.

    Versioning

    class MyObject {
    +	static initClass() {
    +		atom.deserializers.add(this);
    +
    +		this.version = 2;
    +	}
    +
    +	static deserialize(state) {
    +		// ...
    +	}
    +
    +	serialize() {
    +		return {
    +			version: this.constructor.version,
    +			// ...
    +		};
    +	}
    +}
    +
    +MyObject.initClass();
    +

    Your serializable class can optionally have a class-level @version property and include a version key in its serialized state. When deserializing, Pulsar will only attempt to call deserialize if the two versions match, and otherwise return undefined.

    Developing Node Modules

    Pulsar contains a number of packages that are Node modules instead of Pulsar packages. If you want to make changes to the Node modules, for instance atom-keymap, you have to link them into the development environment differently than you would a normal Pulsar package.

    Linking a Node Module Into Your Pulsar Dev Environment

    Here are the steps to run a local version of a Node module within Pulsar. We're using atom-keymap as an example:

    After you get the Node module linked and working, every time you make a change to the Node module's code, you will have to exit Pulsar and do the following:

    $ cd <WHERE YOU CLONED THE NODE MODULE>
    +$ npm install
    +$ cd <WHERE YOU CLONED PULSAR>
    +$ pulsar -p rebuild
    +$ pulsar --dev .
    +

    Interacting With Other Packages Via Services

    Pulsar packages can interact with each other through versioned APIs called services. To provide a service, in your package.json, specify one or more version numbers, each paired with the name of a method on your package's main module:

    {
    +	"providedServices": {
    +		"my-service": {
    +			"description": "Does a useful thing",
    +			"versions": {
    +				"1.2.3": "provideMyServiceV1",
    +				"2.3.4": "provideMyServiceV2"
    +			}
    +		}
    +	}
    +}
    +

    In your package's main module, implement the methods named above. These methods will be called any time a package is activated that consumes their corresponding service. They should return a value that implements the service's API.

    module.exports = {
    +	activate() {
    +		// ...
    +	},
    +
    +	provideMyServiceV1() {
    +		return adaptToLegacyAPI(myService);
    +	},
    +
    +	provideMyServiceV2() {
    +		return myService;
    +	},
    +};
    +

    Similarly, to consume a service, specify one or more version rangesopen in new window, each paired with the name of a method on the package's main module:

    {
    +	"consumedServices": {
    +		"another-service": {
    +			"versions": {
    +				"^1.2.3": "consumeAnotherServiceV1",
    +				">=2.3.4 <2.5": "consumeAnotherServiceV2"
    +			}
    +		}
    +	}
    +}
    +

    These methods will be called any time a package is activated that provides their corresponding service. They will receive the service object as an argument. You will usually need to perform some kind of cleanup in the event that the package providing the service is deactivated. To do this, return a Disposable from your service-consuming method:

    const { Disposable } = require("atom");
    +
    +module.exports = {
    +	activate() {
    +		// ...
    +	},
    +
    +	consumeAnotherServiceV1(service) {
    +		useService(adaptServiceFromLegacyAPI(service));
    +		return new Disposable(() => stopUsingService(service));
    +	},
    +
    +	consumeAnotherServiceV2(service) {
    +		useService(service);
    +		return new Disposable(() => stopUsingService(service));
    +	},
    +};
    +

    Maintaining Your Packages

    Pre-release information

    This section is about a feature in pre-release. The information below documents the intended functionality but there is still ongoing work to support these features with stability.

    While publishing is, by far, the most common action you will perform when working with the packages you provide, there are other things you may need to do.

    Publishing a Package Manually

    STOP

    Publishing a package manually is not a recommended practice and is only for the advanced user who has published packages before. If you perform the steps wrong, you may be unable to publish the new version of your package and may have to completely unpublish your package in order to correct the faulty state. You have been warned.

    Some people prefer to control every aspect of the package publishing process. Normally, the ppm tool manages certain details during publishing to keep things consistent and make everything work smoothly. If you're one of those people that prefers to do things manually, there are certain steps you'll have to take in order to make things work just as smoothly as if ppm has taken care of things for you.

    Note

    Note: The ppm tool will only publish and https://pulsar-edit.devopen in new window will only list packages that are hosted on GitHubopen in new window, regardless of what process is used to publish them.

    When you have completed the changes that you want to publish and are ready to start the publishing process, you must perform the following steps on the master branch:

    1. Update the version number in your package's package.json. The version number must match the regular expression: ^\d+\.\d+\.\d+
    2. Commit the version number change
    3. Create a Git tag referencing the above commit. The tag must match the regular expression ^v\d+\.\d+\.\d+ and the part after the v must match the full text of the version number in the package.json
    4. Execute git push --follow-tags
    5. Execute pulsar -p publish --tag tagname where tagname must match the name of the tag created in the above step

    Adding a Collaborator

    Some packages get too big for one person. Sometimes priorities change and someone else wants to help out. You can let others help or create co-owners by adding them as a collaboratoropen in new window on the GitHub repository for your package. Note: Anyone that has push access to your repository will have the ability to publish new versions of the package that belongs to that repository.

    You can also have packages that are owned by a GitHub organizationopen in new window. Anyone who is a member of an organization's teamopen in new window which has push access to the package's repository will be able to publish new versions of the package.

    Transferring Ownership

    STOP

    🚨 This is a permanent change. There is no going back! 🚨

    If you want to hand off support of your package to someone else, you can do that by transferring the package's repositoryopen in new window to the new owner. Once you do that, they can publish a new version with the updated repository information in the package.json.

    Unpublish Your Package

    If you no longer want to support your package and cannot find anyone to take it over, you can unpublish your package from https://pulsar-edit.devopen in new window. For example, if your package is named package-name then the command you would execute is:

    $ pulsar -p unpublish <package-name>
    +

    This will remove your package from the https://pulsar-edit.devopen in new window package registry. Anyone who has already downloaded a copy of your package will still have it and be able to use it, but it will no longer be available for installation by others.

    Unpublish a Specific Version

    If you mistakenly published a version of your package or perhaps you find a glaring bug or security hole, you may want to unpublish just that version of your package. For example, if your package is named package-name and the bad version of your package is v1.2.3 then the command you would execute is:

    $ pulsar -p unpublish <package-name@1.2.3>
    +

    This will remove just this particular version from the https://pulsar-edit.devopen in new window package registry.

    Rename Your Package

    If you need to rename your package for any reason, you can do so with one simple command – pulsar -p publish --rename changes the name field in your package's package.json, pushes a new commit and tag, and publishes your renamed package. Requests made to the previous name will be forwarded to the new name.

    $ pulsar -p publish --rename <new-package-name>
    +

    Tips

    Tip: Once a package name has been used, it cannot be re-used by another package even if the original package is unpublished.

    Summary

    You should now have a better understanding of some of the core Pulsar APIs and systems.

    + + + diff --git a/docs/launch-manual/sections/behind-pulsar/sections/configuration-api.html b/docs/launch-manual/sections/behind-pulsar/sections/configuration-api.html new file mode 100644 index 0000000000..e42d7ce67f --- /dev/null +++ b/docs/launch-manual/sections/behind-pulsar/sections/configuration-api.html @@ -0,0 +1,243 @@ + + + + + + + + + + + + + + +

    About 1 min

    Configuration API

    Reading Config Settings

    If you are writing a package that you want to make configurable, you'll need to read config settings via the atom.config global. You can read the current value of a namespaced config key with atom.config.get:

    // read a value with `config.get`
    +if (atom.config.get("editor.showInvisibles")) {
    +	this.showInvisibles();
    +}
    +

    Or you can subscribe via atom.config.observe to track changes from any view object.

    const {View} = require('space-pen')
    +
    +class MyView extends View {
    +  function attached() {
    +    this.fontSizeObserveSubscription =
    +      atom.config.observe('editor.fontSize', (newValue, {previous}) => {
    +        this.adjustFontSize(newValue)
    +      })
    +  }
    +
    +  function detached() {
    +    this.fontSizeObserveSubscription.dispose()
    +  }
    +}
    +

    The atom.config.observe method will call the given callback immediately with the current value for the specified key path, and it will also call it in the future whenever the value of that key path changes. If you only want to invoke the callback the next time the value changes, use atom.config.onDidChange instead.

    Subscription methods return Disposableopen in new window objects that can be used to unsubscribe. Note in the example above how we save the subscription to the @fontSizeObserveSubscription instance variable and dispose of it when the view is detached. To group multiple subscriptions together, you can add them all to a CompositeDisposableopen in new window that you dispose when the view is detached.

    Writing Config Settings

    The atom.config database is populated on startup from LNX/MAC: ~/.pulsar/config.cson - WIN: %USERPROFILE%\.pulsar\config.cson but you can programmatically write to it with atom.config.set:

    // basic key update
    +atom.config.set("core.showInvisibles", true);
    +

    If you're exposing package configuration via specific key paths, you'll want to associate them with a schema in your package's main module. Read more about schemas in the Config API documentationopen in new window.

    + + + diff --git a/docs/launch-manual/sections/behind-pulsar/sections/developing-node-modules.html b/docs/launch-manual/sections/behind-pulsar/sections/developing-node-modules.html new file mode 100644 index 0000000000..0837470157 --- /dev/null +++ b/docs/launch-manual/sections/behind-pulsar/sections/developing-node-modules.html @@ -0,0 +1,228 @@ + + + + + + + + + + + + + + +

    About 1 min

    Developing Node Modules

    Pulsar contains a number of packages that are Node modules instead of Pulsar packages. If you want to make changes to the Node modules, for instance atom-keymap, you have to link them into the development environment differently than you would a normal Pulsar package.

    Linking a Node Module Into Your Pulsar Dev Environment

    Here are the steps to run a local version of a Node module within Pulsar. We're using atom-keymap as an example:

    After you get the Node module linked and working, every time you make a change to the Node module's code, you will have to exit Pulsar and do the following:

    $ cd <WHERE YOU CLONED THE NODE MODULE>
    +$ npm install
    +$ cd <WHERE YOU CLONED PULSAR>
    +$ pulsar -p rebuild
    +$ pulsar --dev .
    +
    + + + diff --git a/docs/launch-manual/sections/behind-pulsar/sections/interacting-with-other-packages-via-services.html b/docs/launch-manual/sections/behind-pulsar/sections/interacting-with-other-packages-via-services.html new file mode 100644 index 0000000000..51d4bb779d --- /dev/null +++ b/docs/launch-manual/sections/behind-pulsar/sections/interacting-with-other-packages-via-services.html @@ -0,0 +1,274 @@ + + + + + + + + + + + + + + +

    Less than 1 minute

    Interacting With Other Packages Via Services

    Pulsar packages can interact with each other through versioned APIs called services. To provide a service, in your package.json, specify one or more version numbers, each paired with the name of a method on your package's main module:

    {
    +	"providedServices": {
    +		"my-service": {
    +			"description": "Does a useful thing",
    +			"versions": {
    +				"1.2.3": "provideMyServiceV1",
    +				"2.3.4": "provideMyServiceV2"
    +			}
    +		}
    +	}
    +}
    +

    In your package's main module, implement the methods named above. These methods will be called any time a package is activated that consumes their corresponding service. They should return a value that implements the service's API.

    module.exports = {
    +	activate() {
    +		// ...
    +	},
    +
    +	provideMyServiceV1() {
    +		return adaptToLegacyAPI(myService);
    +	},
    +
    +	provideMyServiceV2() {
    +		return myService;
    +	},
    +};
    +

    Similarly, to consume a service, specify one or more version rangesopen in new window, each paired with the name of a method on the package's main module:

    {
    +	"consumedServices": {
    +		"another-service": {
    +			"versions": {
    +				"^1.2.3": "consumeAnotherServiceV1",
    +				">=2.3.4 <2.5": "consumeAnotherServiceV2"
    +			}
    +		}
    +	}
    +}
    +

    These methods will be called any time a package is activated that provides their corresponding service. They will receive the service object as an argument. You will usually need to perform some kind of cleanup in the event that the package providing the service is deactivated. To do this, return a Disposable from your service-consuming method:

    const { Disposable } = require("atom");
    +
    +module.exports = {
    +	activate() {
    +		// ...
    +	},
    +
    +	consumeAnotherServiceV1(service) {
    +		useService(adaptServiceFromLegacyAPI(service));
    +		return new Disposable(() => stopUsingService(service));
    +	},
    +
    +	consumeAnotherServiceV2(service) {
    +		useService(service);
    +		return new Disposable(() => stopUsingService(service));
    +	},
    +};
    +
    + + + diff --git a/docs/launch-manual/sections/behind-pulsar/sections/keymaps-in-depth.html b/docs/launch-manual/sections/behind-pulsar/sections/keymaps-in-depth.html new file mode 100644 index 0000000000..d8bcc2fc53 --- /dev/null +++ b/docs/launch-manual/sections/behind-pulsar/sections/keymaps-in-depth.html @@ -0,0 +1,262 @@ + + + + + + + + + + + + + + +

    About 6 min

    Keymaps In-Depth

    Structure of a Keymap File

    Keymap files are encoded as JSON or CSON files containing nested hashes. They work much like style sheets, but instead of applying style properties to elements matching the selector, they specify the meaning of keystrokes on elements matching the selector. Here is an example of some bindings that apply when keystrokes pass through atom-text-editor elements:

    Beneath the first selector are several keybindings, mapping specific key combinations to commands. When an element with the atom-text-editor class is focused and LNX/WIN: Ctrl+Backspace - MAC: Alt+Backspace is pressed, a custom DOM event called editor:delete-to-beginning-of-word is emitted on the atom-text-editor element.

    The second selector group also targets editors, but only if they don't have the mini attribute. In this example, the commands for code folding don't really make sense on mini-editors, so the selector restricts them to regular editors.

    Key Combinations

    Key combinations express one or more keys combined with optional modifier keys. For example: ctrl-w v, or cmd-shift-up. A key combination is composed of the following symbols, separated by a -. A key sequence can be expressed as key combinations separated by spaces.

    TypeExamples
    Character literalsa 4 $
    Modifier keyscmd ctrl alt shift
    Special keysenter escape backspace delete tab home end pageup pagedown left right up down space

    Commands

    Commands are custom DOM events that are triggered when a key combination or sequence matches a binding. This allows user interface code to listen for named commands without specifying the specific keybinding that triggers it. For example, the following code creates a command to insert the current date in an editor:

    atom.commands.add("atom-text-editor", {
    +	"user:insert-date": function (event) {
    +		const editor = this.getModel();
    +		return editor.insertText(new Date().toLocaleString());
    +	},
    +});
    +

    atom.commands refers to the global CommandRegistry instance where all commands are set and consequently picked up by the command palette.

    When you are looking to bind new keys, it is often useful to use the Command Palette (LNX/WIN: Ctrl+Shift+P - MAC: Cmd+Shift+P) to discover what commands are being listened for in a given focus context. Commands are "humanized" following a simple algorithm, so a command like editor:fold-current-row would appear as "Editor: Fold Current Row".

    "Composed" Commands

    A common question is, "How do I make a single keybinding execute two or more commands?" There isn't any direct support for this in Pulsar, but it can be achieved by creating a custom command that performs the multiple actions you desire and then creating a keybinding for that command. For example, let's say I want to create a "composed" command that performs a Select Line followed by Cut. You could add the following to your init.js:

    atom.commands.add("atom-text-editor", "custom:cut-line", function () {
    +	const editor = this.getModel();
    +	editor.selectLinesContainingCursors();
    +	editor.cutSelectedText();
    +});
    +

    Then let's say we want to map this custom command to alt-ctrl-z, you could add the following to your keymap:

    'atom-text-editor':
    +  'alt-ctrl-z': 'custom:cut-line'
    +

    Specificity and Cascade Order

    As is the case with CSS applying styles, when multiple bindings match for a single element, the conflict is resolved by choosing the most specific selector. If two matching selectors have the same specificity, the binding for the selector appearing later in the cascade takes precedence.

    Currently, there's no way to specify selector ordering within a single keymap, because JSON objects do not preserve order. We handle cases where selector ordering is critical by breaking the keymap into separate files, such as snippets-1.cson and snippets-2.cson.

    Selectors and Custom Packages

    If a keybinding should only apply to a specific grammar, you can limit bindings to that grammar using the data-grammar attribute on the atom-text-editor element:

    "atom-text-editor[data-grammar='source example']":
    +  'ctrl-.': 'custom:custom-command'
    +

    While selectors can be applied to the entire editor by what grammar is associated with it, they cannot be applied to scopes defined within the grammar or to sub-elements of atom-text-editor.

    Removing Bindings

    When the keymap system encounters a binding with the unset! directive as its command, it will treat the current element as if it had no key bindings matching the current keystroke sequence and continue searching from its parent. For example, the following code removes the keybinding for a in the Tree View, which is normally used to trigger the tree-view:add-file command:

    '.tree-view':
    +  'a': 'unset!'
    +

    Keybinding Resolver

    But if some element above the Tree View had a keybinding for a, that keybinding would still execute even when the focus is inside the Tree View.

    When the keymap system encounters a binding with the abort! directive as its command, it will stop searching for a keybinding. For example, the following code removes the keybinding for LNX/WIN: Ctrl+O - MAC: Cmd+O when the selection is inside an editor pane:

    But if you click inside the Tree View and press LNX/WIN: Ctrl+O - MAC: Cmd+O , it will work.

    Forcing Chromium's Native Keystroke Handling

    If you want to force the native browser behavior for a given keystroke, use the native! directive as the command of a binding. This can be useful to enable the correct behavior in native input elements. If you apply the .native-key-bindings class to an element, all the keystrokes typically handled by the browser will be assigned the native! directive.

    Tips

    Tip: Components and input elements may not correctly handle backspace and arrow keys without forcing this behavior. If your backspace isn't working correctly inside of a component, add either the directive or the .native-key-bindings class.

    Overloading Key Bindings

    Occasionally, it makes sense to layer multiple actions on top of the same key binding. An example of this is the snippets package. Snippets are inserted by typing a snippet prefix such as for and then pressing Tab. Every time Tab is pressed, we want to execute code attempting to expand a snippet if one exists for the text preceding the cursor. If a snippet doesn't exist, we want Tab to actually insert whitespace.

    To achieve this, the snippets package makes use of the .abortKeyBinding() method on the event object representing the snippets:expand command.

    // pseudo-code
    +editor.command("snippets:expand", (e) => {
    +	if (this.cursorFollowsValidPrefix()) {
    +		this.expandSnippet();
    +	} else {
    +		e.abortKeyBinding();
    +	}
    +});
    +

    When the event handler observes that the cursor does not follow a valid prefix, it calls e.abortKeyBinding(), telling the keymap system to continue searching for another matching binding.

    Step-by-Step: How Keydown Events are Mapped to Commands

    • A keydown event occurs on a focused element.
    • Starting at the focused element, the keymap walks upward towards the root of the document, searching for the most specific CSS selector that matches the current DOM element and also contains a keystroke pattern matching the keydown event.
    • When a matching keystroke pattern is found, the search is terminated and the pattern's corresponding command is triggered on the current element.
    • If .abortKeyBinding() is called on the triggered event object, the search is resumed, triggering a binding on the next-most-specific CSS selector for the same element or continuing upward to parent elements.
    • If no bindings are found, the event is handled by Chromium normally.

    Overriding Pulsar's Keyboard Layout Recognition

    Sometimes the problem isn't mapping the command to a key combination, the problem is that Pulsar doesn't recognize properly what keys you're pressing. This is due to some limitations in how Chromium reports keyboard eventsopen in new window. But even this can be customized now.

    You can add the following to your init.js to send Ctrl+@ when you press Ctrl+Alt+G:

    atom.keymaps.addKeystrokeResolver(({ event }) => {
    +	if (
    +		event.code === "KeyG" &&
    +		event.altKey &&
    +		event.ctrlKey &&
    +		event.type !== "keyup"
    +	) {
    +		return "ctrl-@";
    +	}
    +});
    +

    Or if you are still using the init.coffee file:

    atom.keymaps.addKeystrokeResolver ({event}) ->
    +  if event.code is 'KeyG' and event.altKey and event.ctrlKey and event.type isnt 'keyup'
    +    return 'ctrl-@'
    +

    If you want to know the event for the keystroke you pressed you can paste the following script to your developer tools consoleopen in new window

    document.addEventListener("keydown", (e) => console.log(e), true);
    +

    This will print every keypress event in Pulsar to the console so you can inspect KeyboardEvent.key and KeyboardEvent.code.

    + + + diff --git a/docs/launch-manual/sections/behind-pulsar/sections/maintaining-your-packages.html b/docs/launch-manual/sections/behind-pulsar/sections/maintaining-your-packages.html new file mode 100644 index 0000000000..d6a128eed2 --- /dev/null +++ b/docs/launch-manual/sections/behind-pulsar/sections/maintaining-your-packages.html @@ -0,0 +1,226 @@ + + + + + + + + + + + + + + +

    About 3 min

    Maintaining Your Packages

    Pre-release information

    This section is about a feature in pre-release. The information below documents the intended functionality but there is still ongoing work to support these features with stability.

    While publishing is, by far, the most common action you will perform when working with the packages you provide, there are other things you may need to do.

    Publishing a Package Manually

    STOP

    Publishing a package manually is not a recommended practice and is only for the advanced user who has published packages before. If you perform the steps wrong, you may be unable to publish the new version of your package and may have to completely unpublish your package in order to correct the faulty state. You have been warned.

    Some people prefer to control every aspect of the package publishing process. Normally, the ppm tool manages certain details during publishing to keep things consistent and make everything work smoothly. If you're one of those people that prefers to do things manually, there are certain steps you'll have to take in order to make things work just as smoothly as if ppm has taken care of things for you.

    Note

    Note: The ppm tool will only publish and https://pulsar-edit.devopen in new window will only list packages that are hosted on GitHubopen in new window, regardless of what process is used to publish them.

    When you have completed the changes that you want to publish and are ready to start the publishing process, you must perform the following steps on the master branch:

    1. Update the version number in your package's package.json. The version number must match the regular expression: ^\d+\.\d+\.\d+
    2. Commit the version number change
    3. Create a Git tag referencing the above commit. The tag must match the regular expression ^v\d+\.\d+\.\d+ and the part after the v must match the full text of the version number in the package.json
    4. Execute git push --follow-tags
    5. Execute pulsar -p publish --tag tagname where tagname must match the name of the tag created in the above step

    Adding a Collaborator

    Some packages get too big for one person. Sometimes priorities change and someone else wants to help out. You can let others help or create co-owners by adding them as a collaboratoropen in new window on the GitHub repository for your package. Note: Anyone that has push access to your repository will have the ability to publish new versions of the package that belongs to that repository.

    You can also have packages that are owned by a GitHub organizationopen in new window. Anyone who is a member of an organization's teamopen in new window which has push access to the package's repository will be able to publish new versions of the package.

    Transferring Ownership

    STOP

    🚨 This is a permanent change. There is no going back! 🚨

    If you want to hand off support of your package to someone else, you can do that by transferring the package's repositoryopen in new window to the new owner. Once you do that, they can publish a new version with the updated repository information in the package.json.

    Unpublish Your Package

    If you no longer want to support your package and cannot find anyone to take it over, you can unpublish your package from https://pulsar-edit.devopen in new window. For example, if your package is named package-name then the command you would execute is:

    $ pulsar -p unpublish <package-name>
    +

    This will remove your package from the https://pulsar-edit.devopen in new window package registry. Anyone who has already downloaded a copy of your package will still have it and be able to use it, but it will no longer be available for installation by others.

    Unpublish a Specific Version

    If you mistakenly published a version of your package or perhaps you find a glaring bug or security hole, you may want to unpublish just that version of your package. For example, if your package is named package-name and the bad version of your package is v1.2.3 then the command you would execute is:

    $ pulsar -p unpublish <package-name@1.2.3>
    +

    This will remove just this particular version from the https://pulsar-edit.devopen in new window package registry.

    Rename Your Package

    If you need to rename your package for any reason, you can do so with one simple command – pulsar -p publish --rename changes the name field in your package's package.json, pushes a new commit and tag, and publishes your renamed package. Requests made to the previous name will be forwarded to the new name.

    $ pulsar -p publish --rename <new-package-name>
    +

    Tips

    Tip: Once a package name has been used, it cannot be re-used by another package even if the original package is unpublished.

    + + + diff --git a/docs/launch-manual/sections/behind-pulsar/sections/scoped-settings-scopes-and-scope-descriptors.html b/docs/launch-manual/sections/behind-pulsar/sections/scoped-settings-scopes-and-scope-descriptors.html new file mode 100644 index 0000000000..fa1757621d --- /dev/null +++ b/docs/launch-manual/sections/behind-pulsar/sections/scoped-settings-scopes-and-scope-descriptors.html @@ -0,0 +1,249 @@ + + + + + + + + + + + + + + +

    About 2 min

    Scoped Settings, Scopes and Scope Descriptors

    Pulsar supports language-specific settings. You can soft wrap only Markdown files, or set the tab length to 4 in Python files.

    Language-specific settings are a subset of something more general we call "scoped settings". Scoped settings allow targeting down to a specific syntax token type. For example, you could conceivably set a setting to target only Ruby comments, only code inside Markdown files, or even only JavaScript function names.

    Scope Names in Syntax Tokens

    Each token in the editor has a collection of scope names. For example, the aforementioned JavaScript function name might have the scope names function and name. An open paren might have the scope names punctuation, parameters, begin.

    Scope names work just like CSS classes. In fact, in the editor, scope names are attached to a token's DOM node as CSS classes.

    Take this piece of JavaScript:

    function functionName() {
    +	console.log("Log it out");
    +}
    +

    In the dev tools, the first line's markup looks like this.

    Markup

    All the class names on the spans are scope names. Any scope name can be used to target a setting's value.

    Scope Selectors

    Scope selectors allow you to target specific tokens just like a CSS selector targets specific nodes in the DOM. Some examples:

    '.source.js' # selects all javascript tokens
    +'.source.js .function.name' # selects all javascript function names
    +'.function.name' # selects all function names in any language
    +

    Config::setopen in new window accepts a scopeSelector. If you'd like to set a setting for JavaScript function names, you can give it the JavaScript function name scopeSelector:

    atom.config.set("my-package.my-setting", "special value", {
    +	scopeSelector: ".source.js .function.name",
    +});
    +

    Scope Descriptors

    A scope descriptor is an Objectopen in new window that wraps an Array of Strings. The Array describes a path from the root of the syntax tree to a token including all scope names for the entire path.

    In our JavaScript example above, a scope descriptor for the function name token would be:

    ["source.js", "meta.function.js", "entity.name.function.js"];
    +

    Config::getopen in new window accepts a scopeDescriptor. You can get the value for your setting scoped to JavaScript function names via:

    const scopeDescriptor = [
    +	"source.js",
    +	"meta.function.js",
    +	"entity.name.function.js",
    +];
    +const value = atom.config.get("my-package.my-setting", {
    +	scope: scopeDescriptor,
    +});
    +

    But, you do not need to generate scope descriptors by hand. There are a couple methods available to get the scope descriptor from the editor:

    Let's revisit our example using these methods:

    const editor = atom.workspace.getActiveTextEditor();
    +const cursor = editor.getLastCursor();
    +const valueAtCursor = atom.config.get("my-package.my-setting", {
    +	scope: cursor.getScopeDescriptor(),
    +});
    +const valueForLanguage = atom.config.get("my-package.my-setting", {
    +	scope: editor.getRootScopeDescriptor(),
    +});
    +
    + + + diff --git a/docs/launch-manual/sections/behind-pulsar/sections/serialization-in-pulsar.html b/docs/launch-manual/sections/behind-pulsar/sections/serialization-in-pulsar.html new file mode 100644 index 0000000000..8d96c9dde7 --- /dev/null +++ b/docs/launch-manual/sections/behind-pulsar/sections/serialization-in-pulsar.html @@ -0,0 +1,300 @@ + + + + + + + + + + + + + + +

    About 2 min

    Serialization in Pulsar

    When a window is refreshed or restored from a previous session, the view and its associated objects are deserialized from a JSON representation that was stored during the window's previous shutdown. For your own views and objects to be compatible with refreshing, you'll need to make them play nicely with the serializing and deserializing.

    Package Serialization Hook

    Your package's main module can optionally include a serialize method, which will be called before your package is deactivated. You should return a JSON-serializable object, which will be handed back to you as an object argument to activate next time it is called. In the following example, the package keeps an instance of MyObject in the same state across refreshes.

    module.exports = {
    +	activate(state) {
    +		this.myObject = state
    +			? atom.deserializers.deserialize(state)
    +			: new MyObject("Hello");
    +	},
    +
    +	serialize() {
    +		return this.myObject.serialize();
    +	},
    +};
    +

    Serialization Methods

    class MyObject {
    +	constructor(data) {
    +		this.data = data;
    +	}
    +
    +	serialize() {
    +		return {
    +			deserializer: "MyObject",
    +			data: this.data,
    +		};
    +	}
    +}
    +

    serialize()

    Objects that you want to serialize should implement .serialize(). This method should return a serializable object, and it must contain a key named deserializer whose value is the name of a registered deserializer that can convert the rest of the data to an object. It's usually just the name of the class itself.

    Registering Deserializers

    The other side of the coin is deserializers, whose job is to convert a state object returned from a previous call to serialize back into a genuine object.

    deserializers in package.json

    The preferred way to register deserializers is via your package's package.json file:

    {
    +  "name": "wordcount",
    +  ...
    +  "deserializers": {
    +    "MyObject": "deserializeMyObject"
    +  }
    +}
    +

    Here, the key ("MyObject") is the name of the deserializer—the same string used by the deserializer field in the object returned by your serialize() method. The value ("deserializeMyObject") is the name of a function in your main module that'll be passed the serialized data and will return a genuine object. For example, your main module might look like this:

    module.exports = {
    +	deserializeMyObject({ data }) {
    +		return new MyObject(data);
    +	},
    +};
    +

    Now you can call the global deserialize method with state returned from serialize, and your class's deserialize method will be selected automatically.

    atom.deserializers.add(klass)

    An alternative is to use the atom.deserializers.add method with your class in order to make it available to the deserialization system. Usually this is used in conjunction with a class-level deserialize method:

    class MyObject {
    +	static initClass() {
    +		atom.deserializers.add(this);
    +	}
    +
    +	static deserialize({ data }) {
    +		return new MyObject(data);
    +	}
    +
    +	constructor(data) {
    +		this.data = data;
    +	}
    +
    +	serialize() {
    +		return {
    +			deserializer: "MyObject",
    +			data: this.data,
    +		};
    +	}
    +}
    +
    +MyObject.initClass();
    +

    While this used to be the standard method of registering a deserializer, the package.json method is now preferred since it allows Pulsar to defer loading and executing your code until it's actually needed.

    Versioning

    class MyObject {
    +	static initClass() {
    +		atom.deserializers.add(this);
    +
    +		this.version = 2;
    +	}
    +
    +	static deserialize(state) {
    +		// ...
    +	}
    +
    +	serialize() {
    +		return {
    +			version: this.constructor.version,
    +			// ...
    +		};
    +	}
    +}
    +
    +MyObject.initClass();
    +

    Your serializable class can optionally have a class-level @version property and include a version key in its serialized state. When deserializing, Pulsar will only attempt to call deserialize if the two versions match, and otherwise return undefined.

    + + + diff --git a/docs/launch-manual/sections/behind-pulsar/sections/summary.html b/docs/launch-manual/sections/behind-pulsar/sections/summary.html new file mode 100644 index 0000000000..6654dc03de --- /dev/null +++ b/docs/launch-manual/sections/behind-pulsar/sections/summary.html @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + +

    Less than 1 minute

    Summary

    You should now have a better understanding of some of the core Pulsar APIs and systems.

    + + + diff --git a/docs/launch-manual/sections/core-hacking/index.html b/docs/launch-manual/sections/core-hacking/index.html new file mode 100644 index 0000000000..9de180c77e --- /dev/null +++ b/docs/launch-manual/sections/core-hacking/index.html @@ -0,0 +1,1057 @@ + + + + + + + + Hacking the Core | + + + + + + +

    Hacking the Core

    About 2 min

    Under Construction

    This document is under construction, please check back soon for updates. Please see our socials and feel free to ask for assistance or inquire as to the status of this document.

    Now it's time to come to the "Hackable" part of the Hyper-Hackable Editor. As we've seen throughout the second section, a huge part of Pulsar is made up of bundled packages. If you wish to add some functionality to Pulsar, you have access to the same APIs and tools that the core features of Pulsar has. From the tree-viewopen in new window to the command-paletteopen in new window to find-and-replaceopen in new window functionality, even the most core features of Pulsar are implemented as packages.

    In this section, we're going to learn how to extend the functionality of Pulsar through writing packages. This will be everything from new user interfaces to new language grammars to new themes. We'll learn this by writing a series of increasingly complex packages together, introducing you to new APIs and tools and techniques as we need them.

    First though we will look at how to build the editor itself from source.

    If you're looking for an example or specific section using a specific API or feature, you can check below for an index to this section and skip right to it.

    Sections

    Building Pulsar

    If you want to investigate a bug, implement a new feature in Pulsar's core or just want to tinker then you will need to build and run Pulsar from source.

    The Pulsar application code can be found in the pulsar-edit/pulsaropen in new window repository.

    Requirements and dependencies

    To build Pulsar you will need to meet some basic requirements:

    For OS or distribution specific instructions see below:

    Building and running the application

    To build the application so you can start hacking on the core you will need to download the source code to your local machine and cd to the pulsar directory:

    git clone https://github.com/pulsar-edit/pulsar.git && cd pulsar
    +

    Install Node.js (using nvm - see above) and enable corepack (for yarn). This will install the version of Node.js specified in pulsar/.nvmrcopen in new window:

    nvm install
    +corepack enable
    +

    If Node.js is already installed, run the following to make sure the correct version of Node.js is being used (see requirements):

    nvm use
    +node -v
    +

    Run the following to initialize and update the submodules:

    git submodule init && git submodule update
    +

    Now install and build Pulsar & ppm:

    yarn install
    +yarn build
    +yarn build:apm
    +

    Start Pulsar!

    yarn start
    +

    These instructions will also build ppm (Pulsar Package Manager) but it will require some additional configuration for use.

    Building binaries

    The following will allow you to build Pulsar as a stand alone binary or installer. After running you will find your your built application in pulsar/binaries.

    The build script will automatically build for your system's CPU architecture, for example building on an x86_64 CPU will produce binaries for x86_64, building on arm64 will only produce binaries for arm64.

    It is not possible to "cross-build" for different OSs. For Linux binaries you must build from a Linux machine, macOS binaries must be built from macOS etc. Your OS is detected automatically and the script will build the correct binaries for it.

    Using ppm (Pulsar Package Manager)

    ppm is used for installing and managing Pulsar's packages in much the same way that apm did on Atom. However at this point in the project there are a few hoops you have to jump through to get it to work correctly.

    After following the build instructions you will find the ppm binary at pulsar/ppm/bin/apm but by default Pulsar will be looking in the wrong place. There will also be issues relating to the Electron version which will prevent install from the package backend. To solve this a couple of environmental variables need to be exported.

    You can now use the binary to link or install packages.

    For example to install the ide-java package from source:

    # clone the repository and cd into it
    +git clone https://github.com/pulsar-edit/ide-java
    +cd ide-java
    +
    +# from the directory where you are running pulsar source code
    +<pulsar source>/ppm/bin/apm link
    +

    Hacking on the Core

    You will first want to build and run Pulsar from source.

    Running in Development Mode

    Once you have a local copy of Pulsar cloned and built, you can then run Pulsar in Development Mode. But first, if you cloned Pulsar to somewhere other than LNX/MAC: ~/github/pulsar - WIN: %USERPROFILE%\github\pulsar you will need to set the ATOM_DEV_RESOURCE_PATH environment variable to point to the folder in which you cloned Pulsar. To run Pulsar in Dev Mode, use the --dev parameter from the terminal:

    $ pulsar --dev <path-to-open>
    +

    There are a couple benefits of running Pulsar in Dev Mode:

    1. When the ATOM_DEV_RESOURCE_PATH environment variable is set correctly, Pulsar is run using the source code from your local pulsar-edit/pulsar repository. This means you don't have to rebuild after every change, just restart Pulsar 👍
    2. Packages that exist in LNX/MAC: ~/.pulsar/dev/packages - WIN: %USERPROFILE%\.pulsar\dev\packages are loaded instead of packages of the same name normally loaded from other locations. This means that you can have development versions of packages you use loaded but easily go back to the stable versions by launching without Dev Mode.
    3. Packages that contain stylesheets, such as syntax themes, will have those stylesheets automatically reloaded by the dev-live-reloadopen in new window package. This does not live reload JavaScript or CoffeeScript files — you'll need to reload the window (window:reload) to see changes to those.

    Running Pulsar Core Tests Locally

    In order to run Pulsar Core tests from the terminal, first be certain to set the ATOM_DEV_RESOURCE_PATH environment variable as mentioned above and then:

    $ cd <path-to-your-local-pulsar-repo>
    +$ pulsar --test spec
    +

    Tools of the Trade

    To begin, there are a few things we'll assume you know, at least to some degree. Since all of Pulsar is implemented using web technologies, we have to assume you know web technologies such as JavaScript and CSS. Specifically, we'll be using Less, which is a preprocessor for CSS.

    While much of Pulsar has been converted to JavaScript, a lot of older code is still implemented in CoffeeScript but the process of "decaffeination" is ongoing, to continue this conversion feel free to read moreopen in new window. Additionally, Pulsar's default configuration language is CSON, which is based on CoffeeScript. If you don't know CoffeeScript, but you are familiar with JavaScript, you shouldn't have too much trouble. Here is an example of some simple CoffeeScript code:

    MyPackageView = require './my-package-view'
    +
    +module.exports =
    +  myPackageView: null
    +
    +  activate: (state) ->
    +    @myPackageView = new MyPackageView(state.myPackageViewState)
    +
    +  deactivate: ->
    +    @myPackageView.destroy()
    +
    +  serialize: ->
    +    myPackageViewState: @myPackageView.serialize()
    +

    We'll go over examples like this in a bit, but this is what the language looks like. Just about everything you can do with CoffeeScript in Pulsar is also doable in JavaScript. You can brush up on CoffeeScript at coffeescript.orgopen in new window.

    Less is an even simpler transition from CSS. It adds a number of useful things like variables and functions to CSS. You can learn about Less at lesscss.orgopen in new window. Our usage of Less won't get too complex in this book however, so as long as you know basic CSS you should be fine.

    The Init File

    Info

    The default init file for Pulsar has been changed from the previous CoffeeScript init.coffee file used by Atom to JavaScript. The CoffeeScript file will still work but should you wish to reference the specific version of this document for it then you should look at the Atom Archive.

    When Pulsar finishes loading, it will evaluate init.js in your LNX/MAC: ~/.pulsar - WIN: %USERPROFILE%\.pulsar directory, giving you a chance to run JavaScript code to make customizations. Code in this file has full access to Pulsar's APIopen in new window. If customizations become extensive, consider creating a package, which we will cover in Package: Word Count.

    You can open the init.js file in an editor from the LNX: Atom > Init Script - MAC: File > Init Script - WIN: Edit > Init Script menu.

    For example, if you have the Audio Beep configuration setting enabled, you could add the following code to your init.js file to have Pulsar greet you with an audio beep every time it loads:

    atom.beep();
    +

    Because init.js provides access to Pulsar's API, you can use it to implement useful commands without creating a new package or extending an existing one. Here's a command which uses the Selection APIopen in new window and Clipboard APIopen in new window to construct a Markdown link from the selected text and the clipboard contents as the URL:

    atom.commands.add("atom-text-editor", "markdown:paste-as-link", () => {
    +	let clipboardText, editor, selection;
    +	if (!(editor = atom.workspace.getActiveTextEditor())) {
    +		return;
    +	}
    +	selection = editor.getLastSelection();
    +	clipboardText = atom.clipboard.read();
    +	return selection.insertText(
    +		"[" + selection.getText() + "](" + clipboardText + ")"
    +	);
    +});
    +

    Now, reload Pulsar and use the Command Palette to execute the new command, Markdown: Paste As Link, by name. And if you'd like to trigger the command via a keyboard shortcut, you can define a keybinding for the command.

    Package: Word Count

    Let's get started by writing a very simple package and looking at some of the tools needed to develop one effectively. We'll start by writing a package that tells you how many words are in the current buffer and display it in a small modal window.

    Package Generator

    The simplest way to start a package is to use the built-in package generator that ships with Pulsar. As you might expect by now, this generator is itself a separate package implemented in package-generatoropen in new window.

    You can run the generator by invoking the command palette and searching for "Generate Package". A dialog will appear asking you to name your new project. Name it your-name-word-count. Pulsar will then create that directory and fill it out with a skeleton project and link it into your LNX/MAC: ~/.pulsar/packages - WIN: %USERPROFILE%\.pulsar\packages directory so it's loaded when you launch your editor next time.

    Note

    You may encounter a situation where your package is not loaded. That is because a new package using the same name as an actual package hosted on pulsar-edit.devopen in new window (e.g. "wordcount" and "word-count") is not being loaded as you expected. If you follow our suggestion above of using the your-name-word-count package name, you should be safe 😀

    Basic generated Pulsar package

    You can see that Pulsar has created about a dozen files that make up the package. Let's take a look at each of them to get an idea of how a package is structured, then we can modify them to get our word count functionality.

    The basic package layout is as follows:

    my-package/
    +├─ grammars/
    +├─ keymaps/
    +├─ lib/
    +├─ menus/
    +├─ spec/
    +├─ snippets/
    +├─ styles/
    +├─ index.js
    +└─ package.json
    +

    Not every package will have (or need) all of these directories and the package generator doesn't create snippets or grammars. Let's see what some of these are so we can start messing with them.

    package.json

    Similar to Node modulesopen in new window, Pulsar packages contain a package.json file in their top-level directory. This file contains metadata about the package, such as the path to its "main" module, library dependencies, and manifests specifying the order in which its resources should be loaded.

    In addition to some of the regular Node package.json keysopen in new window available, Pulsar package.json files have their own additions.

    • main: the path to the JavaScript file that's the entry point to your package. If this is missing, Pulsar will default to looking for an index.js or index.coffee.
    • styles: an Array of Strings identifying the order of the style sheets your package needs to load. If not specified, style sheets in the styles directory are added alphabetically.
    • keymaps: an Array of Strings identifying the order of the key mappings your package needs to load. If not specified, mappings in the keymaps directory are added alphabetically.
    • menus: an Array of Strings identifying the order of the menu mappings your package needs to load. If not specified, mappings in the menus directory are added alphabetically.
    • snippets: an Array of Strings identifying the order of the snippets your package needs to load. If not specified, snippets in the snippets directory are added alphabetically.
    • activationCommands: an Object identifying commands that trigger your package's activation. The keys are CSS selectors, the values are Arrays of Strings identifying the command. The loading of your package is delayed until one of these events is triggered within the associated scope defined by the CSS selector. If not specified, the activate() method of your main export will be called when your package is loaded.
    • activationHooks: an Array of Strings identifying hooks that trigger your package's activation. The loading of your package is delayed until one of these hooks are triggered. Currently, there are three activation hooks:
      • core:loaded-shell-environment for when Pulsar has finished loading the shell environment variables
      • scope.name:root-scope-used for when a file is opened from the specified language (e.g. source.ruby:root-scope-used)
      • language-package-name:grammar-used for when a specific language package is used (e.g., my-special-language-javascript:grammar-used)
    • workspaceOpeners: An Array of Strings identifying URIs that trigger your package's activation. For example, say your package registers a custom opener for atom://my-custom-panel. By including that string in workspaceOpeners, your package will defer its activation until that URI is opened.

    The package.json in the package we've just generated looks like this currently:

    {
    +	"name": "your-name-word-count",
    +	"main": "./lib/your-name-word-count",
    +	"version": "0.0.0",
    +	"description": "A short description of your package",
    +	"activationCommands": {
    +		"atom-workspace": "your-name-word-count:toggle"
    +	},
    +	"repository": "https://github.com/pulsar-edit/your-name-word-count",
    +	"license": "MIT",
    +	"engines": {
    +		"atom": ">=1.0.0 <2.0.0"
    +	},
    +	"dependencies": {}
    +}
    +

    If you wanted to use activationHooks, you might have:

    {
    +	"name": "your-name-word-count",
    +	"main": "./lib/your-name-word-count",
    +	"version": "0.0.0",
    +	"description": "A short description of your package",
    +	"activationHooks": [
    +		"language-javascript:grammar-used",
    +		"language-coffee-script:grammar-used"
    +	],
    +	"repository": "https://github.com/pulsar-edit/your-name-word-count",
    +	"license": "MIT",
    +	"engines": {
    +		"atom": ">=1.0.0 <2.0.0"
    +	},
    +	"dependencies": {}
    +}
    +

    One of the first things you should do is ensure that this information is filled out. The name, description, repository URL the project will be at, and the license can all be filled out immediately. The other information we'll get into more detail on as we go.

    WARNING

    Do not forget to update the repository URL. The one generated for you is invalid by design and will prevent you from publishing your package until updated.

    Source Code

    If you want to extend Pulsar's behavior, your package should contain a single top-level module, which you export from whichever file is indicated by the main key in your package.json file. In the package we just generated, the main package file is lib/your-name-word-count.js. The remainder of your code should be placed in the lib directory, and required from your top-level file. If the main key is not in your package.json file, it will look for index.js or index.coffee as the main entry point.

    Your package's top-level module is a singleton object that manages the lifecycle of your extensions to Pulsar. Even if your package creates ten different views and appends them to different parts of the DOM, it's all managed from your top-level object.

    Your package's top-level module can implement the following basic methods:

    • activate(state): This optional method is called when your package is activated. It is passed the state data from the last time the window was serialized if your module implements the serialize() method. Use this to do initialization work when your package is started (like setting up DOM elements or binding events). If this method returns a promise the package will be considered loading until the promise resolves (or rejects).
    • initialize(state): This optional method is similar to activate() but is called earlier. Whereas activation occurs after the workspace has been deserialized (and can therefore happen after your package's deserializers have been called), initialize() is guaranteed to be called before everything. Use activate() if you want to be sure that the workspace is ready; use initialize() if you need to do some setup prior to your deserializers or view providers being invoked.
    • serialize(): This optional method is called when the window is shutting down, allowing you to return JSON to represent the state of your component. When the window is later restored, the data you returned is passed to your module's activate method so you can restore your view to where the user left off.
    • deactivate(): This optional method is called when the window is shutting down and when the package is disabled. If your package is watching any files or holding external resources in any other way, release them here. You should also dispose of all subscriptions you're holding on to.

    Style Sheets

    Style sheets for your package should be placed in the styles directory. Any style sheets in this directory will be loaded and attached to the DOM when your package is activated. Style sheets can be written as CSS or Lessopen in new window, but Less is recommended.

    Ideally, you won't need much in the way of styling. Pulsar provides a standard set of components which define both the colors and UI elements for any package that fits into Pulsar seamlessly. You can view all of Pulsar's UI components by opening the styleguide: open the command palette LNX/WIN: Ctrl+Shift+P - MAC: Cmd+Shift+P and search for styleguide, or type LNX/WIN: Ctrl+Shift+G - MAC: Cmd+Ctrl+Shift+G

    If you do need special styling, try to keep only structural styles in the package style sheets. If you must specify colors and sizing, these should be taken from the active theme's ui-variables.lessopen in new window.

    An optional styleSheets array in your package.json can list the style sheets by name to specify a loading order; otherwise, style sheets are loaded alphabetically.

    Keymaps

    You can provide key bindings for commonly used actions for your extension, especially if you're also adding a new command. In our new package, we have a keymap filled in for us already in the keymaps/your-name-word-count.json file:

    {
    +  "atom-workspace": {
    +    "ctrl-alt-o": "your-name-word-count:toggle"
    +  }
    +}
    +

    This means that if you press Alt+Ctrl+O, our package will run the your-name-word-count:toggle command. We'll look at that code next, but if you want to change the default key mapping, you can do that in this file.

    Keymaps are placed in the keymaps subdirectory. By default, all keymaps are loaded in alphabetical order. An optional keymaps array in your package.json can specify which keymaps to load and in what order.

    Keybindings are executed by determining which element the keypress occurred on. In the example above, the your-name-word-count:toggle command is executed when pressing Alt+Ctrl+O on the atom-workspace element. Because the atom-workspace element is the parent of the entire Pulsar UI, this means the key combination will work anywhere in the application.

    We'll cover more advanced keybinding stuff a bit later in Keymaps in Depth.

    Menus are placed in the menus subdirectory. This defines menu elements like what pops up when you right click a context-menu or would go in the application menu to trigger functionality in your package.

    By default, all menus are loaded in alphabetical order. An optional menus array in your package.json can specify which menus to load and in what order.

    Application Menu

    It's recommended that you create an application menu item under the Packages menu for common actions with your package that aren't tied to a specific element. If we look in the menus/your-name-word-count.json file that was generated for us, we'll see a section that looks like this:

    
    +"menu": [
    +  {
    +    "label": "Packages",
    +    "submenu": [
    +      {
    +        "label": "Word Count",
    +        "submenu": [
    +          {
    +            "label": "Toggle",
    +            "command": "your-name-word-count:toggle"
    +          }
    +        ]
    +      }
    +    ]
    +  }
    +]
    +
    +

    This section puts a "Toggle" menu item under a menu group named "Your Name Word Count" in the "Packages" menu.

    Application Menu Item

    When you select that menu item, it will run the your-name-word-count:toggle command, which we'll look at in a bit.

    The menu templates you specify are merged with all other templates provided by other packages in the order which they were loaded.

    Context Menu

    It's recommended to specify a context menu item for commands that are linked to specific parts of the interface. In our menus/your-name-word-count.json file, we can see an auto-generated section that looks like this:

    "context-menu": {
    +    "atom-text-editor": [
    +      {
    +        "label": "Toggle your-name-word-count",
    +        "command": "your-name-word-count:toggle"
    +      }
    +    ]
    +  }
    +

    This adds a "Toggle Word Count" menu option to the menu that pops up when you right-click in an Pulsar text editor pane.

    Context Menu Entry

    When you click that it will again run the your-name-word-count:toggle method in your code.

    Context menus are created by determining which element was selected and then adding all of the menu items whose selectors match that element (in the order which they were loaded). The process is then repeated for the elements until reaching the top of the DOM tree.

    You can also add separators and submenus to your context menus. To add a submenu, provide a submenu key instead of a command. To add a separator, add an item with a single type: 'separator' key/value pair. For instance, you could do something like this:

    {
    +  "context-menu": {
    +    "atom-workspace": [
    +      {
    +        "label": "Text",
    +        "submenu": [
    +          {
    +            "label": "Inspect Element",
    +            "command": "core:inspect"
    +          },
    +          {
    +            "type": "separator"
    +          },
    +          {
    +            "label": "Selector All",
    +            "command": "core:select-all"
    +          },
    +          {
    +            "type": "separator"
    +          },
    +          {
    +            "label": "Deleted Selected Text",
    +            "command": "core:delete"
    +          }
    +        ]
    +      }
    +    ]
    +  }
    +}
    +

    Developing Our Package

    Currently with the generated package we have, if we run that your-name-word-count:toggle command through the menu or the command palette, we'll get a dialog that says "The YourNameWordCount package is Alive! It's ALIVE!".

    Wordcount Package is Alive Dialog

    Understanding the Generated Code

    Let's take a look at the code in our lib directory and see what is happening.

    There are two files in our lib directory. One is the main file (lib/your-name-word-count.js), which is pointed to in the package.json file as the main file to execute for this package. This file handles the logic of the whole package.

    The second file is a View class, lib/your-name-word-count-view.js, which handles the UI elements of the package. Let's look at this file first, since it's pretty simple.

    export default class YourNameWordCountView {
    +	constructor(serializedState) {
    +		// Create root element
    +		this.element = document.createElement("div");
    +		this.element.classList.add("your-name-word-count");
    +
    +		// Create message element
    +		const message = document.createElement("div");
    +		message.textContent = "The YourNameWordCount package is Alive! It's ALIVE!";
    +		message.classList.add("message");
    +		this.element.appendChild(message);
    +	}
    +
    +	// Returns an object that can be retrieved when package is activated
    +	serialize() {}
    +
    +	// Tear down any state and detach
    +	destroy() {
    +		this.element.remove();
    +	}
    +
    +	getElement() {
    +		return this.element;
    +	}
    +}
    +

    Basically the only thing happening here is that when the View class is created, it creates a simple div element and adds the your-name-word-count class to it (so we can find or style it later) and then adds the "Your Name Word Count package is Alive!" text to it. There is also a getElement method which returns that div. The serialize and destroy methods don't do anything and we won't have to worry about that until another example.

    Notice that we're simply using the basic browser DOM methods: createElement() and appendChild().

    The second file we have is the main entry point to the package. Again, because it's referenced in the package.json file. Let's take a look at that file.

    import YourNameWordCountView from "./your-name-word-count-view";
    +import { CompositeDisposable } from "atom";
    +
    +export default {
    +	yourNameWordCountView: null,
    +	modalPanel: null,
    +	subscriptions: null,
    +
    +	activate(state) {
    +		this.yourNameWordCountView = new YourNameWordCountView(
    +			state.yourNameWordCountViewState
    +		);
    +		this.modalPanel = atom.workspace.addModalPanel({
    +			item: this.yourNameWordCountView.getElement(),
    +			visible: false,
    +		});
    +
    +		// Events subscribed to in Pulsar's system can be easily cleaned up with a CompositeDisposable
    +		this.subscriptions = new CompositeDisposable();
    +
    +		// Register command that toggles this view
    +		this.subscriptions.add(
    +			atom.commands.add("atom-workspace", {
    +				"your-name-word-count:toggle": () => this.toggle(),
    +			})
    +		);
    +	},
    +
    +	deactivate() {
    +		this.modalPanel.destroy();
    +		this.subscriptions.dispose();
    +		this.yourNameWordCountView.destroy();
    +	},
    +
    +	serialize() {
    +		return {
    +			yourNameWordCountViewState: this.yourNameWordCountView.serialize(),
    +		};
    +	},
    +
    +	toggle() {
    +		console.log("YourNameWordCount was toggled!");
    +		return this.modalPanel.isVisible()
    +			? this.modalPanel.hide()
    +			: this.modalPanel.show();
    +	},
    +};
    +

    There is a bit more going on here. First of all we can see that we are defining four methods. The only required one is activate. The deactivate and serialize methods are expected by Pulsar but optional. The toggle method is one Pulsar is not looking for, so we'll have to invoke it somewhere for it to be called, which you may recall we do both in the activationCommands section of the package.json file and in the action we have in the menu file.

    The deactivate method simply destroys the various class instances we've created and the serialize method simply passes on the serialization to the View class. Nothing too exciting here.

    The activate command does a number of things. For one, it is not called automatically when Pulsar starts up, it is first called when one of the activationCommands as defined in the package.json file are called. In this case, activate is only called the first time the toggle command is called. If nobody ever invokes the menu item or hotkey, this code is never called.

    This method does two things. The first is that it creates an instance of the View class we have and adds the element that it creates to a hidden modal panel in the Pulsar workspace.

    this.yourNameWordCountView = new YourNameWordCountView(
    +	state.yourNameWordCountViewState
    +);
    +this.modalPanel = atom.workspace.addModalPanel({
    +	item: this.yourNameWordCountView.getElement(),
    +	visible: false,
    +});
    +

    We'll ignore the state stuff for now, since it's not important for this simple package. The rest should be fairly straightforward.

    The next thing this method does is create an instance of the CompositeDisposable class so it can register all the commands that can be called from the package so other packages could subscribe to these events.

    // Events subscribed to in Pulsar's system can be easily cleaned up with a CompositeDisposable
    +this.subscriptions = new CompositeDisposable();
    +
    +// Register command that toggles this view
    +this.subscriptions.add(
    +	atom.commands.add("atom-workspace", {
    +		"your-name-word-count:toggle": () => this.toggle(),
    +	})
    +);
    +

    Next we have the toggle method. This method simply toggles the visibility of the modal panel that we created in the activate method.

    toggle() {
    +  console.log('YourNameWordCount was toggled!');
    +  return (
    +    this.modalPanel.isVisible() ?
    +    this.modalPanel.hide() :
    +    this.modalPanel.show()
    +  );
    +}
    +

    This should be fairly simple to understand. We're looking to see if the modal element is visible and hiding or showing it depending on its current state.

    The Flow

    So, let's review the actual flow in this package.

    1. Pulsar starts up
    2. Pulsar starts loading packages
    3. Pulsar reads your package.json
    4. Pulsar loads keymaps, menus, styles and the main module
    5. Pulsar finishes loading packages
    6. At some point, the user executes your package command your-name-word-count:toggle
    7. Pulsar executes the activate method in your main module which sets up the UI by creating the hidden modal view
    8. Pulsar executes the package command your-name-word-count:toggle which reveals the hidden modal view
    9. At some point, the user executes the your-name-word-count:toggle command again
    10. Pulsar executes the command which hides the modal view
    11. Eventually, Pulsar is shut down which can trigger any serializations that your package has defined

    Tip

    Keep in mind that the flow will be slightly different if you choose not to use activationCommands in your package.

    Counting the Words

    So now that we understand what is happening, let's modify the code so that our little modal box shows us the current word count instead of static text.

    We'll do this in a very simple way. When the dialog is toggled, we'll count the words right before displaying the modal. So let's do this in the toggle command. If we add some code to count the words and ask the view to update itself, we'll have something like this:

    toggle() {
    +  if (this.modalPanel.isVisible()) {
    +    this.modalPanel.hide();
    +  } else {
    +    const editor = atom.workspace.getActiveTextEditor();
    +    const words = editor.getText().split(/\s+/).length;
    +    this.yourNameWordCountView.setCount(words);
    +    this.modalPanel.show();
    +  }
    +}
    +

    Let's look at the 3 lines we've added. First we get an instance of the current editor object (where our text to count is) by calling atom.workspace.getActiveTextEditor()open in new window.

    Next we get the number of words by calling getText()open in new window on our new editor object, then splitting that text on whitespace with a regular expression and then getting the length of that array.

    Finally, we tell our view to update the word count it displays by calling the setCount() method on our view and then showing the modal again. Since that method doesn't yet exist, let's create it now.

    We can add this code to the end of our your-name-word-count-view.js file:

    setCount(count) {
    +  const displayText = `There are ${count} words.`;
    +  this.element.children[0].textContent = displayText;
    +}
    +

    Pretty simple! We take the count number that was passed in and place it into a string that we then stick into the element that our view is controlling.

    Note

    To see your changes, you'll need to reload the code. You can do this by reloading the window (The window:reload command in the Command Palette). A common practice is to have two Pulsar windows, one for developing your package, and one for testing and reloading.

    Word Count Working

    Basic Debugging

    You'll notice a few console.log statements in the code. One of the cool things about Pulsar being built on Chromium is that you can use some of the same debugging tools available to you that you have when doing web development.

    To open up the Developer Console, press LNX/WIN: Ctrl+Shift+I - MAC: Alt+Cmd+I or choose the menu option View > Developer > Toggle Developer Tools.

    Developer Tools Debugging

    From here you can inspect objects, run code and view console output just as though you were debugging a web site.

    Testing

    Your package should have tests, and if they're placed in the spec directory, they can be run by Pulsar.

    Under the hood, Jasmine v1.3open in new window executes your tests, so you can assume that any DSL available there is also available to your package.

    Running Tests

    Once you've got your test suite written, you can run it by pressing LNX/WIN: Alt+Ctrl+P - MAC: Alt+Cmd+Ctrl+P or via the View > Developer > Run Package Specs menu. Our generated package comes with an example test suite, so you can run this right now to see what happens.

    Spec Suite Results

    You can also use the pulsar --test spec command to run them from the command line. It prints the test output and results to the console and returns the proper status code depending on whether the tests passed or failed.

    Summary

    We've now generated, customized and tested our first package for Pulsar. Congratulations! Now let's go ahead and publish it so it's available to the world.

    Package: Modifying Text

    Now that we have our first package written, let's go through examples of other types of packages we can make. This section will guide you though creating a simple command that replaces the selected text with ascii artopen in new window. When you run our new command with the word "cool" selected, it will be replaced with:

                                         o888
    +    ooooooo     ooooooo     ooooooo   888
    +  888     888 888     888 888     888 888
    +  888         888     888 888     888 888
    +    88ooo888    88ooo88     88ooo88  o888o
    +
    +

    This should demonstrate how to do basic text manipulation in the current text buffer and how to deal with selections.

    The final package can be viewed at https://github.com/pulsar-edit/ascii-art.

    Basic Text Insertion

    To begin, press LNX/WIN: Ctrl+Shift+P - MAC: Cmd+Shift+P to bring up the Command Paletteopen in new window. Type "generate package" and select the "Package Generator: Generate Package" command, just as we did in the section on package generation. Enter ascii-art as the name of the package.

    Now let's edit the package files to make our ASCII Art package do something interesting. Since this package doesn't need any UI, we can remove all view-related code so go ahead and delete lib/ascii-art-view.js, spec/ascii-art-view-spec.js, and styles/.

    Next, open up lib/ascii-art.js and remove all view code, so it looks like this:

    const { CompositeDisposable } = require("atom");
    +
    +module.exports = {
    +	subscriptions: null,
    +
    +	activate() {
    +		this.subscriptions = new CompositeDisposable();
    +		this.subscriptions.add(
    +			atom.commands.add("atom-workspace", {
    +				"ascii-art:convert": () => this.convert(),
    +			})
    +		);
    +	},
    +
    +	deactivate() {
    +		this.subscriptions.dispose();
    +	},
    +
    +	convert() {
    +		console.log("Convert text!");
    +	},
    +};
    +

    Create a Command

    Now let's add a command. You should namespace your commands with the package name followed by a : and then the name of the command. As you can see in the code, we called our command ascii-art:convert and we will define it to call the convert() method when it's executed.

    So far, that will simply log to the console. Let's start by making it insert something into the text buffer.

    convert() {
    +  const editor = atom.workspace.getActiveTextEditor()
    +  if (editor) {
    +    editor.insertText('Hello, World!')
    +  }
    +}
    +

    As in Counting Words, we're using atom.workspace.getActiveTextEditor() to get the object that represents the active text editor. If this convert() method is called when not focused on a text editor, nothing will happen.

    Next we insert a string into the current text editor with the insertText()open in new window method. This will insert the text wherever the cursor currently is in the current editor. If there are selections, it will replace all selections with the "Hello, World!" text.

    Reload the Package

    Before we can trigger ascii-art:convert, we need to load the latest code for our package by reloading the window. Run the command "Window: Reload" from the Command Palette or by pressing LNX/WIN: Ctrl+Shift+F5 - MAC: Alt+Cmd+Ctrl+L

    Trigger the Command

    Now open the Command Palette and search for the Ascii Art: Convert command. But it's not there! To fix this, open package.json and find the property called activationCommands. Activation commands make Pulsar launch faster by allowing Pulsar to delay a package's activation until it's needed. So remove the existing command and use ascii-art:convert in activationCommands:

    "activationCommands": {
    +  "atom-workspace": "ascii-art:convert"
    +}
    +

    First, reload the window by running the command "Window: Reload" from the command palette. Now when you run the Ascii Art: Convert command it will insert "Hello, World!" into the active editor, if any.

    Add a Key Binding

    Now let's add a key binding to trigger the ascii-art:convert command. Open keymaps/ascii-art.json and add a key binding linking Alt+Ctrl+A to the ascii-art:convert command. You can delete the pre-existing key binding since you won't need it anymore.

    When finished, the file should look like this:

    {
    +  "atom-text-editor": {
    +    "ctrl-alt-a": "ascii-art:convert"
    +  }
    +}
    +
    +

    Now reload the window and verify that the key binding works.

    WARNING

    The Pulsar keymap system is case-sensitive. This means that there is a distinction between a and A when creating keybindings. a means that you want to trigger the keybinding when you press A. But A means that you want to trigger the keybinding when you press Shift+A. You can also write shift-a when you want to trigger the keybinding when you press Shift+A.

    We strongly recommend always using lowercase and explicitly spelling out when you want to include Shift in your keybindings.

    Add the ASCII Art

    Now we need to convert the selected text to ASCII art. To do this we will use the figletopen in new window Node module from npmopen in new window. Open package.json and add the latest version of figlet to the dependencies:

    "dependencies": {
    +  "figlet": "1.0.8"
    +}
    +

    After saving the file, run the command Update Package Dependencies: Update from the Command Palette. This will install the package's node module dependencies, only figlet in this case. You will need to run Update Package Dependencies: Update whenever you update the dependencies field in your package.json file.

    If for some reason this doesn't work, you'll see a message saying "Failed to update package dependencies" and you will find a new npm-debug.log file in your directory. That file should give you some idea as to what went wrong.

    Now require the figlet node module in lib/ascii-art.js and instead of inserting "Hello, World!", convert the selected text to ASCII art.

    convert () {
    +  const editor = atom.workspace.getActiveTextEditor()
    +  if (editor) {
    +    const selection = editor.getSelectedText()
    +
    +    const figlet = require('figlet')
    +    const font = 'o8'
    +    figlet(selection, {font}, function (error, art) {
    +      if (error) {
    +        console.error(error)
    +      } else {
    +        editor.insertText(`\n${art}\n`)
    +      }
    +    })
    +  }
    +}
    +

    Now reload the editor, select some text in an editor window and press Alt+Ctrl+A. It should be replaced with a ridiculous ASCII art version instead.

    There are a couple of new things in this example we should look at quickly. The first is the editor.getSelectedText()open in new window which, as you might guess, returns the text that is currently selected.

    We then call the Figlet code to convert that into something else and replace the current selection with it with the editor.insertText()open in new window call.

    Summary

    In this section, we've made a UI-less package that takes selected text and replaces it with a processed version. This could be helpful in creating linters or checkers for your code.

    Package: Active Editor Info

    We saw in our Word Count package how we could show information in a modal panel. However, panels aren't the only way to extend Pulsar's UI—you can also add items to the workspace. These items can be dragged to new locations (for example, one of the docks on the edges of the window), and Pulsar will restore them the next time you open the project.

    For this package, we'll define a workspace item that tells us some information about our active text editor. The final package can be viewed at https://github.com/pulsar-edit/active-editor-infoopen in new window.

    Create the Package

    To begin, press LNX/WIN: Ctrl+Shift+P - MAC: Cmd+Shift+P to bring up the Command Paletteopen in new window. Type "generate package" and select the Package Generator: Generate Package command, just as we did in the section on package generation. Enter active-editor-info as the name of the package.

    Add an Opener

    Now let's edit the package files to show our view in a workspace item instead of a modal panel. The way we do this is by registering an opener with Pulsar. Openers are just functions that accept a URI and return a view (if it's a URI that the opener knows about). When you call atom.workspace.open(), Pulsar will go through all of its openers until it finds one that can handle the URI you passed.

    Let's open lib/active-editor-info.js and edit our activate() method to register an opener:

    "use babel";
    +
    +import ActiveEditorInfoView from "./active-editor-info-view";
    +import { CompositeDisposable, Disposable } from "atom";
    +
    +export default {
    +	subscriptions: null,
    +
    +	activate(state) {
    +		this.subscriptions = new CompositeDisposable(
    +			// Add an opener for our view.
    +			atom.workspace.addOpener((uri) => {
    +				if (uri === "atom://active-editor-info") {
    +					return new ActiveEditorInfoView();
    +				}
    +			}),
    +
    +			// Register command that toggles this view
    +			atom.commands.add("atom-workspace", {
    +				"active-editor-info:toggle": () => this.toggle(),
    +			}),
    +
    +			// Destroy any ActiveEditorInfoViews when the package is deactivated.
    +			new Disposable(() => {
    +				atom.workspace.getPaneItems().forEach((item) => {
    +					if (item instanceof ActiveEditorInfoView) {
    +						item.destroy();
    +					}
    +				});
    +			})
    +		);
    +	},
    +
    +	deactivate() {
    +		this.subscriptions.dispose();
    +	},
    +
    +	toggle() {
    +		console.log("Toggle it!");
    +	},
    +};
    +

    You'll notice we also removed the activeEditorInfoView property and the serialize() method. That's because, with workspace items, it's possible to have more than one instance of a given view. Since each instance can have its own state, each should do its own serialization instead of relying on a package-level serialize() method. We'll come back to that later.

    You probably also noticed that our toggle() implementation just logs the text "Toggle it!" to the console. Let's make it actually toggle our view:

      toggle() {
    +    atom.workspace.toggle('atom://active-editor-info');
    +  }
    +

    Updating the View

    Pulsar uses the same view abstractions everywhere, so we can almost use the generated ActiveEditorInfoView class as-is. We just need to add two small methods:

      getTitle() {
    +    // Used by Pulsar for tab text
    +    return 'Active Editor Info';
    +  }
    +
    +  getURI() {
    +    // Used by Pulsar to identify the view when toggling.
    +    return 'atom://active-editor-info';
    +  }
    +

    Now reload the window and run the Active Editor Info: Toggle command from the command palette! Our view will appear in a new tab in the center of the workspace. If you want, you can drag it into one of the docks. Toggling it again will then hide that dock. If you close the tab and run the toggle command again, it will appear in the last place you had it.

    Note

    We've repeated the same URI three times now. That's okay, but it's probably a good idea to define the URL in one place and then import it from that module wherever you need it.

    Constraining Our Item's Locations

    The purpose of our view is to show information about the active text editor, so it doesn't really make sense to show our item in the center of the workspace (where the text editor will be). Let's add some methods to our view class to influence where its opened:

      getDefaultLocation() {
    +    // This location will be used if the user hasn't overridden it by dragging the item elsewhere.
    +    // Valid values are "left", "right", "bottom", and "center" (the default).
    +    return 'right';
    +  }
    +
    +  getAllowedLocations() {
    +    // The locations into which the item can be moved.
    +    return ['left', 'right', 'bottom'];
    +  }
    +

    Now our item will appear in the right dock initially and users will only be able to drag it to one of the other docks.

    Show Active Editor Info

    Now that we have our view all wired up, let's update it to show some information about the active text editor. Add this to the constructor:

    this.subscriptions = atom.workspace
    +	.getCenter()
    +	.observeActivePaneItem((item) => {
    +		if (!atom.workspace.isTextEditor(item)) {
    +			message.innerText = "Open a file to see important information about it.";
    +			return;
    +		}
    +		message.innerHTML = `
    +    <h2>${item.getFileName() || "untitled"}</h2>
    +    <ul>
    +      <li><b>Soft Wrap:</b> ${item.softWrapped}</li>
    +      <li><b>Tab Length:</b> ${item.getTabLength()}</li>
    +      <li><b>Encoding:</b> ${item.getEncoding()}</li>
    +      <li><b>Line Count:</b> ${item.getLineCount()}</li>
    +    </ul>
    +  `;
    +	});
    +

    Now whenever you open a text editor in the center, the view will update with some information about it.

    WARNING

    We use a template string here because it's simple and we have a lot of control over what's going into it, but this could easily result in the insertion of unwanted HTML if you're not careful. Sanitize your input and use the DOM API or a templating system when doing this for real.

    Also, don't forget to clean up the subscription in the destroy() method:

    destroy() {
    +  this.element.remove();
    +  this.subscriptions.dispose();
    +}
    +

    Serialization

    If you were to reload Atom now, you'd see that our item had disappeared. That's because we haven't told Pulsar how to serialize it yet. Let's do that now.

    The first step is to implement a serialize() method on our ActiveEditorInfoView class. Atom will call the serialize() method on every item in the workspace periodically to save its state.

      serialize() {
    +    return {
    +      // This is used to look up the deserializer function. It can be any string, but it needs to be
    +      // unique across all packages!
    +      deserializer: 'active-editor-info/ActiveEditorInfoView'
    +    };
    +  }
    +

    Note

    All of our view's state is derived from the active text editor so we only need the deserializer field. If we had other state that we wanted to preserve across reloads, we would just add things to the object we're returning. Just make sure that they're JSON serializable!

    Next we need to register a deserializer function that Atom can use to recreate the real object when it starts up. The best way to do that is to add a "deserializers" object to our package.json file:

    {
    +  "name": "active-editor-info",
    +  ...
    +  "deserializers": {
    +    "active-editor-info/ActiveEditorInfoView": "deserializeActiveEditorInfoView"
    +  }
    +}
    +

    Notice that the key ("active-editor-info/ActiveEditorInfoView") matches the string we used in our serialize() method above. The value ("deserializeActiveEditorInfoView") refers to a function in our main module, which we still need to add. Go back to active-editor-info.js and do that now:

      deserializeActiveEditorInfoView(serialized) {
    +    return new ActiveEditorInfoView();
    +  }
    +

    The value returned from our serialize() method will be passed to this function. Since our serialized object didn't include any state, we can just return a new ActiveEditorInfoView instance.

    Reload Pulsar and toggle the view with the Active Editor Info: Toggle command. Then reload Pulsar again. Your view should be just where you left it!

    Summary

    In this section, we've made a toggleable workspace item whose placement can be controlled by the user. This could be helpful when creating all sorts of visual tools for working with code!

    Creating a Theme

    Pulsar's interface is rendered using HTML, and it's styled via Lessopen in new window which is a superset of CSS. Don't worry if you haven't heard of Less before; it's just like CSS, but with a few handy extensions.

    Pulsar supports two types of themes: UI and Syntax. UI themes style elements such as the tree view, the tabs, drop-down lists, and the status bar. Syntax themes style the code, gutter and other elements inside the editor view.

    Theme boundary

    Themes can be installed and changed from the Settings View which you can open by selecting the LNX: Edit > Preferences - MAC: Pulsar > Preferences - WIN: File > Preferences menu, and clicking the "Install" or "Themes" tab on the left hand navigation.

    Getting Started

    Themes are pretty straightforward but it's still helpful to be familiar with a few things before starting:

    • Less is a superset of CSS, but it has some really handy features like variables. If you aren't familiar with its syntax, take a few minutes to familiarize yourselfopen in new window.
    • You may also want to review the concept of a package.json (as covered in Pulsar package.json). This file is used to help distribute your theme to Pulsar users.
    • Your theme's package.json must contain a theme key with a value of ui or syntax for Pulsar to recognize and load it as a theme.
    • You can find existing themes to install or fork in Pulsar Package Repositoryopen in new window.

    Creating a Syntax Theme

    Let's create your first theme.

    To get started, press LNX/WIN: Ctrl+Shift+P - MAC: Cmd+Shift+P and start typing Generate Syntax Theme to generate a new theme package. Select Generate Syntax Theme, and you'll be asked for the path where your theme will be created. Let's call ours motif-syntax.

    Tip

    Syntax themes should end with -syntax and UI themes should end with -ui.

    Pulsar will display a new window, showing the motif-syntax theme, with a default set of folders and files created for us. If you open the Settings View with LNX/WIN: Ctrl+, - MAC: Cmd+, and click the "Themes" tab on the left, you'll see the "Motif" theme listed in the "Syntax Theme" drop-down. Select it from the menu to activate it, now when you open an editor you should see your new motif-syntax theme in action.

    Open up styles/colors.less to change the various color variables which have already been defined. For example, turn @red into #f4c2c1.

    Then open styles/base.less and modify the various selectors that have already been defined. These selectors style different parts of code in the editor such as comments, strings and the line numbers in the gutter.

    As an example, let's make the .gutter background-color into @red.

    Reload Pulsar by pressing LNX/WIN: Alt+Ctrl+R - MAC: Alt+Cmd+Ctrl+L to see the changes you made reflected in your Pulsar window. Pretty neat!

    Tip

    You can avoid reloading to see changes you make by opening an Pulsar window in Dev Mode. To open a Dev Mode Pulsar window run pulsar --dev . in the terminal, or use the View > Developer > Open in Dev Mode menu. When you edit your theme, changes will instantly be reflected!

    Note

    It's advised to not specify a font-family in your syntax theme because it will override the Font Family field in Pulsar's settings. If you still like to recommend a font that goes well with your theme, we suggest you do so in your README.

    Creating a UI Theme

    To create a UI theme, do the following:

    1. Fork the ui-theme-templateopen in new window
    2. Clone the forked repository to the local filesystem
    3. Open a terminal in the forked theme's directory
    4. Open your new theme in a Dev Mode Pulsar window run pulsar --dev . in the terminal or use the View > Developer > Open in Dev Mode menu
    5. Change the name of the theme in the theme's package.json file
    6. Name your theme end with a -ui, for example super-white-ui
    7. Run pulsar -p link --dev to symlink your repository to LNX/MAC: ~/.pulsar/dev/packages - WIN: %USERPROFILE%\.pulsar
    8. Reload Pulsar using LNX/WIN: Alt+Ctrl+R - MAC: Alt+Cmd+Ctrl+L
    9. Enable the theme via the "UI Theme" drop-down in the "Themes" tab of the Settings View
    10. Make changes! Since you opened the theme in a Dev Mode window, changes will be instantly reflected in the editor without having to reload.

    Tip

    Because we used pulsar -p link --dev in the above instructions, if you break anything you can always close Pulsar and launch Pulsar normally to force Pulsar to the default theme. This allows you to continue working on your theme even if something goes catastrophically wrong.

    Theme Variables

    UI themes must provide a ui-variables.less and Syntax themes a syntax-variables.less file. It contains predefined variables that packages use to make sure the look and feel matches.

    Here the variables with the default values:

    These default values will be used as a fallback in case a theme doesn't define its own variables.

    Use in Packages

    In any of your package's .less files, you can access the theme variables by importing the ui-variables or syntax-variables file from Pulsar.

    Your package should generally only specify structural styling, and these should come from the style guideopen in new window. Your package shouldn't specify colors, padding sizes, or anything in absolute pixels. You should instead use the theme variables. If you follow this guideline, your package will look good out of the box with any theme!

    Here's an example .less file that a package can define using theme variables:

    @import "ui-variables";
    +
    +.my-selector {
    +	background-color: @base-background-color;
    +	padding: @component-padding;
    +}
    +
    @import "syntax-variables";
    +
    +.my-selector {
    +	background-color: @syntax-background-color;
    +}
    +

    Development workflow

    There are a few tools to help make theme development faster and easier.

    Live Reload

    Reloading by pressing LNX/WIN: Alt+Ctrl+R - MAC: Alt+Cmd+Ctrl+L after you make changes to your theme is less than ideal. Pulsar supports live updatingopen in new window of styles on Pulsar windows in Dev Mode.

    To launch a Dev Mode window:

    • Open your theme directory in a dev window by selecting the View > Developer > Open in Dev Mode menu item
    • Or launch Pulsar from the terminal with pulsar --dev

    If you'd like to reload all the styles at any time, you can use the shortcut LNX/WIN: Alt+Ctrl+R - MAC: Alt+Cmd+Ctrl+L,

    Developer Tools

    Pulsar is based on the Chromium browser and supports its Developer Tools. You can open them by selecting the View > Developer > Toggle Developer Tools menu, or by using the LNX/WIN: Ctrl+Shift+I - MAC: Alt+Cmd+I

    The dev tools allow you to inspect elements and take a look at their CSS properties.

    Developer Tools

    Check out Google's extensive tutorialopen in new window for a short introduction.

    Pulsar Styleguide

    If you are creating an UI theme, you'll want a way to see how your theme changes affect all the components in the system. The Styleguideopen in new window is a page that renders every component Pulsar supports.

    To open the Styleguide, open the command palette with LNX/WIN: Ctrl+Shift+P - MAC: Cmd+Shift+P and search for styleguide, or use the shortcut LNX/WIN: Ctrl+Shift+G - MAC: Cmd+Ctrl+Shift+G.

    Style Guide

    Side by side

    Sometimes when creating a theme (or package) things can go wrong and the editor becomes unusable. E.g. if the text and background have the same color or something gets pushed out of sight. To avoid having to open Pulsar in "normal" mode to fix the issue, it's advised to open two Pulsar windows. One for making changes and one in Dev Mode to see the changes getting applied.

    Side by side screenshot

    Make changes on the left, see the changes getting applied in "Dev Mode" on the right.

    Now if you mess up something, only the window in "Dev Mode" will be affected and you can easily correct the mistake in your "normal" window.

    Publish your theme

    Once you're happy with your theme and would like to share it with other Pulsar users, it's time to publish it. 🎉

    Follow the steps on the Publishing page. The example used is for the Word Count package, but publishing a theme works exactly the same.

    Creating a Grammar

    Pulsar's syntax highlighting and code folding system is powered by Tree-sitteropen in new window. Tree-sitter parsers create and maintain full syntax treesopen in new window representing your code.

    This syntax tree gives Pulsar a comprehensive understanding of the structure of your code, which has several benefits:

    1. Syntax highlighting will not break because of formatting changes.
    2. Code folding will work regardless of how your code is indented.
    3. Editor features can operate on the syntax tree. For instance, the Select Larger Syntax Node and Select Smaller Syntax Node allow you to select conceptually larger and smaller chunks of your code.
    4. Community packages can use the syntax tree to manipulate code intelligently.

    Tree-sitter grammars are relatively new. Many languages in Pulsar are still supported by TextMate grammars, though we intend to phase these out over time.

    If you're adding support for a new language, you're in the right place!

    Getting Started

    There are two components required to use Tree-sitter in Pulsar: a parser and a grammar file.

    The Parser

    Tree-sitter generates parsers based on context-free grammarsopen in new window that are typically written in JavaScript. The generated parsers are C libraries that can be used in other applications as well as Pulsar.

    They can also be developed and tested at the command line, separately from Pulsar. Tree-sitter has its own documentation pageopen in new window on how to create these parsers. The Tree-sitter GitHub organizationopen in new window also contains a lot of example parsers that you can learn from, each in its own repository.

    Once you have created a parser, you need to publish it to the NPM registryopen in new window to use it in Pulsar. To do this, make sure you have a name and version in your parser's package.json:

    {
    +  "name": "tree-sitter-mylanguage",
    +  "version": "0.0.1",
    +  // ...
    +}
    +

    then run the command npm publish.

    The Package

    Once you have a Tree-sitter parser that is available on npm, you can use it in your Pulsar package. Packages with grammars are, by convention, always named starting with language. You'll need a folder with a package.json, a grammars subdirectory, and a single json or cson file in the grammars directory, which can be named anything.

    language-mylanguage
    +├── LICENSE
    +├── README.md
    +├── grammars
    +│   └── mylanguage.cson
    +└── package.json
    +

    The Grammar File

    The mylanguage.cson file specifies how Pulsar should use the parser you created.

    Basic Fields

    It starts with some required fields:

    name: 'My Language'
    +scopeName: 'mylanguage'
    +type: 'tree-sitter'
    +parser: 'tree-sitter-mylanguage'
    +
    • scopeName - A unique, stable identifier for the language. Pulsar users will use this in configuration files if they want to specify custom configuration based on the language.
    • name - A human readable name for the language.
    • parser - The name of the parser node module that will be used for parsing. This string will be passed directly to require()open in new window in order to load the parser.
    • type - This should have the value tree-sitter to indicate to Pulsar that this is a Tree-sitter grammar and not a TextMate grammar.

    Language Recognition

    Next, the file should contain some fields that indicate to Pulsar when this language should be used. These fields are all optional.

    • fileTypes - An array of filename suffixes. The grammar will be used for files whose names end with one of these suffixes. Note that the suffix may be an entire filename.
    • firstLineRegex - A regex pattern that will be tested against the first line of the file. The grammar will be used if this regex matches.
    • contentRegex - A regex pattern that will be tested against the contents of the file in order to break ties in cases where multiple grammars matched the file using the above two criteria. If the contentRegex matches, this grammar will be preferred over another grammar with no contentRegex. If the contentRegex does not match, a grammar with no contentRegex will be preferred over this one.

    Syntax Highlighting

    The HTML classes that Pulsar uses for syntax highlighting do not correspond directly to nodes in the syntax tree. Instead, Tree-sitter grammar files specify scope mappings that specify which classes should be applied to which syntax nodes. The scopes object controls these scope mappings. Its keys are CSS selectors that select nodes in the syntax tree. Its values can be of several different types.

    Here is a simple example:

    scopes:
    +  'call_expression > identifier': 'entity.name.function'
    +

    This entry means that, in the syntax tree, any identifier node whose parent is a call_expression should be highlighted using three classes: syntax--entity, syntax--name, and syntax--function.

    Note that in this selector, we're using the immediate child combinatoropen in new window (>). Arbitrary descendant selectors without this combinator (for example 'call_expression identifier', which would match any identifier occurring anywhere within a call_expression) are currently not supported.

    Advanced Selectors

    The keys of the scopes object can also contain multiple CSS selectors, separated by commas, similar to CSS files. The triple-quote syntax in CSON makes it convenient to write keys like this on multiple lines:

    scopes:
    +  '''
    +  function_declaration > identifier,
    +  call_expression > identifier,
    +  call_expression > field_expression > field_identifier
    +  ''': 'entity.name.function'
    +

    You can use the :nth-child pseudo-classopen in new window to select nodes based on their order within their parent. For example, this example selects identifier nodes which are the fourth (zero-indexed) child of a singleton_method node.

    scopes:
    +  'singleton_method > identifier:nth-child(3)': 'entity.name.function'
    +

    Finally, you can use double-quoted strings in the selectors to select anonymous tokens in the syntax tree, like ( and :. See the Tree-sitter documentationopen in new window for more information about named vs anonymous tokens.

    scopes:
    +  '''
    +    "*",
    +    "/",
    +    "+",
    +    "-"
    +  ''': 'keyword.operator'
    +

    Text-based Mappings

    You can also apply different classes to a syntax node based on its text. Here are some examples:

    scopes:
    +
    +  # Apply the classes `syntax--builtin` and `syntax--variable` to all
    +  # `identifier` nodes whose text is `require`.
    +  'identifier': {exact: 'require', scopes: 'builtin.variable'},
    +
    +  # Apply the classes `syntax--type` and `syntax--integer` to all
    +  # `primitive_type` nodes whose text starts with `int` or `uint`.
    +  'primitive_type': {match: /^u?int/, scopes: 'type.integer'},
    +
    +  # Apply the classes `syntax--builtin`, `syntax--class`, and
    +  # `syntax--name` to `constant` nodes with the text `Array`,
    +  # `Hash` and `String`. For all other `constant` nodes, just
    +  # apply the classes `syntax--class` and `syntax--name`.
    +  'constant': [
    +    {match: '^(Array|Hash|String)$', scopes: 'builtin.class.name'},
    +    'class.name'
    +  ]
    +

    In total there are four types of values that can be associated with selectors in scopes:

    • Strings - Each class name in the dot-separated string will be prefixed with syntax-- and applied to the selected node.
    • Objects with the keys exact and scopes - If the node's text equals the exact string, the scopes string will be used as described above.
    • Objects with the keys match and scopes - If the node's text matches the match regex pattern, the scopes string will be used as described above.
    • Arrays - The elements of the array will be processed from beginning to end. The first element that matches the selected node will be used as describe above.

    Specificity

    If multiple selectors in the scopes object match a node, the node's classes will be decided based on the most specificopen in new window selector. Note that the exact and match rules do not affect specificity, so you may need to supply the same exact or match rules for multiple selectors to ensure that they take precedence over other selectors. You can use the same selector multiple times in a scope mapping, within different comma-separated keys:

    scopes:
    +  'call_expression > identifier': 'entity.name.function'
    +
    +  # If we did not include the second selector here, then this rule
    +  # would not apply to identifiers inside of call_expressions,
    +  # because the selector `call_expression > identifier` is more
    +  # specific than the selector `identifier`.
    +  'identifier, call_expression > identifier': [
    +    {exact: 'require', scopes: 'builtin.variable'},
    +    {match: '^[A-Z]', scopes: 'constructor'},
    +  ]
    +

    Language Injection

    Sometimes, a source file can contain code written in several different languages. Tree-sitter grammars support this situation using a two-part process called language injection. First, an 'outer' language must define an injection point - a set of syntax nodes whose text can be parsed using a different language, along with some logic for guessing the name of the other language that should be used. Second, an 'inner' language must define an injectionRegex - a regex pattern that will be tested against the language name provided by the injection point.

    For example, in JavaScript, tagged template literalsopen in new window sometimes contain code written in a different language, and the name of the language is often used in the 'tag' function, as shown in this example:

    // HTML in a template literal
    +const htmlContent = html`<div>Hello ${name}</div>`;
    +

    The tree-sitter-javascript parser parses this tagged template literal as a call_expression with two children: an identifier and a template_literal:

    (call_expression
    +  (identifier)
    +  (template_literal
    +    (interpolation
    +      (identifier))))
    +

    Here is an injection point that would allow syntax highlighting inside of template literals:

    atom.grammars.addInjectionPoint("source.js", {
    +	type: "call_expression",
    +
    +	language(callExpression) {
    +		const { firstChild } = callExpression;
    +		if (firstChild.type === "identifier") {
    +			return firstChild.text;
    +		}
    +	},
    +
    +	content(callExpression) {
    +		const { lastChild } = callExpression;
    +		if (lastChild.type === "template_string") {
    +			return lastChild;
    +		}
    +	},
    +});
    +

    The language callback would then be called with every call_expression node in the syntax tree. In the example above, it would retrieve the first child of the call_expression, which is an identifier with the name "html". The callback would then return the string "html".

    The content callback would then be called with the same call_expression node and return the template_string node within the call_expression node.

    In order to parse the HTML within the template string, the HTML grammar file would need to specify an injectionRegex:

    injectionRegex: 'html|HTML'
    +

    Code Folding

    The next field in the grammar file, folds, controls code folding. Its value is an array of fold pattern objects. Fold patterns are used to decide whether or not a syntax node can be folded, and if so, where the fold should start and end. Here are some example fold patterns:

    folds: [
    +
    +  # All `comment` nodes are foldable. By default, the fold starts at
    +  # the end of the node's first line, and ends at the beginning
    +  # of the node's last line.
    +  {
    +    type: 'comment'
    +  }
    +
    +  # `if_statement` nodes are foldable if they contain an anonymous
    +  # "then" token and either an `elif_clause` or `else_clause` node.
    +  # The fold starts at the end of the "then" token and ends at the
    +  # `elif_clause` or `else_clause`.
    +  {
    +    type: 'if_statement',
    +    start: {type: '"then"'}
    +    end: {type: ['elif_clause', 'else_clause']}
    +  }
    +
    +  # Any node that starts with an anonymous "(" token and ends with
    +  # an anonymous ")" token is foldable. The fold starts after the
    +  # "(" and ends before the ")".
    +  {
    +    start: {type: '"("', index: 0},
    +    end: {type: '")"', index: -1}
    +  }
    +]
    +

    Fold patterns can have one or more of the following fields:

    • type - A string or array of strings. In order to be foldable according to this pattern, a syntax node's type must match one of these strings.
    • start - An object that is used to identify a child node after which the fold should start. The object can have one or both of the following fields:
      • type - A string or array of strings. To start a fold, a child node's type must match one of these strings.
      • index - a number that's used to select a specific child according to its index. Negative values are interpreted as indices relative the last child, so that -1 means the last child.
    • end - An object that is used to identify a child node before which the fold should end. It has the same structure as the start object.

    Comments

    The last field in the grammar file, comments, controls the behavior of Pulsar's Editor: Toggle Line Comments command. Its value is an object with a start field and an optional end field. The start field is a string that should be prepended to or removed from lines in order to comment or uncomment them.

    In JavaScript, it looks like this:

    comments:
    +  start: '// '
    +

    The end field should be used for languages that only support block comments, not line comments. If present, it will be appended to or removed from the end of the last selected line in order to comment or un-comment the selection.

    In CSS, it would look like this:

    comments:
    +  start: '/* '
    +  end: ' */'
    +

    Example Packages

    More examples of all of these features can be found in the Tree-sitter grammars bundled with Pulsar:

    Creating a Legacy TextMate Grammar

    Pulsar's syntax highlighting can be powered by two types of grammars. If you're adding support for a new language, the preferred way is to create a Tree-sitter grammar. Tree-sitter grammars have better performance and provide support for more editor features, such as the Select Larger Syntax Node command.

    This section describes the Pulsar's legacy support for TextMate grammars.

    TextMate grammars are supported by several popular text editors. They provide a set of regex (regular expression) patterns which are assigned scopes. These scopes are then turned into the CSS classes that you can target in syntax themes.

    Getting Started

    TextMate Grammars depend heavily on regexes, and you should be comfortable with interpreting and writing regexes before continuing. Note that Pulsar uses the Oniguruma engine, which is very similar to the PCRE or Perl regex engines. Here are some resources to help you out:

    • https://www.regular-expressions.info/tutorial.html provides a comprehensive regex tutorial
    • https://www.rexegg.com/regex-quickstart.html contains a cheat sheet for various regex expressions
    • https://regex101.com/ or https://regexr.com/ allows live prototyping
    • https://github.com/kkos/oniguruma/blob/master/doc/RE the docs for the Oniguruma regex engine

    Grammar files are written in the CSONopen in new window or JSONopen in new window format. Whichever one you decide to use is up to you, but this tutorial will be written in CSON.

    Create the Package

    To get started, press LNX/WIN: Ctrl+Shift+P - MAC: Cmd+Shift+P and start typing "Generate Package" to generate a new grammar package. Select "Package Generator: Generate Package," and you'll be asked for the path where your package will be created. Let's call ours language-flight-manual.

    Tip

    Grammar packages should start with language-.

    The default package template creates a lot of folders that aren't needed for grammar packages. Go ahead and delete the keymaps, lib, menus, and styles folders. Furthermore, in package.json, remove the activationCommands section. Now create a new folder called grammars, and inside that a file called flight-manual.cson. This is the main file that we will be working with - start by populating it with a boilerplate templateopen in new window. Now let's go over what each key means.

    scopeName is the root scope of your package. This should generally describe what language your grammar package is highlighting; for example, language-javascript's scopeName is source.js and language-html's is text.html.basic. Name it source.flight-manual for now.

    name is the user-friendly name that is displayed in places like the status bar or the grammar selector. Again, this name should describe what the grammar package is highlighting. Rename it to Flight Manual.

    fileTypes is an array of filetypes that language-flight-manual should highlight. We're interested in highlighting the Flight Manual's Markdown files, so add the md extension to the list and remove the others.

    patterns contains the array of regex patterns that will determine how the file is tokenized.

    Adding Patterns

    To start, let's add a basic pattern to tokenize the words Flight Manual whenever they show up. Your regex should look like \bFlight Manual\b. Here's what your patterns block should look like:

    'patterns': [
    +  {
    +    'match': '\\bFlight Manual\\b'
    +    'name': 'entity.other.flight-manual'
    +  }
    +]
    +

    match is where your regex is contained, and name is the scope name that is to be applied to the entirety of the match. More information about scope names can be found in Section 12.4 of the TextMate Manualopen in new window.

    Tip

    All scopes should end with the portion of the root scopeName after the leading source or text. In our case, all scopes should end with flight-manual.

    Note

    Astute readers may have noticed that the \b was changed to \\b with two backslashes and not one. This is because CSON processes the regex string before handing it to Oniguruma, so all backslashes need to be escaped twice.

    But what if we wanted to apply different scopes to Flight and Manual? This is possible by adding capture groups to the regex and then referencing those capture groups in a new capture property. For example:

    'match': '\\b(Flight) (Manual)\\b'
    +'name': 'entity.other.flight-manual'
    +'captures':
    +  '1':
    +    'name': 'keyword.other.flight.flight-manual'
    +  '2':
    +    'name': 'keyword.other.manual.flight-manual'
    +

    This will assign the scope keyword.other.flight.flight-manual to Flight, keyword.other.manual.flight-manual to Manual, and entity.other.flight-manual to the overarching Flight Manual.

    Begin/End Patterns

    Now let's say we want to tokenize the {{#note}} blocks that occur in Flight Manual files. Our previous two examples used match, but one limit of match is that it can only match single lines. {{#note}} blocks, on the other hand, can span multiple lines. For these cases, you can use the begin/end keys. Once the regex in the begin key is matched, tokenization will continue until the end pattern is reached.

    'begin': '({{)(#note)(}})'
    +'beginCaptures':
    +  '0': # The 0 capture contains the entire match
    +    'name': 'meta.block.start.flight-manual'
    +  '1':
    +    'name': 'punctuation.definition.block.flight-manual'
    +  '2':
    +    'name': 'keyword.note.flight-manual'
    +  '3':
    +    'name': 'punctuation.definition.block.flight-manual'
    +'end': '({{)(/note)(}})'
    +'endCaptures':
    +  '0':
    +    'name': 'meta.block.end.flight-manual'
    +  '1':
    +    'name': 'punctuation.definition.block.flight-manual'
    +  '2':
    +    'name': 'keyword.note.flight-manual'
    +  '3':
    +    'name': 'punctuation.definition.block.flight-manual'
    +'name': 'meta.block.note.flight-manual'
    +

    Tip

    Get into the habit of providing punctuation scopes early on. It's much less effort than having to go back and rewriting all your patterns to support punctuation scopes when your grammar starts to get a bit longer!

    Awesome, we have our first multiline pattern! However, if you've been following along and playing around in your own .md file, you may have noticed that Flight Manual doesn't receive any scopes inside a note block. A begin/end block is essentially a subgrammar of its own: once it starts matching, it will only match its own subpatterns until the end pattern is reached. Since we haven't defined any subpatterns, then clearly nothing will be matched inside of a note block. Let's fix that!

    'begin': '({{)(#note)(}})'
    +'beginCaptures':
    +  '0': # The 0 capture contains the entire match
    +    'name': 'meta.block.start.flight-manual'
    +  '1':
    +    'name': 'punctuation.definition.block.flight-manual'
    +  '2':
    +    'name': 'keyword.note.flight-manual'
    +  '3':
    +    'name': 'punctuation.definition.block.flight-manual'
    +'end': '({{)(/note)(}})'
    +'endCaptures':
    +  '0':
    +    'name': 'meta.block.end.flight-manual'
    +  '1':
    +    'name': 'punctuation.definition.block.flight-manual'
    +  '2':
    +    'name': 'keyword.note.flight-manual'
    +  '3':
    +    'name': 'punctuation.definition.block.flight-manual'
    +'name': 'meta.block.note.flight-manual'
    +'patterns': [
    +  {
    +    'match': '\\b(Flight) (Manual)\\b'
    +    'name': 'entity.other.flight-manual'
    +    'captures':
    +      '1':
    +        'name': 'keyword.other.flight.flight-manual'
    +      '2':
    +        'name': 'keyword.other.manual.flight-manual'
    +  }
    +]
    +

    There. With the patterns block, Flight Manual should now receive the proper scopes.

    Repositories and the Include keyword, or how to avoid duplication

    At this point, note blocks are looking pretty nice, as is the Flight Manual keyword, but the rest of the file is noticeably lacking any form of Markdown syntax highlighting. Is there a way to include the GitHub-Flavored Markdown grammar without copying and pasting everything over? This is where the include keyword comes in. include allows you to include other patterns, even from other grammars! language-gfm's scopeName is source.gfm, so let's include that. Our patterns block should now look like the following:

    'patterns': [
    +  {
    +    'include': 'source.gfm'
    +  }
    +  {
    +    # Flight Manual pattern
    +  }
    +  {
    +    # Note begin/end pattern
    +  }
    +]
    +

    However, including source.gfm has led to another problem: note blocks still don't have any Markdown highlighting! The quick fix would be to add the include pattern to the note's pattern block as well, but now we're duplicating two patterns. You can imagine that as this grammar grows it'll quickly become inefficient to keep copying each new global pattern over to the note pattern as well. Therefore, include helpfully recognizes the special $self scope. $self automatically includes all the top-level patterns of the current grammar. The note block can then be simplified to the following:

    'begin': '({{)(#note)(}})'
    +# beginCaptures
    +'end': '({{)(/note)(}})'
    +# endCaptures
    +'name': 'meta.block.note.flight-manual'
    +'patterns': [
    +  {
    +    'include': '$self'
    +  }
    +]
    +

    Where to Go from Here

    There are several good resources out there that help when writing a grammar. The following is a list of some particularly useful ones (some have been linked to in the sections above as well).

    Converting from TextMate

    It's possible that you have themes or grammars from TextMateopen in new window that you like and use and would like to convert to Pulsar. If so, you're in luck because there are tools to help with the conversion.

    Converting a TextMate Grammar Bundle

    Converting a TextMate bundle will allow you to use its editor preferences, snippets, and colorization inside Pulsar.

    Let's convert the TextMate bundle for the Ropen in new window programming language. You can find other existing TextMate bundles on GitHubopen in new window.

    You can convert the R bundle with the following command:

    $ pulsar -p init --package language-r --convert https://github.com/textmate/r.tmbundle
    +

    You can now change directory into language-r to see the converted bundle. Once you link your package with the pulsar -p link command, your new package is ready to use. Launch Pulsar and open a .r file in the editor to see it in action!

    Converting a TextMate Syntax Theme

    This section will go over how to convert a TextMateopen in new window theme to an Pulsar theme.

    Differences

    TextMate themes use plistopen in new window files while Pulsar themes use CSSopen in new window or Lessopen in new window to style the UI and syntax in the editor.

    The utility that converts the theme first parses the theme's plist file and then creates comparable CSS rules and properties that will style Pulsar similarly.

    Convert the Theme

    Download the theme you wish to convert.

    Now, let's say you've downloaded the theme to ~/Downloads/MyTheme.tmTheme, you can convert the theme with the following command:

    $ pulsar -p init --theme my-theme --convert ~/Downloads/MyTheme.tmTheme
    +

    You can then change directory to my-theme to see the converted theme.

    Activate the Theme

    Once your theme is installed you can enable it by launching Pulsar and opening the Settings View with the LNX: Edit > Preferences - MAC: Pulsar > Preferences - WIN: File > Preferences menu item. Then select the "Themes" tab on the left side navigation. Finally, choose "My Theme" from the "Syntax Theme" dropdown menu to enable your new theme.

    Your theme is now enabled, open an editor to see it in action!

    Publishing

    Pulsar bundles a command line utility called ppm which we first used back in Command Line to search for and install packages via the command line. This is invoked by using the pulsar command with the -p or --package option. The pulsar -p command can also be used to publish Pulsar packages to the public registry and update them.

    See more in Using PPM.

    Prepare Your Package

    There are a few things you should double check before publishing:

    • Your package.json file has name, description, and repository fields.
    • Your package.json name is URL Safe, as in it's not an emoji or special character.
    • Your package.json file has a version field with a value of "0.0.0".
    • Your package.json version field is Semver V2open in new window compliant.
    • Your package.json file has an engines field that contains an entry for atom such as: "engines": {"atom": ">=1.0.0 <2.0.0"}.
    • Your package has a README.md file at the root.
    • Your repository URL in the package.json file is the same as the URL of your repository.
    • Your package is in a Git repository that has been pushed to GitHubopen in new window. Follow this guideopen in new window if your package isn't already on GitHub.

    Publish Your Package

    Before you publish a package it is a good idea to check ahead of time if a package with the same name has already been published to the Pulsar Package Repositoryopen in new window. You can do that by visiting https://web.pulsar-edit.dev/packages/your-package-name to see if the package already exists. If it does, update your package's name to something that is available before proceeding.

    Now let's review what the pulsar -p publish command does:

    1. Registers the package name on Pulsar Package Repository if it is being published for the first time.
    2. Updates the version field in the package.json file and commits it.
    3. Creates a new Git tagopen in new window for the version being published.
    4. Pushes the tag and current branch up to GitHub.
    5. Updates Pulsar Package Repository with the new version being published.

    Now run the following commands to publish your package:

    $ cd path-to-your-package
    +$ pulsar -p publish minor
    +

    If this is the first package you are publishing, the pulsar -p publish command may prompt you for your GitHub username and password. If you have two-factor authentication enabled, use a personal access tokenopen in new window in lieu of a password. This is required to publish and you only need to enter this information the first time you publish. The credentials are stored securely in your keychainopen in new window once you login.

    Your package is now published and available on Pulsar Package Repository. Head on over to https://web.pulsar-edit.dev/packages/your-package-name to see your package's page.

    With pulsar -p publish, you can bump the version and publish by using

    $ pulsar -p publish <version-type>
    +

    where version-type can be major, minor and patch.

    • MAJOR version when you make incompatible API changes
    • MINOR version when you add functionality in a backwards compatible manner
    • PATCH version when you make backwards compatible bug fixes

    i.e. to bump a package from v1.0.0 to v1.1.0:

    $ pulsar -p publish minor
    +

    Check out semantic versioningopen in new window to learn more about best practices for versioning your package releases.

    You can also run pulsar -p help publish to see all the available options and pulsar -p help to see all the other available commands.

    Iconography

    Pulsar comes bundled with the Octicons 4.4.0open in new window icon set. Use them to add icons to your packages.

    NOTE: Some older icons from version 2.1.2 are still kept for backwards compatibility.

    Overview

    In the Styleguide under the "Icons" section you'll find all the Octicons that are available.

    Octicons in the Styleguide

    Usage

    Octicons can be added with simple CSS classes in your markup. Prefix the icon names with icon icon-.

    As an example, to add a monitor icon (device-desktop), use the icon icon-device-desktop classes:

    <span class="icon icon-device-desktop"></span>
    +

    Size

    Octicons look best with a font-size of 16px. It's already used as the default, so you don't need to worry about it. In case you prefer a different icon size, try to use multiples of 16 (32px, 48px etc.) for the sharpest result. Sizes in between are ok too, but might look a bit blurry for icons with straight lines.

    Usability

    Although icons can make your UI visually appealing, when used without a text label, it can be hard to guess its meaning. In cases where space for a text label is insufficient, consider adding a tooltipopen in new window that appears on hover. Or a more subtle title="label" attribute would help as well.

    Debugging

    Pulsar provides several tools to help you understand unexpected behavior and debug problems. This guide describes some of those tools and a few approaches to help you debug and provide more helpful information when submitting issuesopen in new window:

    Update to the Latest Version

    You might be running into an issue which was already fixed in a more recent version of Pulsar than the one you're using.

    If you're using a released version, check which version of Pulsar you're using:

    $ pulsar --version
    +> Pulsar  : 1.63.0-dev
    +> Electron: 12.2.3
    +> Chrome  : 89.0.4389.128
    +> Node    : 14.16.0
    +

    You can find the latest releases on the Pulsar Websiteopen in new window, follow the links for either the latest release or Cirrus CI version. Make sure to mention which version when logging an issue.

    If you're building Pulsar from source, pull down the latest version of master and re-build. Make sure that if logging an issue you include the latest commit hash you built from.

    Using Safe Mode

    A large part of Pulsar's functionality comes from packages you can install. Pulsar will also execute the code in your init script on startup. In some cases, these packages and the code in the init script might be causing unexpected behavior, problems, or performance issues.

    To determine if that is happening, start Pulsar from the terminal in safe mode:

    $ pulsar --safe
    +

    This starts Pulsar, but does not load packages from LNX/MAC: ~/.pulsar/packages or ~/.pulsar/dev/packages - WIN: %USERPROFILE%\.pulsar\packages or %USERPROFILE%\.pulsar\dev\packages. and disables loading of your init script. If you can no longer reproduce the problem in safe mode, it's likely it was caused by one of the packages or the init script.

    If removing or commenting out all content from the init script and starting Pulsar normally still produces the error, then try figuring out which package is causing trouble. Start Pulsar normally again and open the Settings View with LNX/WIN: Ctrl+, - MAC: Cmd+,. Since the Settings View allows you to disable each installed package, you can disable packages one by one until you can no longer reproduce the issue. Restart Pulsar or reload Pulsar with LNX/WIN: Ctrl+Shift+F5 - MAC: Alt+Cmd+Ctrl+L. after you disable each package to make sure it's completely gone.

    When you find the problematic package, you can disable or uninstall the package. We strongly recommend creating an issue on the package's GitHub repository.

    Clearing Saved State

    Pulsar saves a number of things about your environment when you exit in order to restore Pulsar to the same configuration when you next launch the program. In some cases the state that gets saved can be something undesirable that prevents Pulsar from working properly. In these cases, you may want to clear the state that Pulsar has saved.

    DANGER

    Clearing the saved state permanently destroys any state that Pulsar has saved across all projects. This includes unsaved changes to files you may have been editing in all projects. This is a destructive action.

    Clearing the saved state can be done by opening a terminal and executing:

    $ pulsar --clear-window-state
    +

    Reset to Factory Defaults

    In some cases, you may want to reset Pulsar to "factory defaults", in other words clear all of your configuration and remove all packages. This can easily be done by opening a terminal and executing:

    Once that is complete, you can launch Pulsar as normal. Everything will be just as if you first installed Pulsar.

    Tip

    The command given above doesn't delete the old configuration, just puts it somewhere that Pulsar can't find it. If there are pieces of the old configuration you want to retrieve, you can find them in the LNX/MAC: ~/.pulsar-backup - WIN: %USERPROFILE%\.pulsar-backup directory.

    Check for Linked Packages

    If you develop or contribute to Pulsar packages, there may be left-over packages linked to your LNX/MAC: ~/.pulsar/packages or ~/.pulsar/dev/packages - WIN: %USERPROFILE%\.pulsar\packages or %USERPROFILE%\.pulsar\dev\packages. directories. You can use the pulsar -p links command to list all linked packages:

    $ pulsar -p links
    +> /Users/pulsy/.pulsar/dev/packages (0)
    +> └── (no links)
    +> /Users/pulsy/.pulsar/packages (1)
    +> └── color-picker -> /Users/pulsy/github/color-picker
    +

    You can remove links using the pulsar -p unlink command:

    $ pulsar -p unlink color-picker
    +> Unlinking /Users/pulsy/.pulsar/packages/color-picker ✓
    +

    See pulsar -p links --help and pulsar -p unlink --help for more information on these commands.

    Tip

    You can also use pulsar -p unlink --all to easily unlink all packages and themes.

    Check for Incompatible Packages

    If you have packages installed that use native Node modules, when you upgrade to a new version of Pulsar, they might need to be rebuilt. Pulsar detects this and through the incompatible-packages packageopen in new window displays an indicator in the status bar when this happens.

    Incompatible Packages Status Bar Indicator

    If you see this indicator, click it and follow the instructions.

    Check Pulsar and Package Settings

    In some cases, unexpected behavior might be caused by settings in Pulsar or in one of the packages.

    Open Pulsar's Settings Viewopen in new window with LNX/WIN: Ctrl+, - MAC: Cmd+,, the LNX: Edit > Preferences - MAC: Pulsar > Preferences - WIN: File > Preferences menu option, or the Settings View: Open command from the Command Paletteopen in new window.

    Settings View

    Check Pulsar's settings in the Settings View, there's a description of most configuration options in the Basic Customization section. For example, if you want Pulsar to hide the invisible symbols representing whitespace characters, disable the "Show Invisibles" option.

    Some of these options are also available on a per-language basis which means that they may be different for specific languages, for example JavaScript or Python. To check the per-language settings, open the settings for the language package under the Packages tab in the Settings View, for example the language-javascript or language-python package.

    Since Pulsar ships with a set of packages and you can also install additional packages yourself, check the list of packages and their settings. For instance, if you'd like to get rid of the vertical line in the middle of the editor, disable the Wrap Guide packageopen in new window. And if you don't like it when Pulsar strips trailing whitespace or ensures that there's a single trailing newline in the file, you can configure that in the whitespace package'sopen in new window settings.

    Package Settings

    Check Your Configuration

    You might have defined some custom styles, keymaps or snippets in one of your configuration files. In some situations, these personal hacks might be causing the unexpected behavior you're observing so try clearing those files and restarting Pulsar.

    Check Your Keybindings

    If a command is not executing when you press a key combination or the wrong command is executing, there might be an issue with the keybinding for that combination. Pulsar ships with the Keybinding Resolveropen in new window, a neat package which helps you understand what key Pulsar saw you press and the command that was triggered because of it.

    Show the keybinding resolver with LNX/WIN: Ctrl+. - MAC: Cmd+., or with Keybinding Resolver: Show from the Command palette. With the Keybinding Resolver shown, press a key combination:

    Keybinding Resolver

    The Keybinding Resolver shows you a list of keybindings that exist for the key combination, where each item in the list has the following:

    • the command for the keybinding
    • the CSS selector used to define the context in which the keybinding is valid
    • the file in which the keybinding is defined

    The keybindings are listed in two colors. All the keybindings that are matched but not executed are shown in gray. The one that is executed, if any, is shown in green. If the command you wanted to trigger isn't listed, then a keybinding for that command hasn't been loaded.

    If multiple keybindings are matched, Pulsar determines which keybinding will be executed based on the specificity of the selectors and the order in which they were loaded. If the command you wanted to trigger is listed in the Keybinding Resolver, but wasn't the one that was executed, this is normally explained by one of two causes:

    • The key combination was not used in the context defined by the keybinding's selector

      For example, you can't trigger the keybinding for the tree-view:add-file command if the Tree View is not focused.

    • There is another keybinding that took precedence

      This often happens when you install a package which defines keybindings that conflict with existing keybindings. If the package's keybindings have selectors with higher specificity or were loaded later, they'll have priority over existing ones.

    Pulsar loads core Pulsar keybindings and package keybindings first, and user-defined keybindings last. Since user-defined keybindings are loaded last, you can use your keymap.cson file to tweak the keybindings and sort out problems like these. See the Keymaps in Depth section for more information.

    If you notice that a package's keybindings are taking precedence over core Pulsar keybindings, it might be a good idea to report the issue on that package's GitHub repository. You can contact Pulsar maintainers on Pulsar's github discussionsopen in new window.

    Check Font Rendering Issues

    You can determine which fonts are being used to render a specific piece of text by using the Developer Tools. To open the Developer Tools press LNX/WIN: Ctrl+Shift+I - MAC: Alt+Cmd+I. Once the Developer Tools are open, click the "Elements" tab. Use the standard tools for finding the elementopen in new window containing the text you want to check. Once you have selected the element, you can click the "Computed" tab in the styles pane and scroll to the bottom. The list of fonts being used will be shown there:

    Fonts In Use

    Check for Errors in the Developer Tools

    When an unexpected error occurs in Pulsar, you will normally see a red notification which provides details about the error and allows you to create an issue on the right repository:

    Exception Notification

    Not all errors are logged with a notification so if you suspect you're experiencing an error but there's no notification, you can also look for errors in the developer tools Console tab. To access the Console tab, press LNX/WIN: Ctrl+Shift+I - MAC: Alt+Cmd+I to open developer tools and then click the Console tab:

    DevTools Error

    If there are multiple errors, you can scroll down to the bottom of the panel to see the most recent error. Or while reproducing an error, you can right click in the Console tab panel, select Clear console to remove all Console output, and then reproduce the error to see what errors are logged to the Console tab.

    Note

    When running in Dev Mode, the developer tools are automatically shown with the error logged in the Console tab.

    Find Crash Logs

    Diagnose Startup Performance

    If Pulsar is taking a long time to start, you can use the Timecop packageopen in new window to get insight into where Pulsar spends time while loading.

    Timecop

    Timecop displays the following information:

    • Pulsar startup times
    • File compilation times
    • Package loading and activation times
    • Theme loading and activation times

    If a specific package has high load or activation times, you might consider reporting an issue to the maintainers. You can also disable the package to potentially improve future startup times.

    Diagnose Runtime Performance

    If you're experiencing performance problems in a particular situation, your Issue reportsopen in new window will be more valuable if you include a saved profile from Chrome's CPU profiler that gives some insight into what is slow.

    To run a profile, open the Developer Tools with LNX/WIN: Ctrl+Shift+I - MAC: Alt+Cmd+I. From there:

    1. Click the Profiles tab
    2. Select "Collect JavaScript CPU Profile"
    3. Click "Start"

    DevTools Profiler

    Once that is done, then perform the slow action to capture a recording. When finished, click "Stop". Switch to the "Chart" view, and a graph of the recorded actions will appear. You can save and post the profile data by clicking "Save" next to the profile's name in the left panel.

    DevTools Profiler

    To learn more, check out the Chrome documentation on CPU profilingopen in new window.

    Profiling Startup Performance

    If the time for loading the window looks high, you can create a CPU profile for that period using the --profile-startup command line flag when starting Pulsar:

    $ pulsar --profile-startup .
    +

    This will automatically capture a CPU profile as Pulsar is loading and open the Developer Tools once Pulsar loads. From there:

    1. Click the Profiles tab in the Developer Tools
    2. Select the "startup" profile
    3. Click the "Save" link for the startup profile

    You can then include the startup profile in any issue you report.

    Check Your Build Tools

    If you are having issues installing a package using pulsar -p install, this could be because the package has dependencies on libraries that contain native code. This means you will need to have a C++ compiler and Python installed to be able to install it. You can run pulsar -p install --check to see if the Pulsar package manager can build native code on your machine.

    Check out the pre-requisites in the build instructions for your platform for more details.

    Check if your GPU is causing the problem

    If you encounter flickering or other rendering issues, you can stop Pulsar from using your Graphics Processing Unit (GPU) with the --disable-gpu Chromium flag to see if the fault lies with your GPU:

    $ pulsar --disable-gpu
    +

    Chromium (and thus Pulsar) normally uses the GPU to accelerate drawing parts of the interface. --disable-gpu tells Pulsar to not even attempt to do this, and just use the CPU for rendering everything. This means that the parts of the interface that would normally be accelerated using the GPU will instead take slightly longer and render on the CPU. This likely won't make a noticeable difference, but does slightly increase the battery usage on portable devices as the CPU has to work harder to do the things the GPU is optimized for.

    Two other Chromium flags that are useful for debugging are --enable-gpu-rasterization and --force-gpu-rasterization:

    $ pulsar --enable-gpu-rasterization --force-gpu-rasterization
    +

    --enable-gpu-rasterization allows other commands to determine how a layer tile (graphics) should be drawn and --force-gpu-rasterization determines that the Skia GPU backend should be used for drawing layer tiles (only valid with GPU accelerated compositing).

    Be sure to use Chromium flags at the end of the terminal call if you want to use other Pulsar flags as they will not be executed after the Chromium flags e.g.:

    $ pulsar --safe --enable-gpu-rasterization --force-gpu-rasterization
    +

    Writing Specs

    We've looked at and written a few specs through the examples already. Now it's time to take a closer look at the spec framework itself. How exactly do you write tests in Pulsar?

    Pulsar uses Jasmineopen in new window as its spec framework. Any new functionality should have specs to guard against regressions.

    Create a New Spec

    Pulsar specsopen in new window and package specsopen in new window are added to their respective spec directory. The example below creates a spec for Pulsar core.

    Create a Spec File

    Spec files must end with -spec so add sample-spec.js to the spec directory.

    Add One or More describe Methods

    The describe method takes two arguments, a description and a function. If the description explains a behavior it typically begins with when; if it is more like a unit test it begins with the method name.

    describe("when a test is written", function () {
    +	// contents
    +});
    +

    or

    describe("Editor::moveUp", function () {
    +	// contents
    +});
    +

    Add One or More it Methods

    The it method also takes two arguments, a description and a function. Try and make the description flow with the it method. For example, a description of "this should work" doesn't read well as "it this should work". But a description of "should work" sounds great as "it should work".

    describe("when a test is written", function () {
    +	it("has some expectations that should pass", function () {
    +		// Expectations
    +	});
    +});
    +

    Add One or More Expectations

    The best way to learn about expectations is to read the Jasmine documentationopen in new window about them. Below is a simple example.

    describe("when a test is written", function () {
    +	it("has some expectations that should pass", function () {
    +		expect("apples").toEqual("apples");
    +		expect("oranges").not.toEqual("apples");
    +	});
    +});
    +
    Custom Matchers

    In addition to the Jasmine's built-in matchers, Pulsar includes the following:

    • jasmine-jqueryopen in new window
    • The toBeInstanceOf matcher is for the instanceof operator
    • The toHaveLength matcher compares against the .length property
    • The toExistOnDisk matcher checks if the file exists in the filesystem
    • The toHaveFocus matcher checks if the element currently has focus
    • The toShow matcher tests if the element is visible in the dom

    These are defined in spec/spec-helper.jsopen in new window.

    Asynchronous Specs

    Writing Asynchronous specs can be tricky at first. Some examples.

    Promises

    Working with promises is rather easy in Pulsar. You can use our waitsForPromise function.

    describe("when we open a file", function () {
    +	it("should be opened in an editor", function () {
    +		waitsForPromise(function () {
    +			atom.workspace
    +				.open("c.coffee")
    +				.then((editor) => expect(editor.getPath()).toContain("c.coffee"));
    +		});
    +	});
    +});
    +

    This method can be used in the describe, it, beforeEach and afterEach functions.

    describe("when we open a file", function () {
    +	beforeEach(function () {
    +		waitsForPromise(() => atom.workspace.open("c.coffee"));
    +	});
    +
    +	it("should be opened in an editor", function () {
    +		expect(atom.workspace.getActiveTextEditor().getPath()).toContain(
    +			"c.coffee"
    +		);
    +	});
    +});
    +

    If you need to wait for multiple promises use a new waitsForPromise function for each promise. (Caution: Without beforeEach this example will fail!)

    describe("waiting for the packages to load", function () {
    +	beforeEach(function () {
    +		waitsForPromise(() => atom.workspace.open("sample.js"));
    +
    +		waitsForPromise(() => atom.packages.activatePackage("tabs"));
    +
    +		waitsForPromise(() => atom.packages.activatePackage("tree-view"));
    +	});
    +
    +	it("should have waited long enough", function () {
    +		expect(atom.packages.isPackageActive("tabs")).toBe(true);
    +		expect(atom.packages.isPackageActive("tree-view")).toBe(true);
    +	});
    +});
    +

    waitsForPromise can take an additional object argument before the function. The object can have the following properties:

    • shouldReject Whether the promise should reject or resolve (default: false)
    • timeout The amount of time (in ms) to wait for the promise to be resolved or rejected (default: process.env.CI ? 60000 : 5000)
    • label The label to display if promise times out (default: 'promise to be resolved or rejected')
    describe("when we open a file", function () {
    +	it("should be opened in an editor", function () {
    +		waitsForPromise(
    +			{
    +				shouldReject: false,
    +				timeout: 5000,
    +				label: "promise to be resolved or rejected",
    +			},
    +			() =>
    +				atom.workspace
    +					.open("c.coffee")
    +					.then((editor) => expect(editor.getPath()).toContain("c.coffee"))
    +		);
    +	});
    +});
    +

    Asynchronous Functions with Callbacks

    Specs for asynchronous functions can be done using the waitsFor and runs functions. A simple example.

    describe("fs.readdir(path, cb)", function () {
    +	it("is async", function () {
    +		const spy = jasmine.createSpy("fs.readdirSpy");
    +		fs.readdir("/tmp/example", spy);
    +
    +		waitsFor(() => spy.callCount > 0);
    +
    +		runs(function () {
    +			const exp = [null, ["example.coffee"]];
    +
    +			expect(spy.mostRecentCall.args).toEqual(exp);
    +			expect(spy).toHaveBeenCalledWith(null, ["example.coffee"]);
    +		});
    +	});
    +});
    +

    For a more detailed documentation on asynchronous tests please visit the Jasmine documentationopen in new window.

    Running Specs

    Most of the time you'll want to run specs by triggering the window:run-package-specs command. This command is not only to run package specs, it can also be used to run Pulsar core specs when working on Pulsar itself. This will run all the specs in the current project's spec directory.

    To run a limited subset of specs use the fdescribe or fit methods. You can use those to focus a single spec or several specs. Modified from the example above, focusing an individual spec looks like this:

    describe("when a test is written", function () {
    +	fit("has some expectations that should pass", function () {
    +		expect("apples").toEqual("apples");
    +		expect("oranges").not.toEqual("apples");
    +	});
    +});
    +

    Running on CI

    It is now easy to run the specs in a CI environment like Travis and AppVeyor. See the Travis CI For Your Packagesopen in new window and AppVeyor CI For Your Packagesopen in new window posts for more details.

    Running via the Command Line

    To run tests on the command line, run Pulsar with the --test flag followed by one or more paths to test files or directories. You can also specify a --timeout option, which will force-terminate your tests after a certain number of seconds have passed.

    pulsar --test --timeout 60 ./test/test-1.js ./test/test-2.js
    +

    Customizing your test runner

    By default, package tests are run with Jasmine 1.3, which is outdated but can't be changed for compatibility reasons. You can specify your own custom test runner by including an atomTestRunner field in your package.json. Pulsar will require whatever module you specify in this field, so you can use a relative path or the name of a module in your package's dependencies.

    Your test runner module must export a single function, which Pulsar will call within a new window to run your package's tests. Your function will be called with the following parameters:

    • testPaths An array of paths to tests to run. Could be paths to files or directories.
    • buildAtomEnvironment A function that can be called to construct an instance of the atom global. No atom global will be explicitly assigned, but you can assign one in your runner if desired. This function should be called with the following parameters:
      • applicationDelegate An object responsible for Pulsar's interaction with the browser process and host OS. Use buildDefaultApplicationDelegate for a default instance. You can override specific methods on this object to prevent or test these interactions.
      • window A window global.
      • document A document global.
      • configDirPath A path to the configuration directory (usually ~/.pulsar).
      • enablePersistence A boolean indicating whether the Pulsar environment should save or load state from the file system. You probably want this to be false.
    • buildDefaultApplicationDelegate A function that builds a default instance of the application delegate, suitable to be passed as the applicationDelegate parameter to buildAtomEnvironment.
    • logFile An optional path to a log file to which test output should be logged.
    • headless A boolean indicating whether or not the tests are being run from the command line via pulsar --test.
    • legacyTestRunner This function can be invoked to run the legacy Jasmine runner, giving your package a chance to transition to a new test runner while maintaining a subset of its tests in the old environment.

    Your function should return a promise that resolves to an exit code when your tests are finished running. This exit code will be returned when running your tests via the command line.

    Handling URIs

    Packages have the ability to handle special URIs triggered from the system; for example, a package named my-package can register itself to handle any URI starting with atom://my-package/.

    WARNING

    Handling URIs triggered from other applications, like a web browser, is a powerful tool, but also one that can be jarring. You should shape your package's user experience to handle this well. In general, you should avoid taking direct action on behalf of a user. For example, a URI handler that immediately installs a package is too invasive, but a URI handler that shows the package's pane in the settings view is useful. A URI handler that begins to clone a repo is overly aggressive, but a URI handler that prompts the user to clone a repo is okay.

    Any package with a URI handler that we feel violates this guideline is subject to removal from the Pulsar package registry at our discretion.

    Modifying your package.json

    The first step to handling URIs from your package is to modify its package.json file. You should add a new key called uriHandler, and its value should be an object.

    The uriHandler object must contain a key called method with a string value that tells Pulsar which method in your package to call when a URI needs to be handled. The object can optionally include a key called deferActivation which can be set to the boolean false to prevent Pulsar from deferring activation of your package — see more below.

    For example, if we want our package my-package to handle URIs with a method on our package's main module called handleURI, we could add the following to our package.json:

    "uriHandler": {
    +  "method": "handleURI"
    +}
    +

    Modifying your Main Module

    Now that we've told Pulsar that we want our package to handle URIs beginning with atom://my-package/ via our handleURI method, we need to actually write this method. Pulsar passes two arguments to your URI handler method; the first one is the fully-parsed URI plus query string, parsed with Node's url.parse(uri, true)open in new window. The second argument is the raw, string URI; this is normally not needed since the first argument gives you structured information about the URI.

    Here's a sample package, written in JavaScript, that handles URIs with the package.json configuration we saw above.

    export default {
    +	activate() {
    +		// normal activation code here
    +	},
    +
    +	handleURI(parsedUri) {
    +		console.log(parsedUri);
    +	},
    +};
    +

    When Pulsar handles, for example, the URI atom://my-package/my/test/url?value=42&other=false, the package would log out something like the following:

    {
    +  protocol: 'atom:',
    +  slashes: true,
    +  auth: null,
    +  host: 'my-package',
    +  port: null,
    +  hostname: 'my-package',
    +  hash: null,
    +  search: '?value=true&other=false',
    +  query: { value: '42', other: 'false' },
    +  pathname: '/my/test/url',
    +  path: '/my/test/url?value=true&other=false',
    +  href: 'atom://my-package/my/test/url?value=true&other=false'
    +}
    +

    Notice that the query string arguments are available in the query property, but are strings — you'll have to convert to other native types yourself.

    Controlling Activation Deferral

    For performance reasons, adding a uriHandler entry to your package's package.json will enable deferred activation. This means that Pulsar will not activate your package until it has a URI for it to handle — it will then activate your package and then immediately call the URI handler method. If you want to disable the deferred activation, ensuring your package is activated upon startup, you can add "deferActivation": false to the URI handler config. For example,

    "uriHandler": {
    +  "method": "handleURI",
    +  "deferActivation": false
    +}
    +

    Before doing this, make sure your package actually needs to be activated immediately — disabling deferred activation means Pulsar takes longer to start since it has to activate all packages without deferred activation.

    Linux Support

    Because URI handling is different across operating systems and distributions, there is no built-in URI handler support for Pulsar on Linux. If you want to configure URI handling on your system yourself, then you should configure atom: protocol URI's to trigger Pulsar with the --uri-handler flag; for example, the URI atom://test/uri should launch Atom via atom --uri-handler atom://test/uri.

    Core URIs

    Pulsar provides a core URI to handle opening files with the syntax atom://core/open/file?filename=<filepath>&line=<line>&column=<col>

    Cross-Platform Compatibility

    Pulsar runs on a number of platforms and while Electron and Node take care of many of the details there are still some considerations to ensure your package works on other operating systems.

    File symlinks can be used on Windows by non-Administrators by specifying 'junction' as the type (this argument is ignored on macOS & Linux).

    Also consider:

    • Symlinks committed to Git will not checkout correctly on Windows - dynamically create what you need with fs.symlink instead
    • Symlinked directories are only available to Administrators on Windows - avoid a dependency on them

    Filenames

    • Reserved filenames on Windows are com1-com9, lpt1-lpt9, con, nul, aux and prn (regardless of extension, e.g. prn.txt is disallowed)
    • Reserved characters on Windows are ? \ / < > ? % | : " so avoid where possible
    • Names with spaces when passed to the command line;
      • Linux and macOS require a backslash before each space e.g. /my\ test
      • Windows requires you surround the path with double quotes e.g. "c:\my test"

    File paths

    • Windows uses \ although some tools and PowerShell allow / too
    • macOS and Linux use /

    You can dynamically find out what your platform uses with path.sep or better yet use the node path library functions such as join and normalize which automatically take care of this.

    Windows supports up to 250 characters for a path - avoid deeply nested directory structures

    Paths are not URLs

    URL parsing routines should not be used on file paths. While they initially look like a relative path it will fail in a number of scenarios on all platforms.

    • Various characters are misinterpreted, e.g. ? as query string, # as a fragment identifier
    • Windows drive specifiers are incorrectly parsed as a protocol

    If you need to use a path for a URL use the file: protocol with an absolute path instead to ensure drive letters and slashes are appropriately addressed, e.g. file:///c|/test/pic.png

    fs.stat on directories

    The fs.stat function does not return the size of the contents of a directory but rather the allocation size of the directory itself. This returns 0 on Windows and 1024 on macOS and so should not be relied upon.

    path.relative can't traverse drives

    • On a Linux or macOS system path.relative can be used to calculate a relative path to traverse between any two given paths.
    • On Windows this is not always possible as it can contain multiple absolute roots, e.g. c:\ and d:\

    Rapid file operations

    Creation and deletion operations may take a few milliseconds to complete. If you need to remove many files and folders consider RimRAFopen in new window which has built-in retry logic for this.

    Line endings

    • Linux and macOS use LF
    • Windows uses CRLF
    • Git on Windows often has autocrlf set which automatically converts between the two

    If you are writing specs that use text file fixtures consider that this will interfere with file lengths, hash codes and direct text comparisons. It will also change the Atom selection length by 1 character per line.

    If you have spec fixtures that are text files you may want to tell Git to force LF, CRLF or not convert them by specifying the paths in .gitattributes e.g.

    spec/fixtures/always-crlf.txt eol=crlf
    +spec/fixtures/always-lf.txt eol=lf
    +spec/fixtures/leave-as-is.txt -text
    +

    Contributing to Official Pulsar Packages

    If you discover a bug or issue with an official Pulsar package then feel free to open up the issue in that specific repository instead. When in doubt just open the issue on the pulsar-edit/pulsaropen in new window repository but be aware that it may get transferred to the proper package's repository.

    Hacking on Packages

    Cloning

    The first step is creating your own clone. For some packages, you may also need to install the requirements necessary for building Pulsar in order to run pulsar -p install.

    For example, if you want to make changes to the tree-view package, fork the repo on your GitHub account, then clone it:

    $ git clone https://github.com/pulsar-edit/tree-view.git
    +

    Next install all the dependencies:

    $ cd tree-view
    +$ pulsar -p install
    +> Installing modules ✓
    +

    Now you can link it to development mode so when you run an Pulsar window with pulsar -p --dev, you will use your fork instead of the built in package:

    $ pulsar -p link -d
    +

    Running in Development Mode

    Editing a package in Pulsar is a bit of a circular experience: you're using Pulsar to modify itself. What happens if you temporarily break something? You don't want the version of Pulsar you're using to edit to become useless in the process. For this reason, you'll only want to load packages in development mode while you are working on them. You'll perform your editing in stable mode, only switching to development mode to test your changes.

    To open a development mode window, use the Application: Open Dev command. You can also run dev mode from the command line with pulsar --dev.

    To load your package in development mode, create a symlink to it in LNX/MAC: ~/.pulsar/dev/packages - WIN: %USERPROFILE%\.pulsar\dev\packages. This occurs automatically when you clone the package with pulsar -p develop. You can also run pulsar -p link --dev and pulsar -p unlink --dev from the package directory to create and remove dev-mode symlinks.

    Installing Dependencies

    You'll want to keep dependencies up to date by running pulsar -p update after pulling any upstream changes.

    Creating a Fork of a Core Package

    Several of Pulsar's core packages are maintained in the packages directory of the pulsar-edit/pulsar repositoryopen in new window. If you would like to use one of these packages as a starting point for your own package, please follow the steps below.

    Tip

    In most cases, we recommend generating a brand new package or a brand new theme as the starting point for your creation. The guide below applies only to situations where you want to create a package that closely resembles a core Pulsar package.

    Creating Your New Package

    For the sake of this guide, let's assume that you want to start with the current code in the one-light-uiopen in new window package, make some customizations to it, and publish your new package under the name "one-light-ui-plus".

    1. Download the current contents of the pulsar-edit/pulsar repository as a zip fileopen in new window

    2. Unzip the file to a temporary location (for example LNX/MAC: /tmp/pulsar - WIN: C:\TEMP\pulsar)

    3. Copy the contents of the desired package into a working directory for your fork

    1. Create a local repository and commit the initial contents
    $ git init
    +$ git commit -am "Import core Pulsar package"
    +
    1. Update the name property in package.json to give your package a unique name

    2. Make the other customizations that you have in mind

    3. Commit your changes

    $ git commit -am "Apply initial customizations"
    +
    1. Create a public repository on github.comopen in new window for your new package

    2. Follow the instructions in the github.com UI to push your code to your new online repository

    3. Follow the steps in the Publishing guide to publish your new package

    Merging Upstream Changes into Your Package

    The code in the original package will continue to evolve over time, either to fix bugs or to add new enhancements. You may want to incorporate some or all of those updates into your package. To do so, you can follow these steps for merging upstream changes into your package.

    Maintaining a Fork of a Core Package

    Originally, each of Atom's core packages resided in a separate repository. In 2018, in an effort to streamline the development of Atom by reducing overhead, the Atom team consolidated many core Atom packagesopen in new window into the atom/atom repositoryopen in new window. For example, the one-light-ui package was originally maintained in the atom/one-light-uiopen in new window repository, but was moved to the packages/one-light-ui directoryopen in new window in the main repository.

    The Pulsar team has continued this trend and has move even more packages into the core, particularly default language packages. A list of these packages moved can be found in this documentopen in new window.

    If you forked one of the core packages before it was moved into the atom/atom or pulsar-edit/pulsar repository, and you want to continue merging upstream changes into your fork, please follow the steps below.

    Step-by-step guide

    For the sake of this guide, let's assume that you forked the pulsar-edit/one-light-uiopen in new window repository, renamed your fork to one-light-ui-plus, and made some customizations.

    Add pulsar-edit/pulsar as a Remote

    Navigate to your local clone of your fork:

    $ cd path/to/your/fork
    +

    Add the pulsar-edit/pulsar repositoryopen in new window as a git remote:

    $ git remote add upstream https://github.com/pulsar-edit/pulsar.git
    +

    Get the Latest Changes for the Core Package

    Tip

    Follow these steps each time you want to merge upstream changes into your fork.

    Fetch the latest changes from the pulsar-edit/pulsar repository:

    $ git fetch upstream
    +

    Identify recent changes to the core package. For example, if you're maintaining a fork of the one-light-ui package, then you'll want to identify recent changes in the packages/one-light-ui directory:

    $ git log --oneline upstream/master -- packages/one-light-ui
    +f884f6de8 [themes] Rename A[a]tom -> P[p]ulsar
    +0db3190f4 Additional rebranding where needed
    +234adb874 Remove deprecated code strings
    +...
    +

    Look through the log and identify the commits that you want to merge into your fork.

    Merge Upstream Changes into Your Fork

    For each commit that you want to bring into your fork, use [git format-patch][https://git-scm.com/docs/git-format-patch] in conjunction with [git am][https://git-scm.com/docs/git-am]. For example, to merge commit f884f6de8 into your fork:

    $ git format-patch -1 --stdout f884f6de8 | git am -p3
    +

    Repeat this step for each commit that you want to merge into your fork.

    Summary

    If you finished this chapter, you should be an Pulsar-hacking master. We've discussed how you should work with JavaScript and CoffeeScript, and how to put it to good use in creating packages. You should also be able to do this in your own created theme now.

    Even when something goes wrong, you should be able to debug this easily. But also fewer things should go wrong, because you are capable of writing great specs for Pulsar.

    In the next chapter, we’ll go into more of a deep dive on individual internal APIs and systems of Pulsar, even looking at some Pulsar source to see how things are really getting done.

    Having trouble?

    If you have any issues then please feel free to ask for help from the Pulsar Team or the wider community via any of our Community areas

    If you think you have found a bug then please have a look through our existing issuesopen in new window and if you can't find anything then please create a new bug reportopen in new window.

    + + + diff --git a/docs/launch-manual/sections/core-hacking/sections/building-pulsar.html b/docs/launch-manual/sections/core-hacking/sections/building-pulsar.html new file mode 100644 index 0000000000..bd1ced9e12 --- /dev/null +++ b/docs/launch-manual/sections/core-hacking/sections/building-pulsar.html @@ -0,0 +1,233 @@ + + + + + + + + + + + + + + +

    About 2 min

    Building Pulsar

    If you want to investigate a bug, implement a new feature in Pulsar's core or just want to tinker then you will need to build and run Pulsar from source.

    The Pulsar application code can be found in the pulsar-edit/pulsaropen in new window repository.

    Requirements and dependencies

    To build Pulsar you will need to meet some basic requirements:

    For OS or distribution specific instructions see below:

    Building and running the application

    To build the application so you can start hacking on the core you will need to download the source code to your local machine and cd to the pulsar directory:

    git clone https://github.com/pulsar-edit/pulsar.git && cd pulsar
    +

    Install Node.js (using nvm - see above) and enable corepack (for yarn). This will install the version of Node.js specified in pulsar/.nvmrcopen in new window:

    nvm install
    +corepack enable
    +

    If Node.js is already installed, run the following to make sure the correct version of Node.js is being used (see requirements):

    nvm use
    +node -v
    +

    Run the following to initialize and update the submodules:

    git submodule init && git submodule update
    +

    Now install and build Pulsar & ppm:

    yarn install
    +yarn build
    +yarn build:apm
    +

    Start Pulsar!

    yarn start
    +

    These instructions will also build ppm (Pulsar Package Manager) but it will require some additional configuration for use.

    Building binaries

    The following will allow you to build Pulsar as a stand alone binary or installer. After running you will find your your built application in pulsar/binaries.

    The build script will automatically build for your system's CPU architecture, for example building on an x86_64 CPU will produce binaries for x86_64, building on arm64 will only produce binaries for arm64.

    It is not possible to "cross-build" for different OSs. For Linux binaries you must build from a Linux machine, macOS binaries must be built from macOS etc. Your OS is detected automatically and the script will build the correct binaries for it.

    + + + diff --git a/docs/launch-manual/sections/core-hacking/sections/contributing-to-official-pulsar-packages.html b/docs/launch-manual/sections/core-hacking/sections/contributing-to-official-pulsar-packages.html new file mode 100644 index 0000000000..3ca63235ab --- /dev/null +++ b/docs/launch-manual/sections/core-hacking/sections/contributing-to-official-pulsar-packages.html @@ -0,0 +1,228 @@ + + + + + + + + + + + + + + +

    About 1 min

    Contributing to Official Pulsar Packages

    If you discover a bug or issue with an official Pulsar package then feel free to open up the issue in that specific repository instead. When in doubt just open the issue on the pulsar-edit/pulsaropen in new window repository but be aware that it may get transferred to the proper package's repository.

    Hacking on Packages

    Cloning

    The first step is creating your own clone. For some packages, you may also need to install the requirements necessary for building Pulsar in order to run pulsar -p install.

    For example, if you want to make changes to the tree-view package, fork the repo on your GitHub account, then clone it:

    $ git clone https://github.com/pulsar-edit/tree-view.git
    +

    Next install all the dependencies:

    $ cd tree-view
    +$ pulsar -p install
    +> Installing modules ✓
    +

    Now you can link it to development mode so when you run an Pulsar window with pulsar -p --dev, you will use your fork instead of the built in package:

    $ pulsar -p link -d
    +

    Running in Development Mode

    Editing a package in Pulsar is a bit of a circular experience: you're using Pulsar to modify itself. What happens if you temporarily break something? You don't want the version of Pulsar you're using to edit to become useless in the process. For this reason, you'll only want to load packages in development mode while you are working on them. You'll perform your editing in stable mode, only switching to development mode to test your changes.

    To open a development mode window, use the Application: Open Dev command. You can also run dev mode from the command line with pulsar --dev.

    To load your package in development mode, create a symlink to it in LNX/MAC: ~/.pulsar/dev/packages - WIN: %USERPROFILE%\.pulsar\dev\packages. This occurs automatically when you clone the package with pulsar -p develop. You can also run pulsar -p link --dev and pulsar -p unlink --dev from the package directory to create and remove dev-mode symlinks.

    Installing Dependencies

    You'll want to keep dependencies up to date by running pulsar -p update after pulling any upstream changes.

    + + + diff --git a/docs/launch-manual/sections/core-hacking/sections/converting-from-textmate.html b/docs/launch-manual/sections/core-hacking/sections/converting-from-textmate.html new file mode 100644 index 0000000000..76f768a57e --- /dev/null +++ b/docs/launch-manual/sections/core-hacking/sections/converting-from-textmate.html @@ -0,0 +1,225 @@ + + + + + + + + + + + + + + +

    About 1 min

    Converting from TextMate

    It's possible that you have themes or grammars from TextMateopen in new window that you like and use and would like to convert to Pulsar. If so, you're in luck because there are tools to help with the conversion.

    Converting a TextMate Grammar Bundle

    Converting a TextMate bundle will allow you to use its editor preferences, snippets, and colorization inside Pulsar.

    Let's convert the TextMate bundle for the Ropen in new window programming language. You can find other existing TextMate bundles on GitHubopen in new window.

    You can convert the R bundle with the following command:

    $ pulsar -p init --package language-r --convert https://github.com/textmate/r.tmbundle
    +

    You can now change directory into language-r to see the converted bundle. Once you link your package with the pulsar -p link command, your new package is ready to use. Launch Pulsar and open a .r file in the editor to see it in action!

    Converting a TextMate Syntax Theme

    This section will go over how to convert a TextMateopen in new window theme to an Pulsar theme.

    Differences

    TextMate themes use plistopen in new window files while Pulsar themes use CSSopen in new window or Lessopen in new window to style the UI and syntax in the editor.

    The utility that converts the theme first parses the theme's plist file and then creates comparable CSS rules and properties that will style Pulsar similarly.

    Convert the Theme

    Download the theme you wish to convert.

    Now, let's say you've downloaded the theme to ~/Downloads/MyTheme.tmTheme, you can convert the theme with the following command:

    $ pulsar -p init --theme my-theme --convert ~/Downloads/MyTheme.tmTheme
    +

    You can then change directory to my-theme to see the converted theme.

    Activate the Theme

    Once your theme is installed you can enable it by launching Pulsar and opening the Settings View with the LNX: Edit > Preferences - MAC: Pulsar > Preferences - WIN: File > Preferences menu item. Then select the "Themes" tab on the left side navigation. Finally, choose "My Theme" from the "Syntax Theme" dropdown menu to enable your new theme.

    Your theme is now enabled, open an editor to see it in action!

    + + + diff --git a/docs/launch-manual/sections/core-hacking/sections/creating-a-fork-of-a-core-package.html b/docs/launch-manual/sections/core-hacking/sections/creating-a-fork-of-a-core-package.html new file mode 100644 index 0000000000..d418834afd --- /dev/null +++ b/docs/launch-manual/sections/core-hacking/sections/creating-a-fork-of-a-core-package.html @@ -0,0 +1,226 @@ + + + + + + + + + + + + + + +

    About 2 min

    Creating a Fork of a Core Package

    Several of Pulsar's core packages are maintained in the packages directory of the pulsar-edit/pulsar repositoryopen in new window. If you would like to use one of these packages as a starting point for your own package, please follow the steps below.

    Tip

    In most cases, we recommend generating a brand new package or a brand new theme as the starting point for your creation. The guide below applies only to situations where you want to create a package that closely resembles a core Pulsar package.

    Creating Your New Package

    For the sake of this guide, let's assume that you want to start with the current code in the one-light-uiopen in new window package, make some customizations to it, and publish your new package under the name "one-light-ui-plus".

    1. Download the current contents of the pulsar-edit/pulsar repository as a zip fileopen in new window

    2. Unzip the file to a temporary location (for example LNX/MAC: /tmp/pulsar - WIN: C:\TEMP\pulsar)

    3. Copy the contents of the desired package into a working directory for your fork

    1. Create a local repository and commit the initial contents
    $ git init
    +$ git commit -am "Import core Pulsar package"
    +
    1. Update the name property in package.json to give your package a unique name

    2. Make the other customizations that you have in mind

    3. Commit your changes

    $ git commit -am "Apply initial customizations"
    +
    1. Create a public repository on github.comopen in new window for your new package

    2. Follow the instructions in the github.com UI to push your code to your new online repository

    3. Follow the steps in the Publishing guide to publish your new package

    Merging Upstream Changes into Your Package

    The code in the original package will continue to evolve over time, either to fix bugs or to add new enhancements. You may want to incorporate some or all of those updates into your package. To do so, you can follow these steps for merging upstream changes into your package.

    + + + diff --git a/docs/launch-manual/sections/core-hacking/sections/creating-a-grammar.html b/docs/launch-manual/sections/core-hacking/sections/creating-a-grammar.html new file mode 100644 index 0000000000..3d8f142a17 --- /dev/null +++ b/docs/launch-manual/sections/core-hacking/sections/creating-a-grammar.html @@ -0,0 +1,341 @@ + + + + + + + + + + + + + + +

    About 7 min

    Creating a Grammar

    Pulsar's syntax highlighting and code folding system is powered by Tree-sitteropen in new window. Tree-sitter parsers create and maintain full syntax treesopen in new window representing your code.

    This syntax tree gives Pulsar a comprehensive understanding of the structure of your code, which has several benefits:

    1. Syntax highlighting will not break because of formatting changes.
    2. Code folding will work regardless of how your code is indented.
    3. Editor features can operate on the syntax tree. For instance, the Select Larger Syntax Node and Select Smaller Syntax Node allow you to select conceptually larger and smaller chunks of your code.
    4. Community packages can use the syntax tree to manipulate code intelligently.

    Tree-sitter grammars are relatively new. Many languages in Pulsar are still supported by TextMate grammars, though we intend to phase these out over time.

    If you're adding support for a new language, you're in the right place!

    Getting Started

    There are two components required to use Tree-sitter in Pulsar: a parser and a grammar file.

    The Parser

    Tree-sitter generates parsers based on context-free grammarsopen in new window that are typically written in JavaScript. The generated parsers are C libraries that can be used in other applications as well as Pulsar.

    They can also be developed and tested at the command line, separately from Pulsar. Tree-sitter has its own documentation pageopen in new window on how to create these parsers. The Tree-sitter GitHub organizationopen in new window also contains a lot of example parsers that you can learn from, each in its own repository.

    Once you have created a parser, you need to publish it to the NPM registryopen in new window to use it in Pulsar. To do this, make sure you have a name and version in your parser's package.json:

    {
    +  "name": "tree-sitter-mylanguage",
    +  "version": "0.0.1",
    +  // ...
    +}
    +

    then run the command npm publish.

    The Package

    Once you have a Tree-sitter parser that is available on npm, you can use it in your Pulsar package. Packages with grammars are, by convention, always named starting with language. You'll need a folder with a package.json, a grammars subdirectory, and a single json or cson file in the grammars directory, which can be named anything.

    language-mylanguage
    +├── LICENSE
    +├── README.md
    +├── grammars
    +│   └── mylanguage.cson
    +└── package.json
    +

    The Grammar File

    The mylanguage.cson file specifies how Pulsar should use the parser you created.

    Basic Fields

    It starts with some required fields:

    name: 'My Language'
    +scopeName: 'mylanguage'
    +type: 'tree-sitter'
    +parser: 'tree-sitter-mylanguage'
    +
    • scopeName - A unique, stable identifier for the language. Pulsar users will use this in configuration files if they want to specify custom configuration based on the language.
    • name - A human readable name for the language.
    • parser - The name of the parser node module that will be used for parsing. This string will be passed directly to require()open in new window in order to load the parser.
    • type - This should have the value tree-sitter to indicate to Pulsar that this is a Tree-sitter grammar and not a TextMate grammar.

    Language Recognition

    Next, the file should contain some fields that indicate to Pulsar when this language should be used. These fields are all optional.

    • fileTypes - An array of filename suffixes. The grammar will be used for files whose names end with one of these suffixes. Note that the suffix may be an entire filename.
    • firstLineRegex - A regex pattern that will be tested against the first line of the file. The grammar will be used if this regex matches.
    • contentRegex - A regex pattern that will be tested against the contents of the file in order to break ties in cases where multiple grammars matched the file using the above two criteria. If the contentRegex matches, this grammar will be preferred over another grammar with no contentRegex. If the contentRegex does not match, a grammar with no contentRegex will be preferred over this one.

    Syntax Highlighting

    The HTML classes that Pulsar uses for syntax highlighting do not correspond directly to nodes in the syntax tree. Instead, Tree-sitter grammar files specify scope mappings that specify which classes should be applied to which syntax nodes. The scopes object controls these scope mappings. Its keys are CSS selectors that select nodes in the syntax tree. Its values can be of several different types.

    Here is a simple example:

    scopes:
    +  'call_expression > identifier': 'entity.name.function'
    +

    This entry means that, in the syntax tree, any identifier node whose parent is a call_expression should be highlighted using three classes: syntax--entity, syntax--name, and syntax--function.

    Note that in this selector, we're using the immediate child combinatoropen in new window (>). Arbitrary descendant selectors without this combinator (for example 'call_expression identifier', which would match any identifier occurring anywhere within a call_expression) are currently not supported.

    Advanced Selectors

    The keys of the scopes object can also contain multiple CSS selectors, separated by commas, similar to CSS files. The triple-quote syntax in CSON makes it convenient to write keys like this on multiple lines:

    scopes:
    +  '''
    +  function_declaration > identifier,
    +  call_expression > identifier,
    +  call_expression > field_expression > field_identifier
    +  ''': 'entity.name.function'
    +

    You can use the :nth-child pseudo-classopen in new window to select nodes based on their order within their parent. For example, this example selects identifier nodes which are the fourth (zero-indexed) child of a singleton_method node.

    scopes:
    +  'singleton_method > identifier:nth-child(3)': 'entity.name.function'
    +

    Finally, you can use double-quoted strings in the selectors to select anonymous tokens in the syntax tree, like ( and :. See the Tree-sitter documentationopen in new window for more information about named vs anonymous tokens.

    scopes:
    +  '''
    +    "*",
    +    "/",
    +    "+",
    +    "-"
    +  ''': 'keyword.operator'
    +

    Text-based Mappings

    You can also apply different classes to a syntax node based on its text. Here are some examples:

    scopes:
    +
    +  # Apply the classes `syntax--builtin` and `syntax--variable` to all
    +  # `identifier` nodes whose text is `require`.
    +  'identifier': {exact: 'require', scopes: 'builtin.variable'},
    +
    +  # Apply the classes `syntax--type` and `syntax--integer` to all
    +  # `primitive_type` nodes whose text starts with `int` or `uint`.
    +  'primitive_type': {match: /^u?int/, scopes: 'type.integer'},
    +
    +  # Apply the classes `syntax--builtin`, `syntax--class`, and
    +  # `syntax--name` to `constant` nodes with the text `Array`,
    +  # `Hash` and `String`. For all other `constant` nodes, just
    +  # apply the classes `syntax--class` and `syntax--name`.
    +  'constant': [
    +    {match: '^(Array|Hash|String)$', scopes: 'builtin.class.name'},
    +    'class.name'
    +  ]
    +

    In total there are four types of values that can be associated with selectors in scopes:

    • Strings - Each class name in the dot-separated string will be prefixed with syntax-- and applied to the selected node.
    • Objects with the keys exact and scopes - If the node's text equals the exact string, the scopes string will be used as described above.
    • Objects with the keys match and scopes - If the node's text matches the match regex pattern, the scopes string will be used as described above.
    • Arrays - The elements of the array will be processed from beginning to end. The first element that matches the selected node will be used as describe above.

    Specificity

    If multiple selectors in the scopes object match a node, the node's classes will be decided based on the most specificopen in new window selector. Note that the exact and match rules do not affect specificity, so you may need to supply the same exact or match rules for multiple selectors to ensure that they take precedence over other selectors. You can use the same selector multiple times in a scope mapping, within different comma-separated keys:

    scopes:
    +  'call_expression > identifier': 'entity.name.function'
    +
    +  # If we did not include the second selector here, then this rule
    +  # would not apply to identifiers inside of call_expressions,
    +  # because the selector `call_expression > identifier` is more
    +  # specific than the selector `identifier`.
    +  'identifier, call_expression > identifier': [
    +    {exact: 'require', scopes: 'builtin.variable'},
    +    {match: '^[A-Z]', scopes: 'constructor'},
    +  ]
    +

    Language Injection

    Sometimes, a source file can contain code written in several different languages. Tree-sitter grammars support this situation using a two-part process called language injection. First, an 'outer' language must define an injection point - a set of syntax nodes whose text can be parsed using a different language, along with some logic for guessing the name of the other language that should be used. Second, an 'inner' language must define an injectionRegex - a regex pattern that will be tested against the language name provided by the injection point.

    For example, in JavaScript, tagged template literalsopen in new window sometimes contain code written in a different language, and the name of the language is often used in the 'tag' function, as shown in this example:

    // HTML in a template literal
    +const htmlContent = html`<div>Hello ${name}</div>`;
    +

    The tree-sitter-javascript parser parses this tagged template literal as a call_expression with two children: an identifier and a template_literal:

    (call_expression
    +  (identifier)
    +  (template_literal
    +    (interpolation
    +      (identifier))))
    +

    Here is an injection point that would allow syntax highlighting inside of template literals:

    atom.grammars.addInjectionPoint("source.js", {
    +	type: "call_expression",
    +
    +	language(callExpression) {
    +		const { firstChild } = callExpression;
    +		if (firstChild.type === "identifier") {
    +			return firstChild.text;
    +		}
    +	},
    +
    +	content(callExpression) {
    +		const { lastChild } = callExpression;
    +		if (lastChild.type === "template_string") {
    +			return lastChild;
    +		}
    +	},
    +});
    +

    The language callback would then be called with every call_expression node in the syntax tree. In the example above, it would retrieve the first child of the call_expression, which is an identifier with the name "html". The callback would then return the string "html".

    The content callback would then be called with the same call_expression node and return the template_string node within the call_expression node.

    In order to parse the HTML within the template string, the HTML grammar file would need to specify an injectionRegex:

    injectionRegex: 'html|HTML'
    +

    Code Folding

    The next field in the grammar file, folds, controls code folding. Its value is an array of fold pattern objects. Fold patterns are used to decide whether or not a syntax node can be folded, and if so, where the fold should start and end. Here are some example fold patterns:

    folds: [
    +
    +  # All `comment` nodes are foldable. By default, the fold starts at
    +  # the end of the node's first line, and ends at the beginning
    +  # of the node's last line.
    +  {
    +    type: 'comment'
    +  }
    +
    +  # `if_statement` nodes are foldable if they contain an anonymous
    +  # "then" token and either an `elif_clause` or `else_clause` node.
    +  # The fold starts at the end of the "then" token and ends at the
    +  # `elif_clause` or `else_clause`.
    +  {
    +    type: 'if_statement',
    +    start: {type: '"then"'}
    +    end: {type: ['elif_clause', 'else_clause']}
    +  }
    +
    +  # Any node that starts with an anonymous "(" token and ends with
    +  # an anonymous ")" token is foldable. The fold starts after the
    +  # "(" and ends before the ")".
    +  {
    +    start: {type: '"("', index: 0},
    +    end: {type: '")"', index: -1}
    +  }
    +]
    +

    Fold patterns can have one or more of the following fields:

    • type - A string or array of strings. In order to be foldable according to this pattern, a syntax node's type must match one of these strings.
    • start - An object that is used to identify a child node after which the fold should start. The object can have one or both of the following fields:
      • type - A string or array of strings. To start a fold, a child node's type must match one of these strings.
      • index - a number that's used to select a specific child according to its index. Negative values are interpreted as indices relative the last child, so that -1 means the last child.
    • end - An object that is used to identify a child node before which the fold should end. It has the same structure as the start object.

    Comments

    The last field in the grammar file, comments, controls the behavior of Pulsar's Editor: Toggle Line Comments command. Its value is an object with a start field and an optional end field. The start field is a string that should be prepended to or removed from lines in order to comment or uncomment them.

    In JavaScript, it looks like this:

    comments:
    +  start: '// '
    +

    The end field should be used for languages that only support block comments, not line comments. If present, it will be appended to or removed from the end of the last selected line in order to comment or un-comment the selection.

    In CSS, it would look like this:

    comments:
    +  start: '/* '
    +  end: ' */'
    +

    Example Packages

    More examples of all of these features can be found in the Tree-sitter grammars bundled with Pulsar:

    + + + diff --git a/docs/launch-manual/sections/core-hacking/sections/creating-a-legacy-textmate-grammar.html b/docs/launch-manual/sections/core-hacking/sections/creating-a-legacy-textmate-grammar.html new file mode 100644 index 0000000000..64223d392d --- /dev/null +++ b/docs/launch-manual/sections/core-hacking/sections/creating-a-legacy-textmate-grammar.html @@ -0,0 +1,316 @@ + + + + + + + + + + + + + + +

    About 5 min

    Creating a Legacy TextMate Grammar

    Pulsar's syntax highlighting can be powered by two types of grammars. If you're adding support for a new language, the preferred way is to create a Tree-sitter grammar. Tree-sitter grammars have better performance and provide support for more editor features, such as the Select Larger Syntax Node command.

    This section describes the Pulsar's legacy support for TextMate grammars.

    TextMate grammars are supported by several popular text editors. They provide a set of regex (regular expression) patterns which are assigned scopes. These scopes are then turned into the CSS classes that you can target in syntax themes.

    Getting Started

    TextMate Grammars depend heavily on regexes, and you should be comfortable with interpreting and writing regexes before continuing. Note that Pulsar uses the Oniguruma engine, which is very similar to the PCRE or Perl regex engines. Here are some resources to help you out:

    • https://www.regular-expressions.info/tutorial.html provides a comprehensive regex tutorial
    • https://www.rexegg.com/regex-quickstart.html contains a cheat sheet for various regex expressions
    • https://regex101.com/ or https://regexr.com/ allows live prototyping
    • https://github.com/kkos/oniguruma/blob/master/doc/RE the docs for the Oniguruma regex engine

    Grammar files are written in the CSONopen in new window or JSONopen in new window format. Whichever one you decide to use is up to you, but this tutorial will be written in CSON.

    Create the Package

    To get started, press LNX/WIN: Ctrl+Shift+P - MAC: Cmd+Shift+P and start typing "Generate Package" to generate a new grammar package. Select "Package Generator: Generate Package," and you'll be asked for the path where your package will be created. Let's call ours language-flight-manual.

    Tip

    Grammar packages should start with language-.

    The default package template creates a lot of folders that aren't needed for grammar packages. Go ahead and delete the keymaps, lib, menus, and styles folders. Furthermore, in package.json, remove the activationCommands section. Now create a new folder called grammars, and inside that a file called flight-manual.cson. This is the main file that we will be working with - start by populating it with a boilerplate templateopen in new window. Now let's go over what each key means.

    scopeName is the root scope of your package. This should generally describe what language your grammar package is highlighting; for example, language-javascript's scopeName is source.js and language-html's is text.html.basic. Name it source.flight-manual for now.

    name is the user-friendly name that is displayed in places like the status bar or the grammar selector. Again, this name should describe what the grammar package is highlighting. Rename it to Flight Manual.

    fileTypes is an array of filetypes that language-flight-manual should highlight. We're interested in highlighting the Flight Manual's Markdown files, so add the md extension to the list and remove the others.

    patterns contains the array of regex patterns that will determine how the file is tokenized.

    Adding Patterns

    To start, let's add a basic pattern to tokenize the words Flight Manual whenever they show up. Your regex should look like \bFlight Manual\b. Here's what your patterns block should look like:

    'patterns': [
    +  {
    +    'match': '\\bFlight Manual\\b'
    +    'name': 'entity.other.flight-manual'
    +  }
    +]
    +

    match is where your regex is contained, and name is the scope name that is to be applied to the entirety of the match. More information about scope names can be found in Section 12.4 of the TextMate Manualopen in new window.

    Tip

    All scopes should end with the portion of the root scopeName after the leading source or text. In our case, all scopes should end with flight-manual.

    Note

    Astute readers may have noticed that the \b was changed to \\b with two backslashes and not one. This is because CSON processes the regex string before handing it to Oniguruma, so all backslashes need to be escaped twice.

    But what if we wanted to apply different scopes to Flight and Manual? This is possible by adding capture groups to the regex and then referencing those capture groups in a new capture property. For example:

    'match': '\\b(Flight) (Manual)\\b'
    +'name': 'entity.other.flight-manual'
    +'captures':
    +  '1':
    +    'name': 'keyword.other.flight.flight-manual'
    +  '2':
    +    'name': 'keyword.other.manual.flight-manual'
    +

    This will assign the scope keyword.other.flight.flight-manual to Flight, keyword.other.manual.flight-manual to Manual, and entity.other.flight-manual to the overarching Flight Manual.

    Begin/End Patterns

    Now let's say we want to tokenize the {{#note}} blocks that occur in Flight Manual files. Our previous two examples used match, but one limit of match is that it can only match single lines. {{#note}} blocks, on the other hand, can span multiple lines. For these cases, you can use the begin/end keys. Once the regex in the begin key is matched, tokenization will continue until the end pattern is reached.

    'begin': '({{)(#note)(}})'
    +'beginCaptures':
    +  '0': # The 0 capture contains the entire match
    +    'name': 'meta.block.start.flight-manual'
    +  '1':
    +    'name': 'punctuation.definition.block.flight-manual'
    +  '2':
    +    'name': 'keyword.note.flight-manual'
    +  '3':
    +    'name': 'punctuation.definition.block.flight-manual'
    +'end': '({{)(/note)(}})'
    +'endCaptures':
    +  '0':
    +    'name': 'meta.block.end.flight-manual'
    +  '1':
    +    'name': 'punctuation.definition.block.flight-manual'
    +  '2':
    +    'name': 'keyword.note.flight-manual'
    +  '3':
    +    'name': 'punctuation.definition.block.flight-manual'
    +'name': 'meta.block.note.flight-manual'
    +

    Tip

    Get into the habit of providing punctuation scopes early on. It's much less effort than having to go back and rewriting all your patterns to support punctuation scopes when your grammar starts to get a bit longer!

    Awesome, we have our first multiline pattern! However, if you've been following along and playing around in your own .md file, you may have noticed that Flight Manual doesn't receive any scopes inside a note block. A begin/end block is essentially a subgrammar of its own: once it starts matching, it will only match its own subpatterns until the end pattern is reached. Since we haven't defined any subpatterns, then clearly nothing will be matched inside of a note block. Let's fix that!

    'begin': '({{)(#note)(}})'
    +'beginCaptures':
    +  '0': # The 0 capture contains the entire match
    +    'name': 'meta.block.start.flight-manual'
    +  '1':
    +    'name': 'punctuation.definition.block.flight-manual'
    +  '2':
    +    'name': 'keyword.note.flight-manual'
    +  '3':
    +    'name': 'punctuation.definition.block.flight-manual'
    +'end': '({{)(/note)(}})'
    +'endCaptures':
    +  '0':
    +    'name': 'meta.block.end.flight-manual'
    +  '1':
    +    'name': 'punctuation.definition.block.flight-manual'
    +  '2':
    +    'name': 'keyword.note.flight-manual'
    +  '3':
    +    'name': 'punctuation.definition.block.flight-manual'
    +'name': 'meta.block.note.flight-manual'
    +'patterns': [
    +  {
    +    'match': '\\b(Flight) (Manual)\\b'
    +    'name': 'entity.other.flight-manual'
    +    'captures':
    +      '1':
    +        'name': 'keyword.other.flight.flight-manual'
    +      '2':
    +        'name': 'keyword.other.manual.flight-manual'
    +  }
    +]
    +

    There. With the patterns block, Flight Manual should now receive the proper scopes.

    Repositories and the Include keyword, or how to avoid duplication

    At this point, note blocks are looking pretty nice, as is the Flight Manual keyword, but the rest of the file is noticeably lacking any form of Markdown syntax highlighting. Is there a way to include the GitHub-Flavored Markdown grammar without copying and pasting everything over? This is where the include keyword comes in. include allows you to include other patterns, even from other grammars! language-gfm's scopeName is source.gfm, so let's include that. Our patterns block should now look like the following:

    'patterns': [
    +  {
    +    'include': 'source.gfm'
    +  }
    +  {
    +    # Flight Manual pattern
    +  }
    +  {
    +    # Note begin/end pattern
    +  }
    +]
    +

    However, including source.gfm has led to another problem: note blocks still don't have any Markdown highlighting! The quick fix would be to add the include pattern to the note's pattern block as well, but now we're duplicating two patterns. You can imagine that as this grammar grows it'll quickly become inefficient to keep copying each new global pattern over to the note pattern as well. Therefore, include helpfully recognizes the special $self scope. $self automatically includes all the top-level patterns of the current grammar. The note block can then be simplified to the following:

    'begin': '({{)(#note)(}})'
    +# beginCaptures
    +'end': '({{)(/note)(}})'
    +# endCaptures
    +'name': 'meta.block.note.flight-manual'
    +'patterns': [
    +  {
    +    'include': '$self'
    +  }
    +]
    +

    Where to Go from Here

    There are several good resources out there that help when writing a grammar. The following is a list of some particularly useful ones (some have been linked to in the sections above as well).

    + + + diff --git a/docs/launch-manual/sections/core-hacking/sections/creating-a-theme.html b/docs/launch-manual/sections/core-hacking/sections/creating-a-theme.html new file mode 100644 index 0000000000..f3d9b15de1 --- /dev/null +++ b/docs/launch-manual/sections/core-hacking/sections/creating-a-theme.html @@ -0,0 +1,234 @@ + + + + + + + + + + + + + + +

    About 5 min

    Creating a Theme

    Pulsar's interface is rendered using HTML, and it's styled via Lessopen in new window which is a superset of CSS. Don't worry if you haven't heard of Less before; it's just like CSS, but with a few handy extensions.

    Pulsar supports two types of themes: UI and Syntax. UI themes style elements such as the tree view, the tabs, drop-down lists, and the status bar. Syntax themes style the code, gutter and other elements inside the editor view.

    Theme boundary

    Themes can be installed and changed from the Settings View which you can open by selecting the LNX: Edit > Preferences - MAC: Pulsar > Preferences - WIN: File > Preferences menu, and clicking the "Install" or "Themes" tab on the left hand navigation.

    Getting Started

    Themes are pretty straightforward but it's still helpful to be familiar with a few things before starting:

    • Less is a superset of CSS, but it has some really handy features like variables. If you aren't familiar with its syntax, take a few minutes to familiarize yourselfopen in new window.
    • You may also want to review the concept of a package.json (as covered in Pulsar package.json). This file is used to help distribute your theme to Pulsar users.
    • Your theme's package.json must contain a theme key with a value of ui or syntax for Pulsar to recognize and load it as a theme.
    • You can find existing themes to install or fork in Pulsar Package Repositoryopen in new window.

    Creating a Syntax Theme

    Let's create your first theme.

    To get started, press LNX/WIN: Ctrl+Shift+P - MAC: Cmd+Shift+P and start typing Generate Syntax Theme to generate a new theme package. Select Generate Syntax Theme, and you'll be asked for the path where your theme will be created. Let's call ours motif-syntax.

    Tip

    Syntax themes should end with -syntax and UI themes should end with -ui.

    Pulsar will display a new window, showing the motif-syntax theme, with a default set of folders and files created for us. If you open the Settings View with LNX/WIN: Ctrl+, - MAC: Cmd+, and click the "Themes" tab on the left, you'll see the "Motif" theme listed in the "Syntax Theme" drop-down. Select it from the menu to activate it, now when you open an editor you should see your new motif-syntax theme in action.

    Open up styles/colors.less to change the various color variables which have already been defined. For example, turn @red into #f4c2c1.

    Then open styles/base.less and modify the various selectors that have already been defined. These selectors style different parts of code in the editor such as comments, strings and the line numbers in the gutter.

    As an example, let's make the .gutter background-color into @red.

    Reload Pulsar by pressing LNX/WIN: Alt+Ctrl+R - MAC: Alt+Cmd+Ctrl+L to see the changes you made reflected in your Pulsar window. Pretty neat!

    Tip

    You can avoid reloading to see changes you make by opening an Pulsar window in Dev Mode. To open a Dev Mode Pulsar window run pulsar --dev . in the terminal, or use the View > Developer > Open in Dev Mode menu. When you edit your theme, changes will instantly be reflected!

    Note

    It's advised to not specify a font-family in your syntax theme because it will override the Font Family field in Pulsar's settings. If you still like to recommend a font that goes well with your theme, we suggest you do so in your README.

    Creating a UI Theme

    To create a UI theme, do the following:

    1. Fork the ui-theme-templateopen in new window
    2. Clone the forked repository to the local filesystem
    3. Open a terminal in the forked theme's directory
    4. Open your new theme in a Dev Mode Pulsar window run pulsar --dev . in the terminal or use the View > Developer > Open in Dev Mode menu
    5. Change the name of the theme in the theme's package.json file
    6. Name your theme end with a -ui, for example super-white-ui
    7. Run pulsar -p link --dev to symlink your repository to LNX/MAC: ~/.pulsar/dev/packages - WIN: %USERPROFILE%\.pulsar
    8. Reload Pulsar using LNX/WIN: Alt+Ctrl+R - MAC: Alt+Cmd+Ctrl+L
    9. Enable the theme via the "UI Theme" drop-down in the "Themes" tab of the Settings View
    10. Make changes! Since you opened the theme in a Dev Mode window, changes will be instantly reflected in the editor without having to reload.

    Tip

    Because we used pulsar -p link --dev in the above instructions, if you break anything you can always close Pulsar and launch Pulsar normally to force Pulsar to the default theme. This allows you to continue working on your theme even if something goes catastrophically wrong.

    Theme Variables

    UI themes must provide a ui-variables.less and Syntax themes a syntax-variables.less file. It contains predefined variables that packages use to make sure the look and feel matches.

    Here the variables with the default values:

    These default values will be used as a fallback in case a theme doesn't define its own variables.

    Use in Packages

    In any of your package's .less files, you can access the theme variables by importing the ui-variables or syntax-variables file from Pulsar.

    Your package should generally only specify structural styling, and these should come from the style guideopen in new window. Your package shouldn't specify colors, padding sizes, or anything in absolute pixels. You should instead use the theme variables. If you follow this guideline, your package will look good out of the box with any theme!

    Here's an example .less file that a package can define using theme variables:

    @import "ui-variables";
    +
    +.my-selector {
    +	background-color: @base-background-color;
    +	padding: @component-padding;
    +}
    +
    @import "syntax-variables";
    +
    +.my-selector {
    +	background-color: @syntax-background-color;
    +}
    +

    Development workflow

    There are a few tools to help make theme development faster and easier.

    Live Reload

    Reloading by pressing LNX/WIN: Alt+Ctrl+R - MAC: Alt+Cmd+Ctrl+L after you make changes to your theme is less than ideal. Pulsar supports live updatingopen in new window of styles on Pulsar windows in Dev Mode.

    To launch a Dev Mode window:

    • Open your theme directory in a dev window by selecting the View > Developer > Open in Dev Mode menu item
    • Or launch Pulsar from the terminal with pulsar --dev

    If you'd like to reload all the styles at any time, you can use the shortcut LNX/WIN: Alt+Ctrl+R - MAC: Alt+Cmd+Ctrl+L,

    Developer Tools

    Pulsar is based on the Chromium browser and supports its Developer Tools. You can open them by selecting the View > Developer > Toggle Developer Tools menu, or by using the LNX/WIN: Ctrl+Shift+I - MAC: Alt+Cmd+I

    The dev tools allow you to inspect elements and take a look at their CSS properties.

    Developer Tools

    Check out Google's extensive tutorialopen in new window for a short introduction.

    Pulsar Styleguide

    If you are creating an UI theme, you'll want a way to see how your theme changes affect all the components in the system. The Styleguideopen in new window is a page that renders every component Pulsar supports.

    To open the Styleguide, open the command palette with LNX/WIN: Ctrl+Shift+P - MAC: Cmd+Shift+P and search for styleguide, or use the shortcut LNX/WIN: Ctrl+Shift+G - MAC: Cmd+Ctrl+Shift+G.

    Style Guide

    Side by side

    Sometimes when creating a theme (or package) things can go wrong and the editor becomes unusable. E.g. if the text and background have the same color or something gets pushed out of sight. To avoid having to open Pulsar in "normal" mode to fix the issue, it's advised to open two Pulsar windows. One for making changes and one in Dev Mode to see the changes getting applied.

    Side by side screenshot

    Make changes on the left, see the changes getting applied in "Dev Mode" on the right.

    Now if you mess up something, only the window in "Dev Mode" will be affected and you can easily correct the mistake in your "normal" window.

    Publish your theme

    Once you're happy with your theme and would like to share it with other Pulsar users, it's time to publish it. 🎉

    Follow the steps on the Publishing page. The example used is for the Word Count package, but publishing a theme works exactly the same.

    + + + diff --git a/docs/launch-manual/sections/core-hacking/sections/cross-platform-compatibility.html b/docs/launch-manual/sections/core-hacking/sections/cross-platform-compatibility.html new file mode 100644 index 0000000000..ca6ee832c0 --- /dev/null +++ b/docs/launch-manual/sections/core-hacking/sections/cross-platform-compatibility.html @@ -0,0 +1,226 @@ + + + + + + + + + + + + + + +

    About 2 min

    Cross-Platform Compatibility

    Pulsar runs on a number of platforms and while Electron and Node take care of many of the details there are still some considerations to ensure your package works on other operating systems.

    File symlinks can be used on Windows by non-Administrators by specifying 'junction' as the type (this argument is ignored on macOS & Linux).

    Also consider:

    • Symlinks committed to Git will not checkout correctly on Windows - dynamically create what you need with fs.symlink instead
    • Symlinked directories are only available to Administrators on Windows - avoid a dependency on them

    Filenames

    • Reserved filenames on Windows are com1-com9, lpt1-lpt9, con, nul, aux and prn (regardless of extension, e.g. prn.txt is disallowed)
    • Reserved characters on Windows are ? \ / < > ? % | : " so avoid where possible
    • Names with spaces when passed to the command line;
      • Linux and macOS require a backslash before each space e.g. /my\ test
      • Windows requires you surround the path with double quotes e.g. "c:\my test"

    File paths

    • Windows uses \ although some tools and PowerShell allow / too
    • macOS and Linux use /

    You can dynamically find out what your platform uses with path.sep or better yet use the node path library functions such as join and normalize which automatically take care of this.

    Windows supports up to 250 characters for a path - avoid deeply nested directory structures

    Paths are not URLs

    URL parsing routines should not be used on file paths. While they initially look like a relative path it will fail in a number of scenarios on all platforms.

    • Various characters are misinterpreted, e.g. ? as query string, # as a fragment identifier
    • Windows drive specifiers are incorrectly parsed as a protocol

    If you need to use a path for a URL use the file: protocol with an absolute path instead to ensure drive letters and slashes are appropriately addressed, e.g. file:///c|/test/pic.png

    fs.stat on directories

    The fs.stat function does not return the size of the contents of a directory but rather the allocation size of the directory itself. This returns 0 on Windows and 1024 on macOS and so should not be relied upon.

    path.relative can't traverse drives

    • On a Linux or macOS system path.relative can be used to calculate a relative path to traverse between any two given paths.
    • On Windows this is not always possible as it can contain multiple absolute roots, e.g. c:\ and d:\

    Rapid file operations

    Creation and deletion operations may take a few milliseconds to complete. If you need to remove many files and folders consider RimRAFopen in new window which has built-in retry logic for this.

    Line endings

    • Linux and macOS use LF
    • Windows uses CRLF
    • Git on Windows often has autocrlf set which automatically converts between the two

    If you are writing specs that use text file fixtures consider that this will interfere with file lengths, hash codes and direct text comparisons. It will also change the Atom selection length by 1 character per line.

    If you have spec fixtures that are text files you may want to tell Git to force LF, CRLF or not convert them by specifying the paths in .gitattributes e.g.

    spec/fixtures/always-crlf.txt eol=crlf
    +spec/fixtures/always-lf.txt eol=lf
    +spec/fixtures/leave-as-is.txt -text
    +
    + + + diff --git a/docs/launch-manual/sections/core-hacking/sections/debugging.html b/docs/launch-manual/sections/core-hacking/sections/debugging.html new file mode 100644 index 0000000000..5e1f30a1ed --- /dev/null +++ b/docs/launch-manual/sections/core-hacking/sections/debugging.html @@ -0,0 +1,270 @@ + + + + + + + + + + + + + + +

    About 10 min

    Debugging

    Pulsar provides several tools to help you understand unexpected behavior and debug problems. This guide describes some of those tools and a few approaches to help you debug and provide more helpful information when submitting issuesopen in new window:

    Update to the Latest Version

    You might be running into an issue which was already fixed in a more recent version of Pulsar than the one you're using.

    If you're using a released version, check which version of Pulsar you're using:

    $ pulsar --version
    +> Pulsar  : 1.63.0-dev
    +> Electron: 12.2.3
    +> Chrome  : 89.0.4389.128
    +> Node    : 14.16.0
    +

    You can find the latest releases on the Pulsar Websiteopen in new window, follow the links for either the latest release or Cirrus CI version. Make sure to mention which version when logging an issue.

    If you're building Pulsar from source, pull down the latest version of master and re-build. Make sure that if logging an issue you include the latest commit hash you built from.

    Using Safe Mode

    A large part of Pulsar's functionality comes from packages you can install. Pulsar will also execute the code in your init script on startup. In some cases, these packages and the code in the init script might be causing unexpected behavior, problems, or performance issues.

    To determine if that is happening, start Pulsar from the terminal in safe mode:

    $ pulsar --safe
    +

    This starts Pulsar, but does not load packages from LNX/MAC: ~/.pulsar/packages or ~/.pulsar/dev/packages - WIN: %USERPROFILE%\.pulsar\packages or %USERPROFILE%\.pulsar\dev\packages. and disables loading of your init script. If you can no longer reproduce the problem in safe mode, it's likely it was caused by one of the packages or the init script.

    If removing or commenting out all content from the init script and starting Pulsar normally still produces the error, then try figuring out which package is causing trouble. Start Pulsar normally again and open the Settings View with LNX/WIN: Ctrl+, - MAC: Cmd+,. Since the Settings View allows you to disable each installed package, you can disable packages one by one until you can no longer reproduce the issue. Restart Pulsar or reload Pulsar with LNX/WIN: Ctrl+Shift+F5 - MAC: Alt+Cmd+Ctrl+L. after you disable each package to make sure it's completely gone.

    When you find the problematic package, you can disable or uninstall the package. We strongly recommend creating an issue on the package's GitHub repository.

    Clearing Saved State

    Pulsar saves a number of things about your environment when you exit in order to restore Pulsar to the same configuration when you next launch the program. In some cases the state that gets saved can be something undesirable that prevents Pulsar from working properly. In these cases, you may want to clear the state that Pulsar has saved.

    DANGER

    Clearing the saved state permanently destroys any state that Pulsar has saved across all projects. This includes unsaved changes to files you may have been editing in all projects. This is a destructive action.

    Clearing the saved state can be done by opening a terminal and executing:

    $ pulsar --clear-window-state
    +

    Reset to Factory Defaults

    In some cases, you may want to reset Pulsar to "factory defaults", in other words clear all of your configuration and remove all packages. This can easily be done by opening a terminal and executing:

    Once that is complete, you can launch Pulsar as normal. Everything will be just as if you first installed Pulsar.

    Tip

    The command given above doesn't delete the old configuration, just puts it somewhere that Pulsar can't find it. If there are pieces of the old configuration you want to retrieve, you can find them in the LNX/MAC: ~/.pulsar-backup - WIN: %USERPROFILE%\.pulsar-backup directory.

    Check for Linked Packages

    If you develop or contribute to Pulsar packages, there may be left-over packages linked to your LNX/MAC: ~/.pulsar/packages or ~/.pulsar/dev/packages - WIN: %USERPROFILE%\.pulsar\packages or %USERPROFILE%\.pulsar\dev\packages. directories. You can use the pulsar -p links command to list all linked packages:

    $ pulsar -p links
    +> /Users/pulsy/.pulsar/dev/packages (0)
    +> └── (no links)
    +> /Users/pulsy/.pulsar/packages (1)
    +> └── color-picker -> /Users/pulsy/github/color-picker
    +

    You can remove links using the pulsar -p unlink command:

    $ pulsar -p unlink color-picker
    +> Unlinking /Users/pulsy/.pulsar/packages/color-picker ✓
    +

    See pulsar -p links --help and pulsar -p unlink --help for more information on these commands.

    Tip

    You can also use pulsar -p unlink --all to easily unlink all packages and themes.

    Check for Incompatible Packages

    If you have packages installed that use native Node modules, when you upgrade to a new version of Pulsar, they might need to be rebuilt. Pulsar detects this and through the incompatible-packages packageopen in new window displays an indicator in the status bar when this happens.

    Incompatible Packages Status Bar Indicator

    If you see this indicator, click it and follow the instructions.

    Check Pulsar and Package Settings

    In some cases, unexpected behavior might be caused by settings in Pulsar or in one of the packages.

    Open Pulsar's Settings Viewopen in new window with LNX/WIN: Ctrl+, - MAC: Cmd+,, the LNX: Edit > Preferences - MAC: Pulsar > Preferences - WIN: File > Preferences menu option, or the Settings View: Open command from the Command Paletteopen in new window.

    Settings View

    Check Pulsar's settings in the Settings View, there's a description of most configuration options in the Basic Customization section. For example, if you want Pulsar to hide the invisible symbols representing whitespace characters, disable the "Show Invisibles" option.

    Some of these options are also available on a per-language basis which means that they may be different for specific languages, for example JavaScript or Python. To check the per-language settings, open the settings for the language package under the Packages tab in the Settings View, for example the language-javascript or language-python package.

    Since Pulsar ships with a set of packages and you can also install additional packages yourself, check the list of packages and their settings. For instance, if you'd like to get rid of the vertical line in the middle of the editor, disable the Wrap Guide packageopen in new window. And if you don't like it when Pulsar strips trailing whitespace or ensures that there's a single trailing newline in the file, you can configure that in the whitespace package'sopen in new window settings.

    Package Settings

    Check Your Configuration

    You might have defined some custom styles, keymaps or snippets in one of your configuration files. In some situations, these personal hacks might be causing the unexpected behavior you're observing so try clearing those files and restarting Pulsar.

    Check Your Keybindings

    If a command is not executing when you press a key combination or the wrong command is executing, there might be an issue with the keybinding for that combination. Pulsar ships with the Keybinding Resolveropen in new window, a neat package which helps you understand what key Pulsar saw you press and the command that was triggered because of it.

    Show the keybinding resolver with LNX/WIN: Ctrl+. - MAC: Cmd+., or with Keybinding Resolver: Show from the Command palette. With the Keybinding Resolver shown, press a key combination:

    Keybinding Resolver

    The Keybinding Resolver shows you a list of keybindings that exist for the key combination, where each item in the list has the following:

    • the command for the keybinding
    • the CSS selector used to define the context in which the keybinding is valid
    • the file in which the keybinding is defined

    The keybindings are listed in two colors. All the keybindings that are matched but not executed are shown in gray. The one that is executed, if any, is shown in green. If the command you wanted to trigger isn't listed, then a keybinding for that command hasn't been loaded.

    If multiple keybindings are matched, Pulsar determines which keybinding will be executed based on the specificity of the selectors and the order in which they were loaded. If the command you wanted to trigger is listed in the Keybinding Resolver, but wasn't the one that was executed, this is normally explained by one of two causes:

    • The key combination was not used in the context defined by the keybinding's selector

      For example, you can't trigger the keybinding for the tree-view:add-file command if the Tree View is not focused.

    • There is another keybinding that took precedence

      This often happens when you install a package which defines keybindings that conflict with existing keybindings. If the package's keybindings have selectors with higher specificity or were loaded later, they'll have priority over existing ones.

    Pulsar loads core Pulsar keybindings and package keybindings first, and user-defined keybindings last. Since user-defined keybindings are loaded last, you can use your keymap.cson file to tweak the keybindings and sort out problems like these. See the Keymaps in Depth section for more information.

    If you notice that a package's keybindings are taking precedence over core Pulsar keybindings, it might be a good idea to report the issue on that package's GitHub repository. You can contact Pulsar maintainers on Pulsar's github discussionsopen in new window.

    Check Font Rendering Issues

    You can determine which fonts are being used to render a specific piece of text by using the Developer Tools. To open the Developer Tools press LNX/WIN: Ctrl+Shift+I - MAC: Alt+Cmd+I. Once the Developer Tools are open, click the "Elements" tab. Use the standard tools for finding the elementopen in new window containing the text you want to check. Once you have selected the element, you can click the "Computed" tab in the styles pane and scroll to the bottom. The list of fonts being used will be shown there:

    Fonts In Use

    Check for Errors in the Developer Tools

    When an unexpected error occurs in Pulsar, you will normally see a red notification which provides details about the error and allows you to create an issue on the right repository:

    Exception Notification

    Not all errors are logged with a notification so if you suspect you're experiencing an error but there's no notification, you can also look for errors in the developer tools Console tab. To access the Console tab, press LNX/WIN: Ctrl+Shift+I - MAC: Alt+Cmd+I to open developer tools and then click the Console tab:

    DevTools Error

    If there are multiple errors, you can scroll down to the bottom of the panel to see the most recent error. Or while reproducing an error, you can right click in the Console tab panel, select Clear console to remove all Console output, and then reproduce the error to see what errors are logged to the Console tab.

    Note

    When running in Dev Mode, the developer tools are automatically shown with the error logged in the Console tab.

    Find Crash Logs

    Diagnose Startup Performance

    If Pulsar is taking a long time to start, you can use the Timecop packageopen in new window to get insight into where Pulsar spends time while loading.

    Timecop

    Timecop displays the following information:

    • Pulsar startup times
    • File compilation times
    • Package loading and activation times
    • Theme loading and activation times

    If a specific package has high load or activation times, you might consider reporting an issue to the maintainers. You can also disable the package to potentially improve future startup times.

    Diagnose Runtime Performance

    If you're experiencing performance problems in a particular situation, your Issue reportsopen in new window will be more valuable if you include a saved profile from Chrome's CPU profiler that gives some insight into what is slow.

    To run a profile, open the Developer Tools with LNX/WIN: Ctrl+Shift+I - MAC: Alt+Cmd+I. From there:

    1. Click the Profiles tab
    2. Select "Collect JavaScript CPU Profile"
    3. Click "Start"

    DevTools Profiler

    Once that is done, then perform the slow action to capture a recording. When finished, click "Stop". Switch to the "Chart" view, and a graph of the recorded actions will appear. You can save and post the profile data by clicking "Save" next to the profile's name in the left panel.

    DevTools Profiler

    To learn more, check out the Chrome documentation on CPU profilingopen in new window.

    Profiling Startup Performance

    If the time for loading the window looks high, you can create a CPU profile for that period using the --profile-startup command line flag when starting Pulsar:

    $ pulsar --profile-startup .
    +

    This will automatically capture a CPU profile as Pulsar is loading and open the Developer Tools once Pulsar loads. From there:

    1. Click the Profiles tab in the Developer Tools
    2. Select the "startup" profile
    3. Click the "Save" link for the startup profile

    You can then include the startup profile in any issue you report.

    Check Your Build Tools

    If you are having issues installing a package using pulsar -p install, this could be because the package has dependencies on libraries that contain native code. This means you will need to have a C++ compiler and Python installed to be able to install it. You can run pulsar -p install --check to see if the Pulsar package manager can build native code on your machine.

    Check out the pre-requisites in the build instructions for your platform for more details.

    Check if your GPU is causing the problem

    If you encounter flickering or other rendering issues, you can stop Pulsar from using your Graphics Processing Unit (GPU) with the --disable-gpu Chromium flag to see if the fault lies with your GPU:

    $ pulsar --disable-gpu
    +

    Chromium (and thus Pulsar) normally uses the GPU to accelerate drawing parts of the interface. --disable-gpu tells Pulsar to not even attempt to do this, and just use the CPU for rendering everything. This means that the parts of the interface that would normally be accelerated using the GPU will instead take slightly longer and render on the CPU. This likely won't make a noticeable difference, but does slightly increase the battery usage on portable devices as the CPU has to work harder to do the things the GPU is optimized for.

    Two other Chromium flags that are useful for debugging are --enable-gpu-rasterization and --force-gpu-rasterization:

    $ pulsar --enable-gpu-rasterization --force-gpu-rasterization
    +

    --enable-gpu-rasterization allows other commands to determine how a layer tile (graphics) should be drawn and --force-gpu-rasterization determines that the Skia GPU backend should be used for drawing layer tiles (only valid with GPU accelerated compositing).

    Be sure to use Chromium flags at the end of the terminal call if you want to use other Pulsar flags as they will not be executed after the Chromium flags e.g.:

    $ pulsar --safe --enable-gpu-rasterization --force-gpu-rasterization
    +
    + + + diff --git a/docs/launch-manual/sections/core-hacking/sections/hacking-on-the-core.html b/docs/launch-manual/sections/core-hacking/sections/hacking-on-the-core.html new file mode 100644 index 0000000000..173697745d --- /dev/null +++ b/docs/launch-manual/sections/core-hacking/sections/hacking-on-the-core.html @@ -0,0 +1,226 @@ + + + + + + + + + + + + + + +

    Less than 1 minute

    Hacking on the Core

    You will first want to build and run Pulsar from source.

    Running in Development Mode

    Once you have a local copy of Pulsar cloned and built, you can then run Pulsar in Development Mode. But first, if you cloned Pulsar to somewhere other than LNX/MAC: ~/github/pulsar - WIN: %USERPROFILE%\github\pulsar you will need to set the ATOM_DEV_RESOURCE_PATH environment variable to point to the folder in which you cloned Pulsar. To run Pulsar in Dev Mode, use the --dev parameter from the terminal:

    $ pulsar --dev <path-to-open>
    +

    There are a couple benefits of running Pulsar in Dev Mode:

    1. When the ATOM_DEV_RESOURCE_PATH environment variable is set correctly, Pulsar is run using the source code from your local pulsar-edit/pulsar repository. This means you don't have to rebuild after every change, just restart Pulsar 👍
    2. Packages that exist in LNX/MAC: ~/.pulsar/dev/packages - WIN: %USERPROFILE%\.pulsar\dev\packages are loaded instead of packages of the same name normally loaded from other locations. This means that you can have development versions of packages you use loaded but easily go back to the stable versions by launching without Dev Mode.
    3. Packages that contain stylesheets, such as syntax themes, will have those stylesheets automatically reloaded by the dev-live-reloadopen in new window package. This does not live reload JavaScript or CoffeeScript files — you'll need to reload the window (window:reload) to see changes to those.

    Running Pulsar Core Tests Locally

    In order to run Pulsar Core tests from the terminal, first be certain to set the ATOM_DEV_RESOURCE_PATH environment variable as mentioned above and then:

    $ cd <path-to-your-local-pulsar-repo>
    +$ pulsar --test spec
    +
    + + + diff --git a/docs/launch-manual/sections/core-hacking/sections/handling-uris.html b/docs/launch-manual/sections/core-hacking/sections/handling-uris.html new file mode 100644 index 0000000000..ca6b744d32 --- /dev/null +++ b/docs/launch-manual/sections/core-hacking/sections/handling-uris.html @@ -0,0 +1,253 @@ + + + + + + + + + + + + + + +

    About 2 min

    Handling URIs

    Packages have the ability to handle special URIs triggered from the system; for example, a package named my-package can register itself to handle any URI starting with atom://my-package/.

    WARNING

    Handling URIs triggered from other applications, like a web browser, is a powerful tool, but also one that can be jarring. You should shape your package's user experience to handle this well. In general, you should avoid taking direct action on behalf of a user. For example, a URI handler that immediately installs a package is too invasive, but a URI handler that shows the package's pane in the settings view is useful. A URI handler that begins to clone a repo is overly aggressive, but a URI handler that prompts the user to clone a repo is okay.

    Any package with a URI handler that we feel violates this guideline is subject to removal from the Pulsar package registry at our discretion.

    Modifying your package.json

    The first step to handling URIs from your package is to modify its package.json file. You should add a new key called uriHandler, and its value should be an object.

    The uriHandler object must contain a key called method with a string value that tells Pulsar which method in your package to call when a URI needs to be handled. The object can optionally include a key called deferActivation which can be set to the boolean false to prevent Pulsar from deferring activation of your package — see more below.

    For example, if we want our package my-package to handle URIs with a method on our package's main module called handleURI, we could add the following to our package.json:

    "uriHandler": {
    +  "method": "handleURI"
    +}
    +

    Modifying your Main Module

    Now that we've told Pulsar that we want our package to handle URIs beginning with atom://my-package/ via our handleURI method, we need to actually write this method. Pulsar passes two arguments to your URI handler method; the first one is the fully-parsed URI plus query string, parsed with Node's url.parse(uri, true)open in new window. The second argument is the raw, string URI; this is normally not needed since the first argument gives you structured information about the URI.

    Here's a sample package, written in JavaScript, that handles URIs with the package.json configuration we saw above.

    export default {
    +	activate() {
    +		// normal activation code here
    +	},
    +
    +	handleURI(parsedUri) {
    +		console.log(parsedUri);
    +	},
    +};
    +

    When Pulsar handles, for example, the URI atom://my-package/my/test/url?value=42&other=false, the package would log out something like the following:

    {
    +  protocol: 'atom:',
    +  slashes: true,
    +  auth: null,
    +  host: 'my-package',
    +  port: null,
    +  hostname: 'my-package',
    +  hash: null,
    +  search: '?value=true&other=false',
    +  query: { value: '42', other: 'false' },
    +  pathname: '/my/test/url',
    +  path: '/my/test/url?value=true&other=false',
    +  href: 'atom://my-package/my/test/url?value=true&other=false'
    +}
    +

    Notice that the query string arguments are available in the query property, but are strings — you'll have to convert to other native types yourself.

    Controlling Activation Deferral

    For performance reasons, adding a uriHandler entry to your package's package.json will enable deferred activation. This means that Pulsar will not activate your package until it has a URI for it to handle — it will then activate your package and then immediately call the URI handler method. If you want to disable the deferred activation, ensuring your package is activated upon startup, you can add "deferActivation": false to the URI handler config. For example,

    "uriHandler": {
    +  "method": "handleURI",
    +  "deferActivation": false
    +}
    +

    Before doing this, make sure your package actually needs to be activated immediately — disabling deferred activation means Pulsar takes longer to start since it has to activate all packages without deferred activation.

    Linux Support

    Because URI handling is different across operating systems and distributions, there is no built-in URI handler support for Pulsar on Linux. If you want to configure URI handling on your system yourself, then you should configure atom: protocol URI's to trigger Pulsar with the --uri-handler flag; for example, the URI atom://test/uri should launch Atom via atom --uri-handler atom://test/uri.

    Core URIs

    Pulsar provides a core URI to handle opening files with the syntax atom://core/open/file?filename=<filepath>&line=<line>&column=<col>

    + + + diff --git a/docs/launch-manual/sections/core-hacking/sections/iconography.html b/docs/launch-manual/sections/core-hacking/sections/iconography.html new file mode 100644 index 0000000000..add0a917a8 --- /dev/null +++ b/docs/launch-manual/sections/core-hacking/sections/iconography.html @@ -0,0 +1,224 @@ + + + + + + + + + + + + + + +

    Less than 1 minute

    Iconography

    Pulsar comes bundled with the Octicons 4.4.0open in new window icon set. Use them to add icons to your packages.

    NOTE: Some older icons from version 2.1.2 are still kept for backwards compatibility.

    Overview

    In the Styleguide under the "Icons" section you'll find all the Octicons that are available.

    Octicons in the Styleguide

    Usage

    Octicons can be added with simple CSS classes in your markup. Prefix the icon names with icon icon-.

    As an example, to add a monitor icon (device-desktop), use the icon icon-device-desktop classes:

    <span class="icon icon-device-desktop"></span>
    +

    Size

    Octicons look best with a font-size of 16px. It's already used as the default, so you don't need to worry about it. In case you prefer a different icon size, try to use multiples of 16 (32px, 48px etc.) for the sharpest result. Sizes in between are ok too, but might look a bit blurry for icons with straight lines.

    Usability

    Although icons can make your UI visually appealing, when used without a text label, it can be hard to guess its meaning. In cases where space for a text label is insufficient, consider adding a tooltipopen in new window that appears on hover. Or a more subtle title="label" attribute would help as well.

    + + + diff --git a/docs/launch-manual/sections/core-hacking/sections/maintaining-a-fork-of-a-core-package.html b/docs/launch-manual/sections/core-hacking/sections/maintaining-a-fork-of-a-core-package.html new file mode 100644 index 0000000000..394f3ed885 --- /dev/null +++ b/docs/launch-manual/sections/core-hacking/sections/maintaining-a-fork-of-a-core-package.html @@ -0,0 +1,232 @@ + + + + + + + + + + + + + + +

    About 2 min

    Maintaining a Fork of a Core Package

    Originally, each of Atom's core packages resided in a separate repository. In 2018, in an effort to streamline the development of Atom by reducing overhead, the Atom team consolidated many core Atom packagesopen in new window into the atom/atom repositoryopen in new window. For example, the one-light-ui package was originally maintained in the atom/one-light-uiopen in new window repository, but was moved to the packages/one-light-ui directoryopen in new window in the main repository.

    The Pulsar team has continued this trend and has move even more packages into the core, particularly default language packages. A list of these packages moved can be found in this documentopen in new window.

    If you forked one of the core packages before it was moved into the atom/atom or pulsar-edit/pulsar repository, and you want to continue merging upstream changes into your fork, please follow the steps below.

    Step-by-step guide

    For the sake of this guide, let's assume that you forked the pulsar-edit/one-light-uiopen in new window repository, renamed your fork to one-light-ui-plus, and made some customizations.

    Add pulsar-edit/pulsar as a Remote

    Navigate to your local clone of your fork:

    $ cd path/to/your/fork
    +

    Add the pulsar-edit/pulsar repositoryopen in new window as a git remote:

    $ git remote add upstream https://github.com/pulsar-edit/pulsar.git
    +

    Get the Latest Changes for the Core Package

    Tip

    Follow these steps each time you want to merge upstream changes into your fork.

    Fetch the latest changes from the pulsar-edit/pulsar repository:

    $ git fetch upstream
    +

    Identify recent changes to the core package. For example, if you're maintaining a fork of the one-light-ui package, then you'll want to identify recent changes in the packages/one-light-ui directory:

    $ git log --oneline upstream/master -- packages/one-light-ui
    +f884f6de8 [themes] Rename A[a]tom -> P[p]ulsar
    +0db3190f4 Additional rebranding where needed
    +234adb874 Remove deprecated code strings
    +...
    +

    Look through the log and identify the commits that you want to merge into your fork.

    Merge Upstream Changes into Your Fork

    For each commit that you want to bring into your fork, use [git format-patch][https://git-scm.com/docs/git-format-patch] in conjunction with [git am][https://git-scm.com/docs/git-am]. For example, to merge commit f884f6de8 into your fork:

    $ git format-patch -1 --stdout f884f6de8 | git am -p3
    +

    Repeat this step for each commit that you want to merge into your fork.

    + + + diff --git a/docs/launch-manual/sections/core-hacking/sections/package-active-editor-info.html b/docs/launch-manual/sections/core-hacking/sections/package-active-editor-info.html new file mode 100644 index 0000000000..b3f0c1f206 --- /dev/null +++ b/docs/launch-manual/sections/core-hacking/sections/package-active-editor-info.html @@ -0,0 +1,326 @@ + + + + + + + + + + + + + + +

    About 4 min

    Package: Active Editor Info

    We saw in our Word Count package how we could show information in a modal panel. However, panels aren't the only way to extend Pulsar's UI—you can also add items to the workspace. These items can be dragged to new locations (for example, one of the docks on the edges of the window), and Pulsar will restore them the next time you open the project.

    For this package, we'll define a workspace item that tells us some information about our active text editor. The final package can be viewed at https://github.com/pulsar-edit/active-editor-infoopen in new window.

    Create the Package

    To begin, press LNX/WIN: Ctrl+Shift+P - MAC: Cmd+Shift+P to bring up the Command Paletteopen in new window. Type "generate package" and select the Package Generator: Generate Package command, just as we did in the section on package generation. Enter active-editor-info as the name of the package.

    Add an Opener

    Now let's edit the package files to show our view in a workspace item instead of a modal panel. The way we do this is by registering an opener with Pulsar. Openers are just functions that accept a URI and return a view (if it's a URI that the opener knows about). When you call atom.workspace.open(), Pulsar will go through all of its openers until it finds one that can handle the URI you passed.

    Let's open lib/active-editor-info.js and edit our activate() method to register an opener:

    "use babel";
    +
    +import ActiveEditorInfoView from "./active-editor-info-view";
    +import { CompositeDisposable, Disposable } from "atom";
    +
    +export default {
    +	subscriptions: null,
    +
    +	activate(state) {
    +		this.subscriptions = new CompositeDisposable(
    +			// Add an opener for our view.
    +			atom.workspace.addOpener((uri) => {
    +				if (uri === "atom://active-editor-info") {
    +					return new ActiveEditorInfoView();
    +				}
    +			}),
    +
    +			// Register command that toggles this view
    +			atom.commands.add("atom-workspace", {
    +				"active-editor-info:toggle": () => this.toggle(),
    +			}),
    +
    +			// Destroy any ActiveEditorInfoViews when the package is deactivated.
    +			new Disposable(() => {
    +				atom.workspace.getPaneItems().forEach((item) => {
    +					if (item instanceof ActiveEditorInfoView) {
    +						item.destroy();
    +					}
    +				});
    +			})
    +		);
    +	},
    +
    +	deactivate() {
    +		this.subscriptions.dispose();
    +	},
    +
    +	toggle() {
    +		console.log("Toggle it!");
    +	},
    +};
    +

    You'll notice we also removed the activeEditorInfoView property and the serialize() method. That's because, with workspace items, it's possible to have more than one instance of a given view. Since each instance can have its own state, each should do its own serialization instead of relying on a package-level serialize() method. We'll come back to that later.

    You probably also noticed that our toggle() implementation just logs the text "Toggle it!" to the console. Let's make it actually toggle our view:

      toggle() {
    +    atom.workspace.toggle('atom://active-editor-info');
    +  }
    +

    Updating the View

    Pulsar uses the same view abstractions everywhere, so we can almost use the generated ActiveEditorInfoView class as-is. We just need to add two small methods:

      getTitle() {
    +    // Used by Pulsar for tab text
    +    return 'Active Editor Info';
    +  }
    +
    +  getURI() {
    +    // Used by Pulsar to identify the view when toggling.
    +    return 'atom://active-editor-info';
    +  }
    +

    Now reload the window and run the Active Editor Info: Toggle command from the command palette! Our view will appear in a new tab in the center of the workspace. If you want, you can drag it into one of the docks. Toggling it again will then hide that dock. If you close the tab and run the toggle command again, it will appear in the last place you had it.

    Note

    We've repeated the same URI three times now. That's okay, but it's probably a good idea to define the URL in one place and then import it from that module wherever you need it.

    Constraining Our Item's Locations

    The purpose of our view is to show information about the active text editor, so it doesn't really make sense to show our item in the center of the workspace (where the text editor will be). Let's add some methods to our view class to influence where its opened:

      getDefaultLocation() {
    +    // This location will be used if the user hasn't overridden it by dragging the item elsewhere.
    +    // Valid values are "left", "right", "bottom", and "center" (the default).
    +    return 'right';
    +  }
    +
    +  getAllowedLocations() {
    +    // The locations into which the item can be moved.
    +    return ['left', 'right', 'bottom'];
    +  }
    +

    Now our item will appear in the right dock initially and users will only be able to drag it to one of the other docks.

    Show Active Editor Info

    Now that we have our view all wired up, let's update it to show some information about the active text editor. Add this to the constructor:

    this.subscriptions = atom.workspace
    +	.getCenter()
    +	.observeActivePaneItem((item) => {
    +		if (!atom.workspace.isTextEditor(item)) {
    +			message.innerText = "Open a file to see important information about it.";
    +			return;
    +		}
    +		message.innerHTML = `
    +    <h2>${item.getFileName() || "untitled"}</h2>
    +    <ul>
    +      <li><b>Soft Wrap:</b> ${item.softWrapped}</li>
    +      <li><b>Tab Length:</b> ${item.getTabLength()}</li>
    +      <li><b>Encoding:</b> ${item.getEncoding()}</li>
    +      <li><b>Line Count:</b> ${item.getLineCount()}</li>
    +    </ul>
    +  `;
    +	});
    +

    Now whenever you open a text editor in the center, the view will update with some information about it.

    WARNING

    We use a template string here because it's simple and we have a lot of control over what's going into it, but this could easily result in the insertion of unwanted HTML if you're not careful. Sanitize your input and use the DOM API or a templating system when doing this for real.

    Also, don't forget to clean up the subscription in the destroy() method:

    destroy() {
    +  this.element.remove();
    +  this.subscriptions.dispose();
    +}
    +

    Serialization

    If you were to reload Atom now, you'd see that our item had disappeared. That's because we haven't told Pulsar how to serialize it yet. Let's do that now.

    The first step is to implement a serialize() method on our ActiveEditorInfoView class. Atom will call the serialize() method on every item in the workspace periodically to save its state.

      serialize() {
    +    return {
    +      // This is used to look up the deserializer function. It can be any string, but it needs to be
    +      // unique across all packages!
    +      deserializer: 'active-editor-info/ActiveEditorInfoView'
    +    };
    +  }
    +

    Note

    All of our view's state is derived from the active text editor so we only need the deserializer field. If we had other state that we wanted to preserve across reloads, we would just add things to the object we're returning. Just make sure that they're JSON serializable!

    Next we need to register a deserializer function that Atom can use to recreate the real object when it starts up. The best way to do that is to add a "deserializers" object to our package.json file:

    {
    +  "name": "active-editor-info",
    +  ...
    +  "deserializers": {
    +    "active-editor-info/ActiveEditorInfoView": "deserializeActiveEditorInfoView"
    +  }
    +}
    +

    Notice that the key ("active-editor-info/ActiveEditorInfoView") matches the string we used in our serialize() method above. The value ("deserializeActiveEditorInfoView") refers to a function in our main module, which we still need to add. Go back to active-editor-info.js and do that now:

      deserializeActiveEditorInfoView(serialized) {
    +    return new ActiveEditorInfoView();
    +  }
    +

    The value returned from our serialize() method will be passed to this function. Since our serialized object didn't include any state, we can just return a new ActiveEditorInfoView instance.

    Reload Pulsar and toggle the view with the Active Editor Info: Toggle command. Then reload Pulsar again. Your view should be just where you left it!

    Summary

    In this section, we've made a toggleable workspace item whose placement can be controlled by the user. This could be helpful when creating all sorts of visual tools for working with code!

    + + + diff --git a/docs/launch-manual/sections/core-hacking/sections/package-modifying-text.html b/docs/launch-manual/sections/core-hacking/sections/package-modifying-text.html new file mode 100644 index 0000000000..a7b3e1b1e8 --- /dev/null +++ b/docs/launch-manual/sections/core-hacking/sections/package-modifying-text.html @@ -0,0 +1,285 @@ + + + + + + + + + + + + + + +

    About 4 min

    Package: Modifying Text

    Now that we have our first package written, let's go through examples of other types of packages we can make. This section will guide you though creating a simple command that replaces the selected text with ascii artopen in new window. When you run our new command with the word "cool" selected, it will be replaced with:

                                         o888
    +    ooooooo     ooooooo     ooooooo   888
    +  888     888 888     888 888     888 888
    +  888         888     888 888     888 888
    +    88ooo888    88ooo88     88ooo88  o888o
    +
    +

    This should demonstrate how to do basic text manipulation in the current text buffer and how to deal with selections.

    The final package can be viewed at https://github.com/pulsar-edit/ascii-art.

    Basic Text Insertion

    To begin, press LNX/WIN: Ctrl+Shift+P - MAC: Cmd+Shift+P to bring up the Command Paletteopen in new window. Type "generate package" and select the "Package Generator: Generate Package" command, just as we did in the section on package generation. Enter ascii-art as the name of the package.

    Now let's edit the package files to make our ASCII Art package do something interesting. Since this package doesn't need any UI, we can remove all view-related code so go ahead and delete lib/ascii-art-view.js, spec/ascii-art-view-spec.js, and styles/.

    Next, open up lib/ascii-art.js and remove all view code, so it looks like this:

    const { CompositeDisposable } = require("atom");
    +
    +module.exports = {
    +	subscriptions: null,
    +
    +	activate() {
    +		this.subscriptions = new CompositeDisposable();
    +		this.subscriptions.add(
    +			atom.commands.add("atom-workspace", {
    +				"ascii-art:convert": () => this.convert(),
    +			})
    +		);
    +	},
    +
    +	deactivate() {
    +		this.subscriptions.dispose();
    +	},
    +
    +	convert() {
    +		console.log("Convert text!");
    +	},
    +};
    +

    Create a Command

    Now let's add a command. You should namespace your commands with the package name followed by a : and then the name of the command. As you can see in the code, we called our command ascii-art:convert and we will define it to call the convert() method when it's executed.

    So far, that will simply log to the console. Let's start by making it insert something into the text buffer.

    convert() {
    +  const editor = atom.workspace.getActiveTextEditor()
    +  if (editor) {
    +    editor.insertText('Hello, World!')
    +  }
    +}
    +

    As in Counting Words, we're using atom.workspace.getActiveTextEditor() to get the object that represents the active text editor. If this convert() method is called when not focused on a text editor, nothing will happen.

    Next we insert a string into the current text editor with the insertText()open in new window method. This will insert the text wherever the cursor currently is in the current editor. If there are selections, it will replace all selections with the "Hello, World!" text.

    Reload the Package

    Before we can trigger ascii-art:convert, we need to load the latest code for our package by reloading the window. Run the command "Window: Reload" from the Command Palette or by pressing LNX/WIN: Ctrl+Shift+F5 - MAC: Alt+Cmd+Ctrl+L

    Trigger the Command

    Now open the Command Palette and search for the Ascii Art: Convert command. But it's not there! To fix this, open package.json and find the property called activationCommands. Activation commands make Pulsar launch faster by allowing Pulsar to delay a package's activation until it's needed. So remove the existing command and use ascii-art:convert in activationCommands:

    "activationCommands": {
    +  "atom-workspace": "ascii-art:convert"
    +}
    +

    First, reload the window by running the command "Window: Reload" from the command palette. Now when you run the Ascii Art: Convert command it will insert "Hello, World!" into the active editor, if any.

    Add a Key Binding

    Now let's add a key binding to trigger the ascii-art:convert command. Open keymaps/ascii-art.json and add a key binding linking Alt+Ctrl+A to the ascii-art:convert command. You can delete the pre-existing key binding since you won't need it anymore.

    When finished, the file should look like this:

    {
    +  "atom-text-editor": {
    +    "ctrl-alt-a": "ascii-art:convert"
    +  }
    +}
    +
    +

    Now reload the window and verify that the key binding works.

    WARNING

    The Pulsar keymap system is case-sensitive. This means that there is a distinction between a and A when creating keybindings. a means that you want to trigger the keybinding when you press A. But A means that you want to trigger the keybinding when you press Shift+A. You can also write shift-a when you want to trigger the keybinding when you press Shift+A.

    We strongly recommend always using lowercase and explicitly spelling out when you want to include Shift in your keybindings.

    Add the ASCII Art

    Now we need to convert the selected text to ASCII art. To do this we will use the figletopen in new window Node module from npmopen in new window. Open package.json and add the latest version of figlet to the dependencies:

    "dependencies": {
    +  "figlet": "1.0.8"
    +}
    +

    After saving the file, run the command Update Package Dependencies: Update from the Command Palette. This will install the package's node module dependencies, only figlet in this case. You will need to run Update Package Dependencies: Update whenever you update the dependencies field in your package.json file.

    If for some reason this doesn't work, you'll see a message saying "Failed to update package dependencies" and you will find a new npm-debug.log file in your directory. That file should give you some idea as to what went wrong.

    Now require the figlet node module in lib/ascii-art.js and instead of inserting "Hello, World!", convert the selected text to ASCII art.

    convert () {
    +  const editor = atom.workspace.getActiveTextEditor()
    +  if (editor) {
    +    const selection = editor.getSelectedText()
    +
    +    const figlet = require('figlet')
    +    const font = 'o8'
    +    figlet(selection, {font}, function (error, art) {
    +      if (error) {
    +        console.error(error)
    +      } else {
    +        editor.insertText(`\n${art}\n`)
    +      }
    +    })
    +  }
    +}
    +

    Now reload the editor, select some text in an editor window and press Alt+Ctrl+A. It should be replaced with a ridiculous ASCII art version instead.

    There are a couple of new things in this example we should look at quickly. The first is the editor.getSelectedText()open in new window which, as you might guess, returns the text that is currently selected.

    We then call the Figlet code to convert that into something else and replace the current selection with it with the editor.insertText()open in new window call.

    Summary

    In this section, we've made a UI-less package that takes selected text and replaces it with a processed version. This could be helpful in creating linters or checkers for your code.

    + + + diff --git a/docs/launch-manual/sections/core-hacking/sections/package-word-count.html b/docs/launch-manual/sections/core-hacking/sections/package-word-count.html new file mode 100644 index 0000000000..b9160969ce --- /dev/null +++ b/docs/launch-manual/sections/core-hacking/sections/package-word-count.html @@ -0,0 +1,434 @@ + + + + + + + + + + + + + + +

    About 13 min

    Package: Word Count

    Let's get started by writing a very simple package and looking at some of the tools needed to develop one effectively. We'll start by writing a package that tells you how many words are in the current buffer and display it in a small modal window.

    Package Generator

    The simplest way to start a package is to use the built-in package generator that ships with Pulsar. As you might expect by now, this generator is itself a separate package implemented in package-generatoropen in new window.

    You can run the generator by invoking the command palette and searching for "Generate Package". A dialog will appear asking you to name your new project. Name it your-name-word-count. Pulsar will then create that directory and fill it out with a skeleton project and link it into your LNX/MAC: ~/.pulsar/packages - WIN: %USERPROFILE%\.pulsar\packages directory so it's loaded when you launch your editor next time.

    Note

    You may encounter a situation where your package is not loaded. That is because a new package using the same name as an actual package hosted on pulsar-edit.devopen in new window (e.g. "wordcount" and "word-count") is not being loaded as you expected. If you follow our suggestion above of using the your-name-word-count package name, you should be safe 😀

    Basic generated Pulsar package

    You can see that Pulsar has created about a dozen files that make up the package. Let's take a look at each of them to get an idea of how a package is structured, then we can modify them to get our word count functionality.

    The basic package layout is as follows:

    my-package/
    +├─ grammars/
    +├─ keymaps/
    +├─ lib/
    +├─ menus/
    +├─ spec/
    +├─ snippets/
    +├─ styles/
    +├─ index.js
    +└─ package.json
    +

    Not every package will have (or need) all of these directories and the package generator doesn't create snippets or grammars. Let's see what some of these are so we can start messing with them.

    package.json

    Similar to Node modulesopen in new window, Pulsar packages contain a package.json file in their top-level directory. This file contains metadata about the package, such as the path to its "main" module, library dependencies, and manifests specifying the order in which its resources should be loaded.

    In addition to some of the regular Node package.json keysopen in new window available, Pulsar package.json files have their own additions.

    • main: the path to the JavaScript file that's the entry point to your package. If this is missing, Pulsar will default to looking for an index.js or index.coffee.
    • styles: an Array of Strings identifying the order of the style sheets your package needs to load. If not specified, style sheets in the styles directory are added alphabetically.
    • keymaps: an Array of Strings identifying the order of the key mappings your package needs to load. If not specified, mappings in the keymaps directory are added alphabetically.
    • menus: an Array of Strings identifying the order of the menu mappings your package needs to load. If not specified, mappings in the menus directory are added alphabetically.
    • snippets: an Array of Strings identifying the order of the snippets your package needs to load. If not specified, snippets in the snippets directory are added alphabetically.
    • activationCommands: an Object identifying commands that trigger your package's activation. The keys are CSS selectors, the values are Arrays of Strings identifying the command. The loading of your package is delayed until one of these events is triggered within the associated scope defined by the CSS selector. If not specified, the activate() method of your main export will be called when your package is loaded.
    • activationHooks: an Array of Strings identifying hooks that trigger your package's activation. The loading of your package is delayed until one of these hooks are triggered. Currently, there are three activation hooks:
      • core:loaded-shell-environment for when Pulsar has finished loading the shell environment variables
      • scope.name:root-scope-used for when a file is opened from the specified language (e.g. source.ruby:root-scope-used)
      • language-package-name:grammar-used for when a specific language package is used (e.g., my-special-language-javascript:grammar-used)
    • workspaceOpeners: An Array of Strings identifying URIs that trigger your package's activation. For example, say your package registers a custom opener for atom://my-custom-panel. By including that string in workspaceOpeners, your package will defer its activation until that URI is opened.

    The package.json in the package we've just generated looks like this currently:

    {
    +	"name": "your-name-word-count",
    +	"main": "./lib/your-name-word-count",
    +	"version": "0.0.0",
    +	"description": "A short description of your package",
    +	"activationCommands": {
    +		"atom-workspace": "your-name-word-count:toggle"
    +	},
    +	"repository": "https://github.com/pulsar-edit/your-name-word-count",
    +	"license": "MIT",
    +	"engines": {
    +		"atom": ">=1.0.0 <2.0.0"
    +	},
    +	"dependencies": {}
    +}
    +

    If you wanted to use activationHooks, you might have:

    {
    +	"name": "your-name-word-count",
    +	"main": "./lib/your-name-word-count",
    +	"version": "0.0.0",
    +	"description": "A short description of your package",
    +	"activationHooks": [
    +		"language-javascript:grammar-used",
    +		"language-coffee-script:grammar-used"
    +	],
    +	"repository": "https://github.com/pulsar-edit/your-name-word-count",
    +	"license": "MIT",
    +	"engines": {
    +		"atom": ">=1.0.0 <2.0.0"
    +	},
    +	"dependencies": {}
    +}
    +

    One of the first things you should do is ensure that this information is filled out. The name, description, repository URL the project will be at, and the license can all be filled out immediately. The other information we'll get into more detail on as we go.

    WARNING

    Do not forget to update the repository URL. The one generated for you is invalid by design and will prevent you from publishing your package until updated.

    Source Code

    If you want to extend Pulsar's behavior, your package should contain a single top-level module, which you export from whichever file is indicated by the main key in your package.json file. In the package we just generated, the main package file is lib/your-name-word-count.js. The remainder of your code should be placed in the lib directory, and required from your top-level file. If the main key is not in your package.json file, it will look for index.js or index.coffee as the main entry point.

    Your package's top-level module is a singleton object that manages the lifecycle of your extensions to Pulsar. Even if your package creates ten different views and appends them to different parts of the DOM, it's all managed from your top-level object.

    Your package's top-level module can implement the following basic methods:

    • activate(state): This optional method is called when your package is activated. It is passed the state data from the last time the window was serialized if your module implements the serialize() method. Use this to do initialization work when your package is started (like setting up DOM elements or binding events). If this method returns a promise the package will be considered loading until the promise resolves (or rejects).
    • initialize(state): This optional method is similar to activate() but is called earlier. Whereas activation occurs after the workspace has been deserialized (and can therefore happen after your package's deserializers have been called), initialize() is guaranteed to be called before everything. Use activate() if you want to be sure that the workspace is ready; use initialize() if you need to do some setup prior to your deserializers or view providers being invoked.
    • serialize(): This optional method is called when the window is shutting down, allowing you to return JSON to represent the state of your component. When the window is later restored, the data you returned is passed to your module's activate method so you can restore your view to where the user left off.
    • deactivate(): This optional method is called when the window is shutting down and when the package is disabled. If your package is watching any files or holding external resources in any other way, release them here. You should also dispose of all subscriptions you're holding on to.

    Style Sheets

    Style sheets for your package should be placed in the styles directory. Any style sheets in this directory will be loaded and attached to the DOM when your package is activated. Style sheets can be written as CSS or Lessopen in new window, but Less is recommended.

    Ideally, you won't need much in the way of styling. Pulsar provides a standard set of components which define both the colors and UI elements for any package that fits into Pulsar seamlessly. You can view all of Pulsar's UI components by opening the styleguide: open the command palette LNX/WIN: Ctrl+Shift+P - MAC: Cmd+Shift+P and search for styleguide, or type LNX/WIN: Ctrl+Shift+G - MAC: Cmd+Ctrl+Shift+G

    If you do need special styling, try to keep only structural styles in the package style sheets. If you must specify colors and sizing, these should be taken from the active theme's ui-variables.lessopen in new window.

    An optional styleSheets array in your package.json can list the style sheets by name to specify a loading order; otherwise, style sheets are loaded alphabetically.

    Keymaps

    You can provide key bindings for commonly used actions for your extension, especially if you're also adding a new command. In our new package, we have a keymap filled in for us already in the keymaps/your-name-word-count.json file:

    {
    +  "atom-workspace": {
    +    "ctrl-alt-o": "your-name-word-count:toggle"
    +  }
    +}
    +

    This means that if you press Alt+Ctrl+O, our package will run the your-name-word-count:toggle command. We'll look at that code next, but if you want to change the default key mapping, you can do that in this file.

    Keymaps are placed in the keymaps subdirectory. By default, all keymaps are loaded in alphabetical order. An optional keymaps array in your package.json can specify which keymaps to load and in what order.

    Keybindings are executed by determining which element the keypress occurred on. In the example above, the your-name-word-count:toggle command is executed when pressing Alt+Ctrl+O on the atom-workspace element. Because the atom-workspace element is the parent of the entire Pulsar UI, this means the key combination will work anywhere in the application.

    We'll cover more advanced keybinding stuff a bit later in Keymaps in Depth.

    Menus are placed in the menus subdirectory. This defines menu elements like what pops up when you right click a context-menu or would go in the application menu to trigger functionality in your package.

    By default, all menus are loaded in alphabetical order. An optional menus array in your package.json can specify which menus to load and in what order.

    Application Menu

    It's recommended that you create an application menu item under the Packages menu for common actions with your package that aren't tied to a specific element. If we look in the menus/your-name-word-count.json file that was generated for us, we'll see a section that looks like this:

    
    +"menu": [
    +  {
    +    "label": "Packages",
    +    "submenu": [
    +      {
    +        "label": "Word Count",
    +        "submenu": [
    +          {
    +            "label": "Toggle",
    +            "command": "your-name-word-count:toggle"
    +          }
    +        ]
    +      }
    +    ]
    +  }
    +]
    +
    +

    This section puts a "Toggle" menu item under a menu group named "Your Name Word Count" in the "Packages" menu.

    Application Menu Item

    When you select that menu item, it will run the your-name-word-count:toggle command, which we'll look at in a bit.

    The menu templates you specify are merged with all other templates provided by other packages in the order which they were loaded.

    Context Menu

    It's recommended to specify a context menu item for commands that are linked to specific parts of the interface. In our menus/your-name-word-count.json file, we can see an auto-generated section that looks like this:

    "context-menu": {
    +    "atom-text-editor": [
    +      {
    +        "label": "Toggle your-name-word-count",
    +        "command": "your-name-word-count:toggle"
    +      }
    +    ]
    +  }
    +

    This adds a "Toggle Word Count" menu option to the menu that pops up when you right-click in an Pulsar text editor pane.

    Context Menu Entry

    When you click that it will again run the your-name-word-count:toggle method in your code.

    Context menus are created by determining which element was selected and then adding all of the menu items whose selectors match that element (in the order which they were loaded). The process is then repeated for the elements until reaching the top of the DOM tree.

    You can also add separators and submenus to your context menus. To add a submenu, provide a submenu key instead of a command. To add a separator, add an item with a single type: 'separator' key/value pair. For instance, you could do something like this:

    {
    +  "context-menu": {
    +    "atom-workspace": [
    +      {
    +        "label": "Text",
    +        "submenu": [
    +          {
    +            "label": "Inspect Element",
    +            "command": "core:inspect"
    +          },
    +          {
    +            "type": "separator"
    +          },
    +          {
    +            "label": "Selector All",
    +            "command": "core:select-all"
    +          },
    +          {
    +            "type": "separator"
    +          },
    +          {
    +            "label": "Deleted Selected Text",
    +            "command": "core:delete"
    +          }
    +        ]
    +      }
    +    ]
    +  }
    +}
    +

    Developing Our Package

    Currently with the generated package we have, if we run that your-name-word-count:toggle command through the menu or the command palette, we'll get a dialog that says "The YourNameWordCount package is Alive! It's ALIVE!".

    Wordcount Package is Alive Dialog

    Understanding the Generated Code

    Let's take a look at the code in our lib directory and see what is happening.

    There are two files in our lib directory. One is the main file (lib/your-name-word-count.js), which is pointed to in the package.json file as the main file to execute for this package. This file handles the logic of the whole package.

    The second file is a View class, lib/your-name-word-count-view.js, which handles the UI elements of the package. Let's look at this file first, since it's pretty simple.

    export default class YourNameWordCountView {
    +	constructor(serializedState) {
    +		// Create root element
    +		this.element = document.createElement("div");
    +		this.element.classList.add("your-name-word-count");
    +
    +		// Create message element
    +		const message = document.createElement("div");
    +		message.textContent = "The YourNameWordCount package is Alive! It's ALIVE!";
    +		message.classList.add("message");
    +		this.element.appendChild(message);
    +	}
    +
    +	// Returns an object that can be retrieved when package is activated
    +	serialize() {}
    +
    +	// Tear down any state and detach
    +	destroy() {
    +		this.element.remove();
    +	}
    +
    +	getElement() {
    +		return this.element;
    +	}
    +}
    +

    Basically the only thing happening here is that when the View class is created, it creates a simple div element and adds the your-name-word-count class to it (so we can find or style it later) and then adds the "Your Name Word Count package is Alive!" text to it. There is also a getElement method which returns that div. The serialize and destroy methods don't do anything and we won't have to worry about that until another example.

    Notice that we're simply using the basic browser DOM methods: createElement() and appendChild().

    The second file we have is the main entry point to the package. Again, because it's referenced in the package.json file. Let's take a look at that file.

    import YourNameWordCountView from "./your-name-word-count-view";
    +import { CompositeDisposable } from "atom";
    +
    +export default {
    +	yourNameWordCountView: null,
    +	modalPanel: null,
    +	subscriptions: null,
    +
    +	activate(state) {
    +		this.yourNameWordCountView = new YourNameWordCountView(
    +			state.yourNameWordCountViewState
    +		);
    +		this.modalPanel = atom.workspace.addModalPanel({
    +			item: this.yourNameWordCountView.getElement(),
    +			visible: false,
    +		});
    +
    +		// Events subscribed to in Pulsar's system can be easily cleaned up with a CompositeDisposable
    +		this.subscriptions = new CompositeDisposable();
    +
    +		// Register command that toggles this view
    +		this.subscriptions.add(
    +			atom.commands.add("atom-workspace", {
    +				"your-name-word-count:toggle": () => this.toggle(),
    +			})
    +		);
    +	},
    +
    +	deactivate() {
    +		this.modalPanel.destroy();
    +		this.subscriptions.dispose();
    +		this.yourNameWordCountView.destroy();
    +	},
    +
    +	serialize() {
    +		return {
    +			yourNameWordCountViewState: this.yourNameWordCountView.serialize(),
    +		};
    +	},
    +
    +	toggle() {
    +		console.log("YourNameWordCount was toggled!");
    +		return this.modalPanel.isVisible()
    +			? this.modalPanel.hide()
    +			: this.modalPanel.show();
    +	},
    +};
    +

    There is a bit more going on here. First of all we can see that we are defining four methods. The only required one is activate. The deactivate and serialize methods are expected by Pulsar but optional. The toggle method is one Pulsar is not looking for, so we'll have to invoke it somewhere for it to be called, which you may recall we do both in the activationCommands section of the package.json file and in the action we have in the menu file.

    The deactivate method simply destroys the various class instances we've created and the serialize method simply passes on the serialization to the View class. Nothing too exciting here.

    The activate command does a number of things. For one, it is not called automatically when Pulsar starts up, it is first called when one of the activationCommands as defined in the package.json file are called. In this case, activate is only called the first time the toggle command is called. If nobody ever invokes the menu item or hotkey, this code is never called.

    This method does two things. The first is that it creates an instance of the View class we have and adds the element that it creates to a hidden modal panel in the Pulsar workspace.

    this.yourNameWordCountView = new YourNameWordCountView(
    +	state.yourNameWordCountViewState
    +);
    +this.modalPanel = atom.workspace.addModalPanel({
    +	item: this.yourNameWordCountView.getElement(),
    +	visible: false,
    +});
    +

    We'll ignore the state stuff for now, since it's not important for this simple package. The rest should be fairly straightforward.

    The next thing this method does is create an instance of the CompositeDisposable class so it can register all the commands that can be called from the package so other packages could subscribe to these events.

    // Events subscribed to in Pulsar's system can be easily cleaned up with a CompositeDisposable
    +this.subscriptions = new CompositeDisposable();
    +
    +// Register command that toggles this view
    +this.subscriptions.add(
    +	atom.commands.add("atom-workspace", {
    +		"your-name-word-count:toggle": () => this.toggle(),
    +	})
    +);
    +

    Next we have the toggle method. This method simply toggles the visibility of the modal panel that we created in the activate method.

    toggle() {
    +  console.log('YourNameWordCount was toggled!');
    +  return (
    +    this.modalPanel.isVisible() ?
    +    this.modalPanel.hide() :
    +    this.modalPanel.show()
    +  );
    +}
    +

    This should be fairly simple to understand. We're looking to see if the modal element is visible and hiding or showing it depending on its current state.

    The Flow

    So, let's review the actual flow in this package.

    1. Pulsar starts up
    2. Pulsar starts loading packages
    3. Pulsar reads your package.json
    4. Pulsar loads keymaps, menus, styles and the main module
    5. Pulsar finishes loading packages
    6. At some point, the user executes your package command your-name-word-count:toggle
    7. Pulsar executes the activate method in your main module which sets up the UI by creating the hidden modal view
    8. Pulsar executes the package command your-name-word-count:toggle which reveals the hidden modal view
    9. At some point, the user executes the your-name-word-count:toggle command again
    10. Pulsar executes the command which hides the modal view
    11. Eventually, Pulsar is shut down which can trigger any serializations that your package has defined

    Tip

    Keep in mind that the flow will be slightly different if you choose not to use activationCommands in your package.

    Counting the Words

    So now that we understand what is happening, let's modify the code so that our little modal box shows us the current word count instead of static text.

    We'll do this in a very simple way. When the dialog is toggled, we'll count the words right before displaying the modal. So let's do this in the toggle command. If we add some code to count the words and ask the view to update itself, we'll have something like this:

    toggle() {
    +  if (this.modalPanel.isVisible()) {
    +    this.modalPanel.hide();
    +  } else {
    +    const editor = atom.workspace.getActiveTextEditor();
    +    const words = editor.getText().split(/\s+/).length;
    +    this.yourNameWordCountView.setCount(words);
    +    this.modalPanel.show();
    +  }
    +}
    +

    Let's look at the 3 lines we've added. First we get an instance of the current editor object (where our text to count is) by calling atom.workspace.getActiveTextEditor()open in new window.

    Next we get the number of words by calling getText()open in new window on our new editor object, then splitting that text on whitespace with a regular expression and then getting the length of that array.

    Finally, we tell our view to update the word count it displays by calling the setCount() method on our view and then showing the modal again. Since that method doesn't yet exist, let's create it now.

    We can add this code to the end of our your-name-word-count-view.js file:

    setCount(count) {
    +  const displayText = `There are ${count} words.`;
    +  this.element.children[0].textContent = displayText;
    +}
    +

    Pretty simple! We take the count number that was passed in and place it into a string that we then stick into the element that our view is controlling.

    Note

    To see your changes, you'll need to reload the code. You can do this by reloading the window (The window:reload command in the Command Palette). A common practice is to have two Pulsar windows, one for developing your package, and one for testing and reloading.

    Word Count Working

    Basic Debugging

    You'll notice a few console.log statements in the code. One of the cool things about Pulsar being built on Chromium is that you can use some of the same debugging tools available to you that you have when doing web development.

    To open up the Developer Console, press LNX/WIN: Ctrl+Shift+I - MAC: Alt+Cmd+I or choose the menu option View > Developer > Toggle Developer Tools.

    Developer Tools Debugging

    From here you can inspect objects, run code and view console output just as though you were debugging a web site.

    Testing

    Your package should have tests, and if they're placed in the spec directory, they can be run by Pulsar.

    Under the hood, Jasmine v1.3open in new window executes your tests, so you can assume that any DSL available there is also available to your package.

    Running Tests

    Once you've got your test suite written, you can run it by pressing LNX/WIN: Alt+Ctrl+P - MAC: Alt+Cmd+Ctrl+P or via the View > Developer > Run Package Specs menu. Our generated package comes with an example test suite, so you can run this right now to see what happens.

    Spec Suite Results

    You can also use the pulsar --test spec command to run them from the command line. It prints the test output and results to the console and returns the proper status code depending on whether the tests passed or failed.

    Summary

    We've now generated, customized and tested our first package for Pulsar. Congratulations! Now let's go ahead and publish it so it's available to the world.

    + + + diff --git a/docs/launch-manual/sections/core-hacking/sections/publishing.html b/docs/launch-manual/sections/core-hacking/sections/publishing.html new file mode 100644 index 0000000000..f86a0faf35 --- /dev/null +++ b/docs/launch-manual/sections/core-hacking/sections/publishing.html @@ -0,0 +1,227 @@ + + + + + + + + + + + + + + +

    About 2 min

    Publishing

    Pulsar bundles a command line utility called ppm which we first used back in Command Line to search for and install packages via the command line. This is invoked by using the pulsar command with the -p or --package option. The pulsar -p command can also be used to publish Pulsar packages to the public registry and update them.

    See more in Using PPM.

    Prepare Your Package

    There are a few things you should double check before publishing:

    • Your package.json file has name, description, and repository fields.
    • Your package.json name is URL Safe, as in it's not an emoji or special character.
    • Your package.json file has a version field with a value of "0.0.0".
    • Your package.json version field is Semver V2open in new window compliant.
    • Your package.json file has an engines field that contains an entry for atom such as: "engines": {"atom": ">=1.0.0 <2.0.0"}.
    • Your package has a README.md file at the root.
    • Your repository URL in the package.json file is the same as the URL of your repository.
    • Your package is in a Git repository that has been pushed to GitHubopen in new window. Follow this guideopen in new window if your package isn't already on GitHub.

    Publish Your Package

    Before you publish a package it is a good idea to check ahead of time if a package with the same name has already been published to the Pulsar Package Repositoryopen in new window. You can do that by visiting https://web.pulsar-edit.dev/packages/your-package-name to see if the package already exists. If it does, update your package's name to something that is available before proceeding.

    Now let's review what the pulsar -p publish command does:

    1. Registers the package name on Pulsar Package Repository if it is being published for the first time.
    2. Updates the version field in the package.json file and commits it.
    3. Creates a new Git tagopen in new window for the version being published.
    4. Pushes the tag and current branch up to GitHub.
    5. Updates Pulsar Package Repository with the new version being published.

    Now run the following commands to publish your package:

    $ cd path-to-your-package
    +$ pulsar -p publish minor
    +

    If this is the first package you are publishing, the pulsar -p publish command may prompt you for your GitHub username and password. If you have two-factor authentication enabled, use a personal access tokenopen in new window in lieu of a password. This is required to publish and you only need to enter this information the first time you publish. The credentials are stored securely in your keychainopen in new window once you login.

    Your package is now published and available on Pulsar Package Repository. Head on over to https://web.pulsar-edit.dev/packages/your-package-name to see your package's page.

    With pulsar -p publish, you can bump the version and publish by using

    $ pulsar -p publish <version-type>
    +

    where version-type can be major, minor and patch.

    • MAJOR version when you make incompatible API changes
    • MINOR version when you add functionality in a backwards compatible manner
    • PATCH version when you make backwards compatible bug fixes

    i.e. to bump a package from v1.0.0 to v1.1.0:

    $ pulsar -p publish minor
    +

    Check out semantic versioningopen in new window to learn more about best practices for versioning your package releases.

    You can also run pulsar -p help publish to see all the available options and pulsar -p help to see all the other available commands.

    + + + diff --git a/docs/launch-manual/sections/core-hacking/sections/summary.html b/docs/launch-manual/sections/core-hacking/sections/summary.html new file mode 100644 index 0000000000..7eaa40e401 --- /dev/null +++ b/docs/launch-manual/sections/core-hacking/sections/summary.html @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + +

    Less than 1 minute

    Summary

    If you finished this chapter, you should be an Pulsar-hacking master. We've discussed how you should work with JavaScript and CoffeeScript, and how to put it to good use in creating packages. You should also be able to do this in your own created theme now.

    Even when something goes wrong, you should be able to debug this easily. But also fewer things should go wrong, because you are capable of writing great specs for Pulsar.

    In the next chapter, we’ll go into more of a deep dive on individual internal APIs and systems of Pulsar, even looking at some Pulsar source to see how things are really getting done.

    + + + diff --git a/docs/launch-manual/sections/core-hacking/sections/the-init-file.html b/docs/launch-manual/sections/core-hacking/sections/the-init-file.html new file mode 100644 index 0000000000..97535f80bb --- /dev/null +++ b/docs/launch-manual/sections/core-hacking/sections/the-init-file.html @@ -0,0 +1,235 @@ + + + + + + + + + + + + + + +

    About 1 min

    The Init File

    Info

    The default init file for Pulsar has been changed from the previous CoffeeScript init.coffee file used by Atom to JavaScript. The CoffeeScript file will still work but should you wish to reference the specific version of this document for it then you should look at the Atom Archive.

    When Pulsar finishes loading, it will evaluate init.js in your LNX/MAC: ~/.pulsar - WIN: %USERPROFILE%\.pulsar directory, giving you a chance to run JavaScript code to make customizations. Code in this file has full access to Pulsar's APIopen in new window. If customizations become extensive, consider creating a package, which we will cover in Package: Word Count.

    You can open the init.js file in an editor from the LNX: Atom > Init Script - MAC: File > Init Script - WIN: Edit > Init Script menu.

    For example, if you have the Audio Beep configuration setting enabled, you could add the following code to your init.js file to have Pulsar greet you with an audio beep every time it loads:

    atom.beep();
    +

    Because init.js provides access to Pulsar's API, you can use it to implement useful commands without creating a new package or extending an existing one. Here's a command which uses the Selection APIopen in new window and Clipboard APIopen in new window to construct a Markdown link from the selected text and the clipboard contents as the URL:

    atom.commands.add("atom-text-editor", "markdown:paste-as-link", () => {
    +	let clipboardText, editor, selection;
    +	if (!(editor = atom.workspace.getActiveTextEditor())) {
    +		return;
    +	}
    +	selection = editor.getLastSelection();
    +	clipboardText = atom.clipboard.read();
    +	return selection.insertText(
    +		"[" + selection.getText() + "](" + clipboardText + ")"
    +	);
    +});
    +

    Now, reload Pulsar and use the Command Palette to execute the new command, Markdown: Paste As Link, by name. And if you'd like to trigger the command via a keyboard shortcut, you can define a keybinding for the command.

    + + + diff --git a/docs/launch-manual/sections/core-hacking/sections/tools-of-the-trade.html b/docs/launch-manual/sections/core-hacking/sections/tools-of-the-trade.html new file mode 100644 index 0000000000..0726c223af --- /dev/null +++ b/docs/launch-manual/sections/core-hacking/sections/tools-of-the-trade.html @@ -0,0 +1,236 @@ + + + + + + + + + + + + + + +

    Less than 1 minute

    Tools of the Trade

    To begin, there are a few things we'll assume you know, at least to some degree. Since all of Pulsar is implemented using web technologies, we have to assume you know web technologies such as JavaScript and CSS. Specifically, we'll be using Less, which is a preprocessor for CSS.

    While much of Pulsar has been converted to JavaScript, a lot of older code is still implemented in CoffeeScript but the process of "decaffeination" is ongoing, to continue this conversion feel free to read moreopen in new window. Additionally, Pulsar's default configuration language is CSON, which is based on CoffeeScript. If you don't know CoffeeScript, but you are familiar with JavaScript, you shouldn't have too much trouble. Here is an example of some simple CoffeeScript code:

    MyPackageView = require './my-package-view'
    +
    +module.exports =
    +  myPackageView: null
    +
    +  activate: (state) ->
    +    @myPackageView = new MyPackageView(state.myPackageViewState)
    +
    +  deactivate: ->
    +    @myPackageView.destroy()
    +
    +  serialize: ->
    +    myPackageViewState: @myPackageView.serialize()
    +

    We'll go over examples like this in a bit, but this is what the language looks like. Just about everything you can do with CoffeeScript in Pulsar is also doable in JavaScript. You can brush up on CoffeeScript at coffeescript.orgopen in new window.

    Less is an even simpler transition from CSS. It adds a number of useful things like variables and functions to CSS. You can learn about Less at lesscss.orgopen in new window. Our usage of Less won't get too complex in this book however, so as long as you know basic CSS you should be fine.

    + + + diff --git a/docs/launch-manual/sections/core-hacking/sections/using-ppm.html b/docs/launch-manual/sections/core-hacking/sections/using-ppm.html new file mode 100644 index 0000000000..86324e280d --- /dev/null +++ b/docs/launch-manual/sections/core-hacking/sections/using-ppm.html @@ -0,0 +1,229 @@ + + + + + + + + + + + + + + +

    Less than 1 minute

    Using ppm (Pulsar Package Manager)

    ppm is used for installing and managing Pulsar's packages in much the same way that apm did on Atom. However at this point in the project there are a few hoops you have to jump through to get it to work correctly.

    After following the build instructions you will find the ppm binary at pulsar/ppm/bin/apm but by default Pulsar will be looking in the wrong place. There will also be issues relating to the Electron version which will prevent install from the package backend. To solve this a couple of environmental variables need to be exported.

    You can now use the binary to link or install packages.

    For example to install the ide-java package from source:

    # clone the repository and cd into it
    +git clone https://github.com/pulsar-edit/ide-java
    +cd ide-java
    +
    +# from the directory where you are running pulsar source code
    +<pulsar source>/ppm/bin/apm link
    +
    + + + diff --git a/docs/launch-manual/sections/core-hacking/sections/writing-specs.html b/docs/launch-manual/sections/core-hacking/sections/writing-specs.html new file mode 100644 index 0000000000..1d2eb7607d --- /dev/null +++ b/docs/launch-manual/sections/core-hacking/sections/writing-specs.html @@ -0,0 +1,311 @@ + + + + + + + + + + + + + + +

    About 5 min

    Writing Specs

    We've looked at and written a few specs through the examples already. Now it's time to take a closer look at the spec framework itself. How exactly do you write tests in Pulsar?

    Pulsar uses Jasmineopen in new window as its spec framework. Any new functionality should have specs to guard against regressions.

    Create a New Spec

    Pulsar specsopen in new window and package specsopen in new window are added to their respective spec directory. The example below creates a spec for Pulsar core.

    Create a Spec File

    Spec files must end with -spec so add sample-spec.js to the spec directory.

    Add One or More describe Methods

    The describe method takes two arguments, a description and a function. If the description explains a behavior it typically begins with when; if it is more like a unit test it begins with the method name.

    describe("when a test is written", function () {
    +	// contents
    +});
    +

    or

    describe("Editor::moveUp", function () {
    +	// contents
    +});
    +

    Add One or More it Methods

    The it method also takes two arguments, a description and a function. Try and make the description flow with the it method. For example, a description of "this should work" doesn't read well as "it this should work". But a description of "should work" sounds great as "it should work".

    describe("when a test is written", function () {
    +	it("has some expectations that should pass", function () {
    +		// Expectations
    +	});
    +});
    +

    Add One or More Expectations

    The best way to learn about expectations is to read the Jasmine documentationopen in new window about them. Below is a simple example.

    describe("when a test is written", function () {
    +	it("has some expectations that should pass", function () {
    +		expect("apples").toEqual("apples");
    +		expect("oranges").not.toEqual("apples");
    +	});
    +});
    +
    Custom Matchers

    In addition to the Jasmine's built-in matchers, Pulsar includes the following:

    • jasmine-jqueryopen in new window
    • The toBeInstanceOf matcher is for the instanceof operator
    • The toHaveLength matcher compares against the .length property
    • The toExistOnDisk matcher checks if the file exists in the filesystem
    • The toHaveFocus matcher checks if the element currently has focus
    • The toShow matcher tests if the element is visible in the dom

    These are defined in spec/spec-helper.jsopen in new window.

    Asynchronous Specs

    Writing Asynchronous specs can be tricky at first. Some examples.

    Promises

    Working with promises is rather easy in Pulsar. You can use our waitsForPromise function.

    describe("when we open a file", function () {
    +	it("should be opened in an editor", function () {
    +		waitsForPromise(function () {
    +			atom.workspace
    +				.open("c.coffee")
    +				.then((editor) => expect(editor.getPath()).toContain("c.coffee"));
    +		});
    +	});
    +});
    +

    This method can be used in the describe, it, beforeEach and afterEach functions.

    describe("when we open a file", function () {
    +	beforeEach(function () {
    +		waitsForPromise(() => atom.workspace.open("c.coffee"));
    +	});
    +
    +	it("should be opened in an editor", function () {
    +		expect(atom.workspace.getActiveTextEditor().getPath()).toContain(
    +			"c.coffee"
    +		);
    +	});
    +});
    +

    If you need to wait for multiple promises use a new waitsForPromise function for each promise. (Caution: Without beforeEach this example will fail!)

    describe("waiting for the packages to load", function () {
    +	beforeEach(function () {
    +		waitsForPromise(() => atom.workspace.open("sample.js"));
    +
    +		waitsForPromise(() => atom.packages.activatePackage("tabs"));
    +
    +		waitsForPromise(() => atom.packages.activatePackage("tree-view"));
    +	});
    +
    +	it("should have waited long enough", function () {
    +		expect(atom.packages.isPackageActive("tabs")).toBe(true);
    +		expect(atom.packages.isPackageActive("tree-view")).toBe(true);
    +	});
    +});
    +

    waitsForPromise can take an additional object argument before the function. The object can have the following properties:

    • shouldReject Whether the promise should reject or resolve (default: false)
    • timeout The amount of time (in ms) to wait for the promise to be resolved or rejected (default: process.env.CI ? 60000 : 5000)
    • label The label to display if promise times out (default: 'promise to be resolved or rejected')
    describe("when we open a file", function () {
    +	it("should be opened in an editor", function () {
    +		waitsForPromise(
    +			{
    +				shouldReject: false,
    +				timeout: 5000,
    +				label: "promise to be resolved or rejected",
    +			},
    +			() =>
    +				atom.workspace
    +					.open("c.coffee")
    +					.then((editor) => expect(editor.getPath()).toContain("c.coffee"))
    +		);
    +	});
    +});
    +

    Asynchronous Functions with Callbacks

    Specs for asynchronous functions can be done using the waitsFor and runs functions. A simple example.

    describe("fs.readdir(path, cb)", function () {
    +	it("is async", function () {
    +		const spy = jasmine.createSpy("fs.readdirSpy");
    +		fs.readdir("/tmp/example", spy);
    +
    +		waitsFor(() => spy.callCount > 0);
    +
    +		runs(function () {
    +			const exp = [null, ["example.coffee"]];
    +
    +			expect(spy.mostRecentCall.args).toEqual(exp);
    +			expect(spy).toHaveBeenCalledWith(null, ["example.coffee"]);
    +		});
    +	});
    +});
    +

    For a more detailed documentation on asynchronous tests please visit the Jasmine documentationopen in new window.

    Running Specs

    Most of the time you'll want to run specs by triggering the window:run-package-specs command. This command is not only to run package specs, it can also be used to run Pulsar core specs when working on Pulsar itself. This will run all the specs in the current project's spec directory.

    To run a limited subset of specs use the fdescribe or fit methods. You can use those to focus a single spec or several specs. Modified from the example above, focusing an individual spec looks like this:

    describe("when a test is written", function () {
    +	fit("has some expectations that should pass", function () {
    +		expect("apples").toEqual("apples");
    +		expect("oranges").not.toEqual("apples");
    +	});
    +});
    +

    Running on CI

    It is now easy to run the specs in a CI environment like Travis and AppVeyor. See the Travis CI For Your Packagesopen in new window and AppVeyor CI For Your Packagesopen in new window posts for more details.

    Running via the Command Line

    To run tests on the command line, run Pulsar with the --test flag followed by one or more paths to test files or directories. You can also specify a --timeout option, which will force-terminate your tests after a certain number of seconds have passed.

    pulsar --test --timeout 60 ./test/test-1.js ./test/test-2.js
    +

    Customizing your test runner

    By default, package tests are run with Jasmine 1.3, which is outdated but can't be changed for compatibility reasons. You can specify your own custom test runner by including an atomTestRunner field in your package.json. Pulsar will require whatever module you specify in this field, so you can use a relative path or the name of a module in your package's dependencies.

    Your test runner module must export a single function, which Pulsar will call within a new window to run your package's tests. Your function will be called with the following parameters:

    • testPaths An array of paths to tests to run. Could be paths to files or directories.
    • buildAtomEnvironment A function that can be called to construct an instance of the atom global. No atom global will be explicitly assigned, but you can assign one in your runner if desired. This function should be called with the following parameters:
      • applicationDelegate An object responsible for Pulsar's interaction with the browser process and host OS. Use buildDefaultApplicationDelegate for a default instance. You can override specific methods on this object to prevent or test these interactions.
      • window A window global.
      • document A document global.
      • configDirPath A path to the configuration directory (usually ~/.pulsar).
      • enablePersistence A boolean indicating whether the Pulsar environment should save or load state from the file system. You probably want this to be false.
    • buildDefaultApplicationDelegate A function that builds a default instance of the application delegate, suitable to be passed as the applicationDelegate parameter to buildAtomEnvironment.
    • logFile An optional path to a log file to which test output should be logged.
    • headless A boolean indicating whether or not the tests are being run from the command line via pulsar --test.
    • legacyTestRunner This function can be invoked to run the legacy Jasmine runner, giving your package a chance to transition to a new test runner while maintaining a subset of its tests in the old environment.

    Your function should return a promise that resolves to an exit code when your tests are finished running. This exit code will be returned when running your tests via the command line.

    + + + diff --git a/docs/launch-manual/sections/faq/index.html b/docs/launch-manual/sections/faq/index.html new file mode 100644 index 0000000000..0c97e8fa92 --- /dev/null +++ b/docs/launch-manual/sections/faq/index.html @@ -0,0 +1,223 @@ + + + + + + + + FAQ | + + + + + + +

    FAQ

    Less than 1 minute

    FAQ

    Under Construction

    This document is under construction, please check back soon for updates. Please see our socials and feel free to ask for assistance or inquire as to the status of this document.

    Having trouble?

    If you have any issues then please feel free to ask for help from the Pulsar Team or the wider community via any of our Community areas

    If you think you have found a bug then please have a look through our existing issuesopen in new window and if you can't find anything then please create a new bug reportopen in new window.

    Common Issues

    macOS error "App is damaged and can’t be opened"

    The binary is likely from before macOS binaries started being signed. A up-to-date and signed binary may be downloaded from the download page. The unsigned binary can be made to run by running xattr -cr /Applications/Pulsar.app/ in the terminal. See hereopen in new window for more information.

    Pulsar does not launch "GPU process isn't usable. Goodbye"

    You may need to launch the application with the argument --no-sandbox to get around this issue. This is something under investigationopen in new window.

    + + + diff --git a/docs/launch-manual/sections/faq/sections/common-issues.html b/docs/launch-manual/sections/faq/sections/common-issues.html new file mode 100644 index 0000000000..031196a36e --- /dev/null +++ b/docs/launch-manual/sections/faq/sections/common-issues.html @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + +

    Less than 1 minute

    Common Issues

    macOS error "App is damaged and can’t be opened"

    The binary is likely from before macOS binaries started being signed. A up-to-date and signed binary may be downloaded from the download page. The unsigned binary can be made to run by running xattr -cr /Applications/Pulsar.app/ in the terminal. See hereopen in new window for more information.

    Pulsar does not launch "GPU process isn't usable. Goodbye"

    You may need to launch the application with the argument --no-sandbox to get around this issue. This is something under investigationopen in new window.

    + + + diff --git a/docs/launch-manual/sections/faq/sections/get-help.html b/docs/launch-manual/sections/faq/sections/get-help.html new file mode 100644 index 0000000000..0e964eba7b --- /dev/null +++ b/docs/launch-manual/sections/faq/sections/get-help.html @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + +

    Less than 1 minute

    Having trouble?

    If you have any issues then please feel free to ask for help from the Pulsar Team or the wider community via any of our Community areas

    If you think you have found a bug then please have a look through our existing issuesopen in new window and if you can't find anything then please create a new bug reportopen in new window.

    + + + diff --git a/docs/launch-manual/sections/getting-started/index.html b/docs/launch-manual/sections/getting-started/index.html new file mode 100644 index 0000000000..8cffba38a3 --- /dev/null +++ b/docs/launch-manual/sections/getting-started/index.html @@ -0,0 +1,242 @@ + + + + + + + + Getting Started | + + + + + + +

    Getting Started

    Less than 1 minute

    Getting Started

    Under Construction

    This document is under construction, please check back soon for updates. Please see our community links and feel free to ask for assistance or inquire as to the status of this document.

    This section is all about how to start using Pulsar, such as how to install it and how to use it for basic text editing.

    Why Pulsar?

    There are a lot of text editors out there; why should you spend your time learning about and using Pulsar? Editors like Sublime and TextMate offer convenience but only limited extensibility. On the other end of the spectrum, Emacs and Vim offer extreme flexibility, but they aren't very approachable and can only be customized with special-purpose scripting languages.

    We think we can do better. Our goal is a zero-compromise combination of hackability and usability: an editor that will be welcoming to an elementary school student on their first day learning to code, but also a tool they won't outgrow as they develop into seasoned hackers.

    As we've used Pulsar to build Pulsar, what began as an experiment has gradually matured into a tool we can't live without. On the surface, Pulsar is the modern desktop text editor you've come to expect. Pop the hood, however, and you'll discover a system begging to be hacked on.

    The Nucleus of Pulsar

    The web is not without its faults, but two decades of development has forged it into an incredibly malleable and powerful platform. So when we set out to write a text editor that we ourselves would want to extend, web technology was the obvious choice. But first, we had to free it from its chains.

    The Native Web

    Web browsers are great for browsing web pages, but writing code is a specialized activity that warrants dedicated tools. More importantly, the browser severely restricts access to the local system for security reasons, and for us, a text editor that couldn't write files or run local sub-processes was a non-starter.

    For this reason, we didn't build Pulsar as a traditional web application. Instead, Pulsar is a specialized variant of Chromium designed to be a text editor rather than a web browser. Every Pulsar window is essentially a locally-rendered web page.

    All the APIs available to a typical Node.js application are also available to the code running in each window's JavaScript context. This hybrid provides a unique client-side development experience.

    Since everything is local, you don't have to worry about asset pipelines, script concatenation, and asynchronous module definitions. If you want to load some code, just require it at the top of your file. Node's module system makes it easy to break the system down into lots of small, focused packages.

    JavaScript, Meet C++

    Interacting with native code is also really simple. For example, we wrote a wrapper around the Oniguruma regular expression engine for our TextMate grammar support. In a browser, that would have required adventures with NaCl or Esprima. Node integration made it easy.

    In addition to the Node APIs, we also expose APIs for native dialogs, adding application and context menu items, manipulating the window dimensions, etc.

    Web Tech: The Fun Parts

    Another great benefit, that comes with writing code for Pulsar, is the guarantee that it's running on the newest version of Chromium. That means we can ignore issues like browser compatibility and polyfills. We can use all the web's shiny features of tomorrow, today.

    For example, the layout of our workspace and panes is based on flexbox. It's an emerging standard and has gone through a lot of change since we started using it, but none of that mattered as long as it worked.

    With the entire industry pushing web technology forward, we're confident that we're building Pulsar on fertile ground. Native UI technologies come and go, but the web is a standard that becomes more capable and ubiquitous with every passing year. We're excited to dig deeper into its toolbox.

    An Open-Source Text Editor

    We see Pulsar as a great replacement for Atom but we can't do it without your support going forward, since we know that we can't achieve our vision for Pulsar alone. As Emacs and Vim have demonstrated over the past three decades, if you want to build a thriving, long-lasting community around a text editor, it has to be open source.

    The entire Pulsar editor is free and open source and available under our Organizationalopen in new window repositories.

    Installing Pulsar

    To get started with Pulsar, we'll need to get it on your system. This section will go over installing Pulsar on your system, as well as the basics of how to build it from source.

    Installing Pulsar should be fairly simple. Generally, you can go to pulsar-edit.devopen in new window and you should see a download button.

    Simply select your operating system (if not opened automatically) and architecture (where necessary) and choose the type of download you require.

    The button or buttons should be specific to your platform, and the download package should be easily installable. However, let's go over them here in a bit of detail.

    Updating Pulsar

    You should consider updating Pulsar periodically for the latest improvements to the software. Additionally, When Pulsar receives hotfixes for security vulnerabilities you will want to update your version of Pulsar as soon as possible.

    Currently Pulsar does not support automatic updates. What this means is that new versions will have to be obtained via the Pulsar Downloads here on our website. This is something on our roadmap to change as soon as possible.

    If you have installed Pulsar via a package manager then you should use the instructions provided by that package manager for updating your installation.

    Portable Mode

    Pulsar stores configuration and state in a .pulsar directory usually located in your home directory (%userprofile% on Windows). You can however run Pulsar in portable mode where both the app and the configuration are stored together such as on a removable storage device.

    To setup Pulsar in portable mode download the relevant package and extract it to your removable storage.

    Portable Notes

    • The .pulsar directory must be writeable
    • You can move an existing .pulsar directory to your portable device
    • Pulsar can also store its Electron user data in your .pulsar directory - just create a subdirectory called electronUserData inside .pulsar
    • Alternatively you can set the ATOM_HOME environment variable to point wherever you want (you can write a .sh or .cmd script to temporarily set it and launch it from that)
    • Portable mode installations will not automatically update

    Building Pulsar from Source

    The Hacking the Core section of the launch manual covers instructions on how to clone and build the source code if you prefer that option.

    Proxy and Firewall Settings

    Behind a Firewall?

    If you are behind a firewall and seeing SSL errors when installing packages you can disable strict SSL by running:

    $ pulsar -p config set strict-ssl false
    +

    Using a Proxy?

    If you are using a HTTP(S) proxy you can configure ppm to use it by running:

    $ pulsar -p config set https-proxy <YOUR_PROXY_ADDRESS>
    +

    You can run pulsar -p config get https-proxy to verify it has been set correctly.

    Pulsar Basics

    Now that Pulsar is installed on your system, let's fire it up, configure it and get acquainted with the editor.

    When you launch Pulsar for the first time, you should get a screen that looks like this:

    Pulsar's welcome screen

    This is the Pulsar welcome screen and gives you a pretty good starting point for how to get started with the editor.

    Terminology

    You can find definitions for all the various terms that we use throughout the manual in our Glossary.

    Command Palette

    In that welcome screen, we are introduced to probably the most important command in Pulsar, the Command Palette. If you press Cmd+Shift+PCtrl+Shift+P while focused in an editor pane, the command palette will pop up.

    Note:

    Throughout the book, we will use shortcut keybindings like LNX: Ctrl+Shift+P - MAC: Cmd+Shift+P - WIN: Ctrl+Shift+P to demonstrate how to run a command and tabbed sections where necessary where instructions for different platforms may differ.

    If you have customized your Pulsar keymap, you can always see the keybinding you have mapped in the Command Palette or the Keybindings tab in the Settings View.

    This search-driven menu can do just about any major task that is possible in Pulsar. Instead of clicking around all the application menus to look for something, you can press LNX/WIN: Ctrl+Shift+P - MAC: Cmd+Shift+P and search for the command.

    Command Palette

    Not only can you see and quickly search through thousands of possible commands, but you can also see if there is a keybinding associated with it. This is great because it means you can guess your way to doing interesting things while also learning the shortcut key strokes for doing it.

    For the rest of the book, we will try to be clear as to the text you can search for in the Command Palette in addition to the keybinding for different commands.

    Settings and Preferences

    Pulsar has a number of settings and preferences you can modify in the Settings View.

    Settings View

    This includes things like changing the theme, specifying how to handle wrapping, font settings, tab size, scroll speed and much more. You can also use this screen to install new packages and themes, which we'll cover in Pulsar Packages.

    To open the Settings View, you can:

    • Use the LNX: Edit > Preferences - MAC: Pulsar > Preferences - WIN: File > Settings menu item in the menu bar
    • Search for settings-view:open in the Command Palette
    • Use the LNX/WIN: Ctrl+, - MAC: Cmd+, keybinding.

    Changing the Theme

    The Settings View also lets you change the themes for Pulsar. Pulsar ships with 4 different UI themes, dark and light variants of the Pulsar and One theme, as well as 8 different syntax themes. You can modify the active theme by clicking on the Themes tab in the sidebar of the Settings View, or you can install new themes by clicking the Install tab.

    Changing the theme from the Settings View

    The UI themes control the style of UI elements like the tabs and the tree view, while the syntax themes control the syntax highlighting of text you load into the editor. To change the syntax or UI theme, simply pick something different in the appropriate dropdown list.

    There are also dozens of themes on the Pulsar Package Repositoryopen in new window that you can choose from if you want something different. We will cover customizing a theme in Style Tweaks and creating your own theme in Creating a Theme.

    Soft Wrap

    You can use the Settings View to specify your whitespace and wrapping preferences.

    Whitespace and wrapping preferences settings

    Enabling "Soft Tabs" will insert spaces instead of actual tab characters when you press the Tab key and the "Tab Length" setting specifies how many spaces to insert when you do so, or how many spaces are used to represent a tab if "Soft Tabs" is disabled.

    The "Soft Wrap" option will wrap lines that are too long to fit in your current window. If soft wrapping is disabled, the lines will simply run off the side of the screen and you will have to scroll the window to see the rest of the content. If "Soft Wrap At Preferred Line Length" is toggled, the lines will wrap at 80 characters instead of the end of the screen. You can also change the default line length to a value other than 80 on this screen.

    In Basic Customization we will see how to set different wrap preferences for different types of files (for example, if you want to wrap Markdown files but not other files).

    Opening, Modifying, and Saving Files

    Now that your editor is looking and acting how you want, let's start opening up and editing files. This is a text editor after all, right?

    Opening a File

    There are several ways to open a file in Pulsar. You can do it by choosing File > Open from the menu bar or by pressing

    LNX/WIN: Ctrl+O - MAC: Cmd+O

    to choose a file from the standard dialog.

    Open file by dialog

    This is useful for opening a file that is not contained in the project you're currently in (more on that next), or if you're starting from a new window for some reason.

    Another way to open a file in Pulsar is from the command line using the pulsar command.

    Note

    Install Shell Commands on macOS

    The Pulsar menu has an item named "Install Shell Commands" which installs the pulsar and ppm commands if Pulsar wasn't able to install them itself on a macOS system.

    On Linux commands are installed automatically as a part of Pulsar's installation process. Windows requires the path to be exposed manuallyopen in new window by the user at this time.

    You can run the pulsar command with one or more file paths to open up those files in Pulsar.

    $ pulsar --help
    +> Pulsar Editor v1.100.0
    +
    +> Usage: pulsar [options] [path ...]
    +
    +> One or more paths to files or folders may be specified. If there is an
    +> existing Pulsar window that contains all of the given folders, the paths
    +> will be opened in that window. Otherwise, they will be opened in a new
    +> window.
    +
    +> ...
    +

    This is a great tool if you're used to the terminal or you work from the terminal a lot. Just fire off pulsar [files] and you're ready to start editing. You can even open a file at a certain line (and optionally column) so the cursor will be positioned exactly where you want. For example, you may search some keyword in a repository to find the line you want to edit:

    $ git grep -n 'Opening a File$'
    +getting-started/sections/pulsar-basics.md:130:##### Opening a File
    +

    and then jump to the beginning of that line by appending a colon and the line number to the file path:

    $ pulsar getting-started/sections/pulsar-basics.md:130
    +

    Sometimes you may want the cursor to jump to the exact column position of the searched keyword. Just append another colon plus the column number:

    $ git grep -n --column 'Windows Explorer'
    +getting-started/sections/pulsar-basics.md.md:150:722
    +$ pulsar getting-started/sections/pulsar-basics.md:150:722
    +

    Editing and Saving a File

    Editing a file is pretty straightforward. You can click around and scroll with your mouse and type to change the content. There is no special editing mode or key commands. If you prefer editors with modes or more complex key commands, you should take a look at the Pulsar Package Repositoryopen in new window. There are a lot of packages that emulate popular styles.

    Opening Directories

    You can open any number of directories from the command line by passing their paths to the pulsar command line tool. For example, you could run the command pulsar ./hopes ./dreams to open both the hopes and the dreams directories at the same time.

    When you open Pulsar with one or more directories, you will automatically get a Tree View on the side of your window.

    Tree View in an open project

    The Tree View allows you to explore and modify the file and directory structure of your project. You can open, rename, delete and create new files from this view.

    Note

    Pulsar Packages

    Like many parts of Pulsar, the Tree View is not built directly into the editor, but is its own standalone package that is shipped with Pulsar by default. Packages that are bundled with Pulsar are referred to as Core packages. Ones that aren't bundled with Pulsar are referred to as Community packages.

    You can find the source code to the Tree View on GitHub at https://github.com/pulsar-edit/tree-view.

    This is one of the interesting things about Pulsar. Many of its core features are actually just packages implemented the same way you would implement any other functionality. This means that if you don't like the Tree View for example, you could write your own implementation of that functionality and replace it entirely.

    Opening a File in a Project

    Once you have a project open in Pulsar, you can easily find and open any file within that project.

    The fuzzy finder uses the core.ignoredNames, fuzzy-finder.ignoredNames and core.excludeVCSIgnoredPaths configuration settings to filter out files and folders that will not be shown. If you have a project with tons of files you don't want it to search through, you can add patterns or paths to either of these config settings or your standard .gitignore filesopen in new window. We'll learn more about config settings in Global Configuration Settings, but for now you can easily set these in the Settings View under Core Settings.

    Both core.ignoredNames and fuzzy-finder.ignoredNames are interpreted as glob patterns as implemented by the minimatch Node moduleopen in new window.

    Tip

    Configuration Setting Notation

    Sometimes you'll see us refer to configuration settings all spelled out like "Ignored Names in Core Settings". Other times you'll see us use the shorthand name like core.ignoredNames. Both of these refer to the same thing. The shorthand is the package name, then a dot ., followed by the "camel-cased" name of the setting.

    If you have a phrase you want to camel-case, follow these steps:

    1. Lowercase the first word
    2. Capitalize the first letter in all other words
    3. Remove the spaces

    So "Ignored Names" becomes "ignoredNames".

    Summary

    You should now have a basic understanding of what Pulsar is and what you want to do with it. You should also have it installed on your system and be able to use it for the most basic text editing operations.

    Now you're ready to start digging into the fun stuff.

    + + + diff --git a/docs/launch-manual/sections/getting-started/sections/installing-pulsar.html b/docs/launch-manual/sections/getting-started/sections/installing-pulsar.html new file mode 100644 index 0000000000..2aa049ee8f --- /dev/null +++ b/docs/launch-manual/sections/getting-started/sections/installing-pulsar.html @@ -0,0 +1,225 @@ + + + + + + + + + + + + + + +

    About 3 min

    Installing Pulsar

    To get started with Pulsar, we'll need to get it on your system. This section will go over installing Pulsar on your system, as well as the basics of how to build it from source.

    Installing Pulsar should be fairly simple. Generally, you can go to pulsar-edit.devopen in new window and you should see a download button.

    Simply select your operating system (if not opened automatically) and architecture (where necessary) and choose the type of download you require.

    The button or buttons should be specific to your platform, and the download package should be easily installable. However, let's go over them here in a bit of detail.

    Updating Pulsar

    You should consider updating Pulsar periodically for the latest improvements to the software. Additionally, When Pulsar receives hotfixes for security vulnerabilities you will want to update your version of Pulsar as soon as possible.

    Currently Pulsar does not support automatic updates. What this means is that new versions will have to be obtained via the Pulsar Downloads here on our website. This is something on our roadmap to change as soon as possible.

    If you have installed Pulsar via a package manager then you should use the instructions provided by that package manager for updating your installation.

    Portable Mode

    Pulsar stores configuration and state in a .pulsar directory usually located in your home directory (%userprofile% on Windows). You can however run Pulsar in portable mode where both the app and the configuration are stored together such as on a removable storage device.

    To setup Pulsar in portable mode download the relevant package and extract it to your removable storage.

    Portable Notes

    • The .pulsar directory must be writeable
    • You can move an existing .pulsar directory to your portable device
    • Pulsar can also store its Electron user data in your .pulsar directory - just create a subdirectory called electronUserData inside .pulsar
    • Alternatively you can set the ATOM_HOME environment variable to point wherever you want (you can write a .sh or .cmd script to temporarily set it and launch it from that)
    • Portable mode installations will not automatically update

    Building Pulsar from Source

    The Hacking the Core section of the launch manual covers instructions on how to clone and build the source code if you prefer that option.

    Proxy and Firewall Settings

    Behind a Firewall?

    If you are behind a firewall and seeing SSL errors when installing packages you can disable strict SSL by running:

    $ pulsar -p config set strict-ssl false
    +

    Using a Proxy?

    If you are using a HTTP(S) proxy you can configure ppm to use it by running:

    $ pulsar -p config set https-proxy <YOUR_PROXY_ADDRESS>
    +

    You can run pulsar -p config get https-proxy to verify it has been set correctly.

    + + + diff --git a/docs/launch-manual/sections/getting-started/sections/pulsar-basics.html b/docs/launch-manual/sections/getting-started/sections/pulsar-basics.html new file mode 100644 index 0000000000..55f80bd885 --- /dev/null +++ b/docs/launch-manual/sections/getting-started/sections/pulsar-basics.html @@ -0,0 +1,240 @@ + + + + + + + + + + + + + + +

    About 10 min

    Pulsar Basics

    Now that Pulsar is installed on your system, let's fire it up, configure it and get acquainted with the editor.

    When you launch Pulsar for the first time, you should get a screen that looks like this:

    Pulsar's welcome screen

    This is the Pulsar welcome screen and gives you a pretty good starting point for how to get started with the editor.

    Terminology

    You can find definitions for all the various terms that we use throughout the manual in our Glossary.

    Command Palette

    In that welcome screen, we are introduced to probably the most important command in Pulsar, the Command Palette. If you press Cmd+Shift+PCtrl+Shift+P while focused in an editor pane, the command palette will pop up.

    Note:

    Throughout the book, we will use shortcut keybindings like LNX: Ctrl+Shift+P - MAC: Cmd+Shift+P - WIN: Ctrl+Shift+P to demonstrate how to run a command and tabbed sections where necessary where instructions for different platforms may differ.

    If you have customized your Pulsar keymap, you can always see the keybinding you have mapped in the Command Palette or the Keybindings tab in the Settings View.

    This search-driven menu can do just about any major task that is possible in Pulsar. Instead of clicking around all the application menus to look for something, you can press LNX/WIN: Ctrl+Shift+P - MAC: Cmd+Shift+P and search for the command.

    Command Palette

    Not only can you see and quickly search through thousands of possible commands, but you can also see if there is a keybinding associated with it. This is great because it means you can guess your way to doing interesting things while also learning the shortcut key strokes for doing it.

    For the rest of the book, we will try to be clear as to the text you can search for in the Command Palette in addition to the keybinding for different commands.

    Settings and Preferences

    Pulsar has a number of settings and preferences you can modify in the Settings View.

    Settings View

    This includes things like changing the theme, specifying how to handle wrapping, font settings, tab size, scroll speed and much more. You can also use this screen to install new packages and themes, which we'll cover in Pulsar Packages.

    To open the Settings View, you can:

    • Use the LNX: Edit > Preferences - MAC: Pulsar > Preferences - WIN: File > Settings menu item in the menu bar
    • Search for settings-view:open in the Command Palette
    • Use the LNX/WIN: Ctrl+, - MAC: Cmd+, keybinding.

    Changing the Theme

    The Settings View also lets you change the themes for Pulsar. Pulsar ships with 4 different UI themes, dark and light variants of the Pulsar and One theme, as well as 8 different syntax themes. You can modify the active theme by clicking on the Themes tab in the sidebar of the Settings View, or you can install new themes by clicking the Install tab.

    Changing the theme from the Settings View

    The UI themes control the style of UI elements like the tabs and the tree view, while the syntax themes control the syntax highlighting of text you load into the editor. To change the syntax or UI theme, simply pick something different in the appropriate dropdown list.

    There are also dozens of themes on the Pulsar Package Repositoryopen in new window that you can choose from if you want something different. We will cover customizing a theme in Style Tweaks and creating your own theme in Creating a Theme.

    Soft Wrap

    You can use the Settings View to specify your whitespace and wrapping preferences.

    Whitespace and wrapping preferences settings

    Enabling "Soft Tabs" will insert spaces instead of actual tab characters when you press the Tab key and the "Tab Length" setting specifies how many spaces to insert when you do so, or how many spaces are used to represent a tab if "Soft Tabs" is disabled.

    The "Soft Wrap" option will wrap lines that are too long to fit in your current window. If soft wrapping is disabled, the lines will simply run off the side of the screen and you will have to scroll the window to see the rest of the content. If "Soft Wrap At Preferred Line Length" is toggled, the lines will wrap at 80 characters instead of the end of the screen. You can also change the default line length to a value other than 80 on this screen.

    In Basic Customization we will see how to set different wrap preferences for different types of files (for example, if you want to wrap Markdown files but not other files).

    Opening, Modifying, and Saving Files

    Now that your editor is looking and acting how you want, let's start opening up and editing files. This is a text editor after all, right?

    Opening a File

    There are several ways to open a file in Pulsar. You can do it by choosing File > Open from the menu bar or by pressing

    LNX/WIN: Ctrl+O - MAC: Cmd+O

    to choose a file from the standard dialog.

    Open file by dialog

    This is useful for opening a file that is not contained in the project you're currently in (more on that next), or if you're starting from a new window for some reason.

    Another way to open a file in Pulsar is from the command line using the pulsar command.

    Note

    Install Shell Commands on macOS

    The Pulsar menu has an item named "Install Shell Commands" which installs the pulsar and ppm commands if Pulsar wasn't able to install them itself on a macOS system.

    On Linux commands are installed automatically as a part of Pulsar's installation process. Windows requires the path to be exposed manuallyopen in new window by the user at this time.

    You can run the pulsar command with one or more file paths to open up those files in Pulsar.

    $ pulsar --help
    +> Pulsar Editor v1.100.0
    +
    +> Usage: pulsar [options] [path ...]
    +
    +> One or more paths to files or folders may be specified. If there is an
    +> existing Pulsar window that contains all of the given folders, the paths
    +> will be opened in that window. Otherwise, they will be opened in a new
    +> window.
    +
    +> ...
    +

    This is a great tool if you're used to the terminal or you work from the terminal a lot. Just fire off pulsar [files] and you're ready to start editing. You can even open a file at a certain line (and optionally column) so the cursor will be positioned exactly where you want. For example, you may search some keyword in a repository to find the line you want to edit:

    $ git grep -n 'Opening a File$'
    +getting-started/sections/pulsar-basics.md:130:##### Opening a File
    +

    and then jump to the beginning of that line by appending a colon and the line number to the file path:

    $ pulsar getting-started/sections/pulsar-basics.md:130
    +

    Sometimes you may want the cursor to jump to the exact column position of the searched keyword. Just append another colon plus the column number:

    $ git grep -n --column 'Windows Explorer'
    +getting-started/sections/pulsar-basics.md.md:150:722
    +$ pulsar getting-started/sections/pulsar-basics.md:150:722
    +

    Editing and Saving a File

    Editing a file is pretty straightforward. You can click around and scroll with your mouse and type to change the content. There is no special editing mode or key commands. If you prefer editors with modes or more complex key commands, you should take a look at the Pulsar Package Repositoryopen in new window. There are a lot of packages that emulate popular styles.

    Opening Directories

    You can open any number of directories from the command line by passing their paths to the pulsar command line tool. For example, you could run the command pulsar ./hopes ./dreams to open both the hopes and the dreams directories at the same time.

    When you open Pulsar with one or more directories, you will automatically get a Tree View on the side of your window.

    Tree View in an open project

    The Tree View allows you to explore and modify the file and directory structure of your project. You can open, rename, delete and create new files from this view.

    Note

    Pulsar Packages

    Like many parts of Pulsar, the Tree View is not built directly into the editor, but is its own standalone package that is shipped with Pulsar by default. Packages that are bundled with Pulsar are referred to as Core packages. Ones that aren't bundled with Pulsar are referred to as Community packages.

    You can find the source code to the Tree View on GitHub at https://github.com/pulsar-edit/tree-view.

    This is one of the interesting things about Pulsar. Many of its core features are actually just packages implemented the same way you would implement any other functionality. This means that if you don't like the Tree View for example, you could write your own implementation of that functionality and replace it entirely.

    Opening a File in a Project

    Once you have a project open in Pulsar, you can easily find and open any file within that project.

    The fuzzy finder uses the core.ignoredNames, fuzzy-finder.ignoredNames and core.excludeVCSIgnoredPaths configuration settings to filter out files and folders that will not be shown. If you have a project with tons of files you don't want it to search through, you can add patterns or paths to either of these config settings or your standard .gitignore filesopen in new window. We'll learn more about config settings in Global Configuration Settings, but for now you can easily set these in the Settings View under Core Settings.

    Both core.ignoredNames and fuzzy-finder.ignoredNames are interpreted as glob patterns as implemented by the minimatch Node moduleopen in new window.

    Tip

    Configuration Setting Notation

    Sometimes you'll see us refer to configuration settings all spelled out like "Ignored Names in Core Settings". Other times you'll see us use the shorthand name like core.ignoredNames. Both of these refer to the same thing. The shorthand is the package name, then a dot ., followed by the "camel-cased" name of the setting.

    If you have a phrase you want to camel-case, follow these steps:

    1. Lowercase the first word
    2. Capitalize the first letter in all other words
    3. Remove the spaces

    So "Ignored Names" becomes "ignoredNames".

    + + + diff --git a/docs/launch-manual/sections/getting-started/sections/summary.html b/docs/launch-manual/sections/getting-started/sections/summary.html new file mode 100644 index 0000000000..a9d2b8c9b8 --- /dev/null +++ b/docs/launch-manual/sections/getting-started/sections/summary.html @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + +

    Less than 1 minute

    Summary

    You should now have a basic understanding of what Pulsar is and what you want to do with it. You should also have it installed on your system and be able to use it for the most basic text editing operations.

    Now you're ready to start digging into the fun stuff.

    + + + diff --git a/docs/launch-manual/sections/getting-started/sections/why-pulsar.html b/docs/launch-manual/sections/getting-started/sections/why-pulsar.html new file mode 100644 index 0000000000..44e91626d5 --- /dev/null +++ b/docs/launch-manual/sections/getting-started/sections/why-pulsar.html @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + +

    About 2 min

    Why Pulsar?

    There are a lot of text editors out there; why should you spend your time learning about and using Pulsar? Editors like Sublime and TextMate offer convenience but only limited extensibility. On the other end of the spectrum, Emacs and Vim offer extreme flexibility, but they aren't very approachable and can only be customized with special-purpose scripting languages.

    We think we can do better. Our goal is a zero-compromise combination of hackability and usability: an editor that will be welcoming to an elementary school student on their first day learning to code, but also a tool they won't outgrow as they develop into seasoned hackers.

    As we've used Pulsar to build Pulsar, what began as an experiment has gradually matured into a tool we can't live without. On the surface, Pulsar is the modern desktop text editor you've come to expect. Pop the hood, however, and you'll discover a system begging to be hacked on.

    The Nucleus of Pulsar

    The web is not without its faults, but two decades of development has forged it into an incredibly malleable and powerful platform. So when we set out to write a text editor that we ourselves would want to extend, web technology was the obvious choice. But first, we had to free it from its chains.

    The Native Web

    Web browsers are great for browsing web pages, but writing code is a specialized activity that warrants dedicated tools. More importantly, the browser severely restricts access to the local system for security reasons, and for us, a text editor that couldn't write files or run local sub-processes was a non-starter.

    For this reason, we didn't build Pulsar as a traditional web application. Instead, Pulsar is a specialized variant of Chromium designed to be a text editor rather than a web browser. Every Pulsar window is essentially a locally-rendered web page.

    All the APIs available to a typical Node.js application are also available to the code running in each window's JavaScript context. This hybrid provides a unique client-side development experience.

    Since everything is local, you don't have to worry about asset pipelines, script concatenation, and asynchronous module definitions. If you want to load some code, just require it at the top of your file. Node's module system makes it easy to break the system down into lots of small, focused packages.

    JavaScript, Meet C++

    Interacting with native code is also really simple. For example, we wrote a wrapper around the Oniguruma regular expression engine for our TextMate grammar support. In a browser, that would have required adventures with NaCl or Esprima. Node integration made it easy.

    In addition to the Node APIs, we also expose APIs for native dialogs, adding application and context menu items, manipulating the window dimensions, etc.

    Web Tech: The Fun Parts

    Another great benefit, that comes with writing code for Pulsar, is the guarantee that it's running on the newest version of Chromium. That means we can ignore issues like browser compatibility and polyfills. We can use all the web's shiny features of tomorrow, today.

    For example, the layout of our workspace and panes is based on flexbox. It's an emerging standard and has gone through a lot of change since we started using it, but none of that mattered as long as it worked.

    With the entire industry pushing web technology forward, we're confident that we're building Pulsar on fertile ground. Native UI technologies come and go, but the web is a standard that becomes more capable and ubiquitous with every passing year. We're excited to dig deeper into its toolbox.

    An Open-Source Text Editor

    We see Pulsar as a great replacement for Atom but we can't do it without your support going forward, since we know that we can't achieve our vision for Pulsar alone. As Emacs and Vim have demonstrated over the past three decades, if you want to build a thriving, long-lasting community around a text editor, it has to be open source.

    The entire Pulsar editor is free and open source and available under our Organizationalopen in new window repositories.

    + + + diff --git a/docs/launch-manual/sections/using-pulsar/index.html b/docs/launch-manual/sections/using-pulsar/index.html new file mode 100644 index 0000000000..2683f39d15 --- /dev/null +++ b/docs/launch-manual/sections/using-pulsar/index.html @@ -0,0 +1,359 @@ + + + + + + + + Using Pulsar | + + + + + + +

    Using Pulsar

    Less than 1 minute

    Using Pulsar

    Under Construction

    This document is under construction, please check back soon for updates. Please see our socials and feel free to ask for assistance or inquire as to the status of this document.

    Pulsar Packages

    First we'll start with the Pulsar package system. As we mentioned previously, Pulsar itself is a very basic core of functionality that ships with a number of useful packages that add new features like the Tree Viewopen in new window and the Settings Viewopen in new window.

    In fact, there are more than 80 packages that comprise all of the functionality that is available in Pulsar by default. For example, the Welcome screenopen in new window that you see when you first start Pulsar, the spell checkeropen in new window, the themesopen in new window and the Fuzzy Finderopen in new window are all packages that are separately maintained and all use the same APIs that you have access to, as we'll see in great detail in Hacking the Core.

    This means that packages can be incredibly powerful and can change everything from the very look and feel of the entire interface to the basic operation of even core functionality.

    In order to install a new package, you can use the Install tab in the now familiar Settings View. Open up the Settings View using LNX/WIN: Ctrl+, - MAC: Cmd+, click on the "Install" tab and type your search query into the box under Install Packages.

    The packages listed here have been published to https://web.pulsar-edit.devopen in new window which is the official registry for Pulsar packages. Searching on the Settings View will go to the Pulsar package registry and pull in anything that matches your search terms.

    Package install screen

    All of the packages will come up with an "Install" button. Clicking that will download the package and install it. Your editor will now have the functionality that the package provides.

    Package Settings

    Once a package is installed in Pulsar, it will show up in the Settings View under the "Packages" tab, along with all the pre-installed packages that come with Pulsar. To filter the list in order to find one, you can type into search box directly under the "Installed Packages" heading.

    Package settings screen

    Clicking on the "Settings" button for a package will give you the settings screen for that package specifically. Here you have the option of changing some of the default variables for the package, seeing what all the command keybindings are, disabling the package temporarily, looking at the source code, seeing the current version of the package, reporting issues and uninstalling the package.

    If a new version of any of your packages is released, Pulsar will automatically detect it and you can upgrade the package from either this screen or from the "Updates" tab. This helps you easily keep all your installed packages up to date.

    Pulsar Themes

    You can also find and install new themes for Pulsar from the Settings View. These can be either UI themes or syntax themes and you can search for them from the "Install" tab, just like searching for new packages. Make sure to press the "Themes" toggle next to the search box.

    Theme search screen

    Clicking on the theme title will take you to a profile page for the theme on pulsar-edit.dev, which often has a screenshot of the theme. This way you can see what it looks like before installing it.

    Clicking on "Install" will install the theme and make it available in the Theme dropdowns as we saw in Changing the Theme.

    Example of the Unity UI theme with Monokai syntax theme

    Command Line

    You can also install packages or themes from the command line using ppm (Pulsar Package Manager). This is used by running pulsar -p <commmand> or pulsar --package <command>.

    Tip

    Check that you have ppm available by running the following command in your terminal:

    $ pulsar -p help install
    +

    You should see a message print out with details about the pulsar -p install command.

    If you do not, see the Installing Pulsar section for instructions on how to install the pulsar command for your system.

    You can install packages by using the pulsar -p install command:

    • pulsar -p install <package_name> to install the latest version.
    • pulsar -p install <package_name>@<package_version> to install a specific version.

    For example pulsar -p install minimap@4.40.0 installs the 4.40.0 release of the minimapopen in new window package.

    You can also use ppm to find new packages to install. If you run pulsar -p search, you can search the package registry for a search term.

    $ pulsar -p search linter
    +> Search Results For 'linter' (30)
    +> ├── linter A Base Linter with Cow Powers (9863242 downloads, 4757 stars)
    +> ├── linter-ui-default Default UI for the Linter package (7755748 downloads, 1201 stars)
    +> ├── linter-eslint Lint JavaScript on the fly, using ESLint (v7 or older) (2418043 downloads, 1660 stars)
    +> ├── linter-jshint Linter plugin for JavaScript, using jshint (1202044 downloads, 1271 stars)
    +> ├── linter-gcc Lint C and C++ source files using gcc / g++ (863989 downloads, 194 stars)
    +> ...
    +> ├── linter-shellcheck Lint Bash on the fly, using shellcheck (136938 downloads, 280 stars)
    +> └── linter-rust Lint Rust-files, using rustc and/or cargo (132550 downloads, 91 stars)
    +

    You can use pulsar -p view to see more information about a specific package.

    $ pulsar -p view linter
    +> linter
    +> ├── 3.4.0
    +> ├── https://github.com/steelbrain/linter
    +> ├── A Base Linter with Cow Powers
    +> ├── 9863242 downloads
    +> └── 4757 stars
    +>
    +> Run `pulsar -p install linter` to install this package.
    +

    Using ppm to install from other sources

    By default ppm will be using the Pulsar Package Repositoryopen in new window. However you can also use it to install from other locations which can be useful if you are trying to install a package not published to the Pulsar Package Repository.

    Github or Git Remotes

    Pulsar can install from a GitHub repository or any valid git remote url. The -b option can then be used to specify a particular tag or branch.

    Git remote
    pulsar -p install <git_remote> [-b <branch_or_tag>]

    GitHub
    pulsar -p install <github_username>/<github_project> [-b <branch_or_tag>]

    For example to install the Generic-LSPopen in new window package from GitHub you could use either:

    pulsar -p install https://github.com/mauricioszabo/generic-lsp/

    or

    pulsar -p install mauricioszabo/generic-lsp

    This will use the current HEAD commit of the default branch. If you want to install a specific version from a branch or tag then use the -b option:

    e.g. pulsar -p install https://github.com/mauricioszabo/generic-lsp/ -b v1.0.3

    Moving in Pulsar

    While it's pretty easy to move around Pulsar by clicking with the mouse or using the arrow keys, there are some keybindings that may help you keep your hands on the keyboard and navigate around a little faster.

    You can also move directly to a specific line (and column) number with Ctrl+G. This will bring up a dialog that asks which line you would like to jump to. You can also use the row:column syntax to jump to a character in that line as well.

    Go directly to a line

    Additional Movement and Selection Commands

    Pulsar also has a few movement and selection commands that don't have keybindings by default. You can access these commands from the Command Palette, but if you find yourself using commands that don't have a keybinding often, have no fear! You can easily add an entry to your keymap.cson to create a key combination. You can open keymap.cson file in an editor from the LNX: Edit > Keymap - MAC: Pulsar > Keymap - WIN: File > Keymap menu. For example, the command editor:move-to-beginning-of-screen-line is available in the command palette, but it's not bound to any key combination. To create a key combination you need to add an entry in your keymap.cson file. For editor:select-to-previous-word-boundary, you can add the following to your keymap.cson:

    This will bind the command editor:select-to-previous-word-boundary to LNX/WIN: Ctrl+Shift+E - MAC: Cmd+Shift+E. For more information on customizing your keybindings, see Customizing Keybindings.

    Here's a list of Movement and Selection Commands that do not have a keyboard shortcut by default:

    You can also jump around a little more informatively with the Symbols View. To jump to a symbol such as a method definition, press LNX/WIN: Ctrl+R - MAC: Cmd+R. This opens a list of all symbols in the current file, which you can fuzzy filter similarly to LNX/WIN: Ctrl+T - MAC: Cmd+T. You can also search for symbols across your project but it requires a tags file.

    Search by symbol across your project

    You can generate a tags file by using the ctags utilityopen in new window. Once it is installed, you can use it to generate a tags file by running a command to generate it. See the ctags documentationopen in new window for details.

    You can customize how tags are generated by creating your own .ctags file in your home directory, LNX/MAC: ~/.ctags - WIN: %USERPROFILE%\.ctags. An example can be found hereopen in new window.

    The symbols navigation functionality is implemented in the symbols-viewopen in new window package.

    Bookmarks

    Pulsar also has a great way to bookmark specific lines in your project so you can jump back to them quickly.

    If you press LNX/WIN: Alt+Ctrl+F2 - MAC Cmd+F2, Pulsar will toggle a "bookmark" on the current line. You can set these throughout your project and use them to quickly find and jump to important lines of your project. A small bookmark symbol is added to the line gutter, like on line 22 of the image below.

    If you hit F2, Pulsar will jump to the next bookmark in the file you currently have focused. If you use Shift+F2 it will cycle backwards through them instead.

    You can also see a list of all your project's current bookmarks and quickly filter them and jump to any of them by hitting Ctrl+F2.

    View and filter bookmarks

    The bookmarks functionality is implemented in the bookmarksopen in new window package.

    Pulsar Selections

    Text selections in Pulsar support a number of actions, such as scoping deletion, indentation and search actions, and marking text for actions such as quoting and bracketing.

    Selections mirror many of the movement commands. They're actually exactly the same keybindings as the movement commands, but with a Shift key added in.

    Editing and Deleting Text

    So far we've looked at a number of ways to move around and select regions of a file, so now let's actually change some of that text. Obviously you can type in order to insert characters, but there are also a number of ways to delete and manipulate text that could come in handy.

    Basic Manipulation

    There are a handful of cool keybindings for basic text manipulation that might come in handy. These range from moving around lines of text and duplicating lines to changing the case.

    Pulsar also has built in functionality to re-flow a paragraph to hard-wrap at a given maximum line length. You can format the current selection to have lines no longer than 80 (or whatever number editor.preferredLineLength is set to) characters using LNX/WIN: Ctrl+Shift+Q - MAC: Alt+Cmd+Q. If nothing is selected, the current paragraph will be reflowed.

    Deleting and Cutting

    You can also delete or cut text out of your buffer with some shortcuts. Be ruthless.

    Multiple Cursors and Selections

    One of the cool things that Pulsar can do out of the box is support multiple cursors. This can be incredibly helpful in manipulating long lists of text.

    Using these commands you can place cursors in multiple places in your document and effectively execute the same commands in multiple places at once.

    Using multiple cursors

    This can be incredibly helpful in doing many type of repetitive tasks such as renaming variables or changing the format of some text. You can use this with almost any package or command - for example, changing case and moving or duplicating lines.

    You can also use the mouse to select text with the LNX/WIN: Ctrl - MAC: Cmd key pressed down to select multiple regions of your text simultaneously.

    Return to a single cursor with Esc or by clicking anywhere in the file to position a cursor there as normal.

    Whitespace

    Pulsar comes with several commands to help you manage the whitespace in your document. One very useful pair of commands converts leading spaces into tabs and converts leading tabs into spaces. If you're working with a document that has mixed whitespace, these commands are great for helping to normalize the file. There are no keybindings for the whitespace commands, so you will have to search your command palette for "Convert Spaces to Tabs" (or vice versa) to run one of these commands.

    The whitespace commands are implemented in the whitespaceopen in new window package. The settings for the whitespace commands are managed on the page for the whitespace package.

    Managing your whitespace settings

    Note

    The "Remove Trailing Whitespace" option is on by default. This means that every time you save any file opened in Pulsar, it will strip all trailing whitespace from the file. If you want to disable this, go to the whitespace package in your settings panel and uncheck that option.

    Pulsar will also by default ensure that your file has a trailing newline. You can also disable this option on that screen.

    Brackets

    Pulsar ships with intelligent and easy to use bracket handling.

    It will by default highlight [], (), and {} style brackets when your cursor is over them. It will also highlight matching XML and HTML tags.

    Pulsar will also automatically autocomplete [], (), and {}, "", '', “”, ‘’, «», ‹›, and `` when you type the leading one. If you have a selection and you type any of these opening brackets or quotes, Pulsar will enclose the selection with the opening and closing brackets or quotes.

    There are a few other interesting bracket related commands that you can use.

    The brackets functionality is implemented in the bracket-matcheropen in new window package. Like all of these packages, to change defaults related to bracket handling, or to disable it entirely, you can navigate to this package in the Settings view.

    Encoding

    Pulsar also ships with some basic file encoding support should you find yourself working with non-UTF-8 encoded files, or should you wish to create one.

    If you pull up the file encoding dialog, you can choose an alternate file encoding to save your file in.

    When you open a file, Pulsar will try to auto-detect the encoding. If Pulsar can't identify the encoding, the encoding will default to UTF-8, which is also the default encoding for new files.

    Changing your file encoding

    If you pull up the encoding menu and change the active encoding to something else, the file will be written out in that encoding the next time you save the file.

    The encoding selector is implemented in the encoding-selectoropen in new window package.

    Find and Replace

    Finding and replacing text in your file or project is quick and easy in Pulsar.

    If you launch either of those commands, you'll be greeted with the Find and Replace panel at the bottom of your screen.

    Find and replace text in the current file

    To search within your current file you can press LNX/WIN: Cmd+F - MAC: Ctrl+F, type in a search string and press LNX/WIN/MAC: Enter or LNX/WINF3 - MAC: Cmd+G or the "Find Next" button) multiple times to cycle through all the matches in that file. Alt+Enter will find all occurrences of the search string. The Find and Replace panel also contains buttons for toggling case sensitivity, performing regular expression matching, scoping the search to selections, and performing whole word search.

    If you type a string in the replacement text box, you can replace matches with a different string. For example, if you wanted to replace every instance of the string "Atom" with the string "Pulsar", you would enter those values in the two text boxes and press the "Replace All" button to perform the replacements.

    Note

    Note: Pulsar uses JavaScript regular expressions to perform regular expression searches.

    When doing a regular expression search, the replacement syntax to refer back to search groups is $1, $2, … $&. Refer to JavaScript's guide to regular expressionsopen in new window to learn more about regular expression syntax you can use in Pulsar.

    You can also find and replace throughout your entire project if you invoke the panel with LNX/WIN: Ctrl+Shift+F - MAC: Cmd+Shift+F.

    Find and replace text in your project

    This is a great way to find out where in your project a function is called, an anchor is linked to or a specific misspelling is located. Click on the matching line to jump to that location in that file.

    You can limit a search to a subset of the files in your project by entering a glob patternopen in new window into the "File/Directory pattern" text box. For example, the pattern src/*.js would restrict the search to JavaScript files in the src directory. The "globstar" pattern (**) can be used to match arbitrarily many subdirectories. For example, docs/**/*.md will match docs/a/foo.md, docs/a/b/foo.md, etc. You can enter multiple glob patterns separated by commas, which is useful for searching in multiple file types or subdirectories.

    When you have multiple project folders open, this feature can also be used to search in only one of those folders. For example, if you had the folders /path1/folder1 and /path2/folder2 open, you could enter a pattern starting with folder1 to search only in the first folder.

    Press Esc while focused on the Find and Replace panel to clear the pane from your workspace.

    The Find and Replace functionality is implemented in the find-and-replaceopen in new window package and uses the scandalopen in new window Node module to do the actual searching.

    Snippets

    Snippets are an incredibly powerful way to quickly generate commonly needed code syntax from a shortcut.

    The idea is that you can type something like habtm and then press the Tab key and it will expand into has_and_belongs_to_many.

    Many Core and Community packages come bundled with their own snippets that are specific to it. For example, the language-html package that provides support for HTML syntax highlighting and grammar comes with dozens of snippets to create many of the various HTML tags you might want to use. If you create a new HTML file in Pulsar, you can type html and then press Tab and it will expand to:

    <!DOCTYPE html>
    +<html lang="en" dir="ltr">
    +	<head>
    +		<meta charset="utf-8" />
    +		<title></title>
    +	</head>
    +	<body></body>
    +</html>
    +

    It will also position the cursor in the lang attribute value so you can edit it if necessary. Many snippets have multiple focus points that you can move through with the Tab key as well - for instance, in the case of this HTML snippet, after the cursor is placed in the lang attribute value, you can continue pressing Tab and the cursor will move to the dir attribute value, then to the middle of the title tag, then finally to the middle of the body tag.

    To see all the available snippets for the file type that you currently have open, choose "Snippets: Available" in the Command Palette.

    View all available snippets

    You can also use fuzzy search to filter this list down by typing in the selection box. Selecting one of them will execute the snippet where your cursor is (or multiple cursors are).

    Creating Your Own Snippets

    So that's pretty cool, but what if there is something the language package didn't include or something that is custom to the code you write? Luckily it's incredibly easy to add your own snippets.

    There is a text file in your LNX/MAC: ~/.pulsar - WIN: %USERPROFILE%\.pulsar directory called snippets.cson that contains all your custom snippets that are loaded when you launch Pulsar. You can also easily open up that file by selecting the LNX: Edit > Snippets - MAC: Pulsar > Snippets - WIN: File > Snippets menu.

    Snippet Format

    So let's look at how to write a snippet. The basic snippet format looks like this:

    '.source.js':
    +  'console.log':
    +    'prefix': 'log'
    +    'body': 'console.log(${1:"crash"});$2'
    +

    The leftmost keys are the selectors where these snippets should be active. The easiest way to determine what this should be is to go to the language package of the language you want to add a snippet for and look for the "Scope" string.

    For example, if we wanted to add a snippet that would work for Java files, we would look up the language-java package in our Settings view and we can see the Scope is source.java. Then the top level snippet key would be that prepended by a period (like a CSS class selector would do).

    Finding the selector scope for a snippet

    The next level of keys are the snippet names. These are used for describing the snippet in a more readable way in the snippet menu. You can name them whatever you want.

    Under each snippet name is a prefix that should trigger the snippet and a body to insert when the snippet is triggered.

    Each $ followed by a number is a tab stop. Tab stops are cycled through by pressing Tab once a snippet has been triggered.

    Tab stops with the same number will create multiple cursors.

    The above example adds a log snippet to JavaScript files that would expand to:

    console.log("crash");
    +

    The string "crash" would be initially selected and pressing tab again would place the cursor after the ;

    Warning

    Snippet keys, unlike CSS selectors, can only be repeated once per level. If there are duplicate keys at the same level, then only the last one will be read. See Configuring with CSON for more information.

    Multi-line Snippet Body

    You can also use CoffeeScript multi-line syntaxopen in new window using """ for larger templates:

    '.source.js':
    +  'if, else if, else':
    +    'prefix': 'ieie'
    +    'body': """
    +      if (${1:true}) {
    +        $2
    +      } else if (${3:false}) {
    +        $4
    +      } else {
    +        $5
    +      }
    +    """
    +

    As you might expect, there is a snippet to create snippets. If you open up a snippets file and type snip and then press Tab, you will get the following text inserted:

    '.source.js':
    +  'Snippet Name':
    +    'prefix': 'hello'
    +    'body': 'Hello World!'
    +

    💥 Just fill that bad boy out and you have yourself a snippet. As soon as you save the file, Pulsar should reload the snippets and you will immediately be able to try it out.

    Multiple Snippets per Source

    You can see below the format for including multiple snippets for the same scope in your snippets.cson file. Just include the snippet name, prefix, and body keys for additional snippets inside the scope key:

    '.source.gfm':
    +  'Hello World':
    +    'prefix': 'hewo'
    +    'body': 'Hello World!'
    +
    +  'Github Hello':
    +    'prefix': 'gihe'
    +    'body': 'Octocat says Hi!'
    +
    +  'Octocat Image Link':
    +    'prefix': 'octopic'
    +    'body': '![GitHub Octocat](https://assets-cdn.github.com/images/modules/logos_page/Octocat.png)'
    +

    Again, see Configuring with CSON for more information on CSON key structure and non-repeatability.

    More Info

    The snippets functionality is implemented in the snippetsopen in new window package.

    For more examples, see the snippets in the language-htmlopen in new window and language-javascriptopen in new window packages.

    Autocomplete

    If you're still looking to save some typing time, Pulsar also ships with simple autocompletion functionality.

    The autocomplete system lets you view and insert possible completions in the editor using Tab or Enter.

    Autocomplete menu

    By default, the autocomplete system will look through the current open file for strings that match what you're starting to type.

    If you want more options, in the Settings panel for the autocomplete-plus package you can toggle a setting to make autocomplete-plus look for text in all your open buffers rather than just the current file.

    The Autocomplete functionality is implemented in the autocomplete-plusopen in new window package.

    Folding

    If you want to see an overview of the structure of the code file you're working on, folding can be a helpful tool. Folding hides blocks of code such as functions or looping blocks in order to simplify what is on your screen.

    Panes

    Tip

    If you don't like using tabs, you don't have to. You can disable the tabs packageopen in new window and each pane will still support multiple pane items. You just won't have tabs to use to click between them.

    Pending Pane Items

    "Pending Pane Items" were formerly referred to as "Preview Tabs"

    When you open a new file by single-clicking in the Tree View, it will open in a new tab with an italic title. This indicates that the file is "pending". When a file is pending, it will be replaced by the next pending file that is opened. This allows you to click through a bunch of files to find something without having to go back and close them all.

    You can confirm a pending file by doing any of the following:

    • Double-clicking the tab of the file
    • Double-clicking the file in the tree view
    • Editing the contents of the file
    • Saving the file

    You can also open a file already confirmed by double-clicking it in the tree view instead of single-clicking it.

    Disabling Pending Pane Items

    Allow Pending Pane Items setting

    If you would prefer to not have files open in pending form, you can disable this behavior by unchecking "Allow Pending Pane Items" in the Core Settings section of the Settings View. With pending pane items disabled, single-clicking a file in the Tree View will select the file but not open it. You will have to double-click the file to open it.

    Grammar

    The "grammar" of a file is what language Pulsar has associated with that file. Types of grammars would include "Java" or "GitHub-Flavored Markdown". We looked at this a bit when we created some snippets in Snippets.

    When you load a file, Pulsar does a little work to try to figure out what type of file it is. Largely this is accomplished by looking at its file extension (.md is generally a Markdown file, etc.), though sometimes it has to inspect the content a bit to figure it out.

    When you open a file and Pulsar can't determine a grammar for the file, it will default to "Plain Text", which is the simplest one. If it does default to "Plain Text", picks the wrong grammar for the file, or if for any reason you wish to change the selected grammar, you can pull up the Grammar Selector with Ctrl+Shift+L.

    Grammar Selector

    When the grammar of a file is changed, Atom will remember that for the current session.

    The Grammar Selector functionality is implemented in the grammar-selectoropen in new window package.

    Version Control in Pulsar

    Version control is an important aspect of any project and Pulsar comes with basic Gitopen in new window and GitHubopen in new window integration built in.

    In order to use version control in Pulsar, the project root needs to contain the Git repository.

    Checkout HEAD revision

    The LNX/WIN: Alt+Ctrl+Z - MAC: Alt+Cmd+Z keybinding checks out the HEAD revision of the file in the editor.

    This is a quick way to discard any saved and staged changes you've made and restore the file to the version in the HEAD commit. This is essentially the same as running git checkout HEAD -- <path> and git reset HEAD -- <path> from the command line for that path.

    Git checkout

    This command goes onto the undo stack so you can use LNX/WIN: Ctrl+Z - MAC: Cmd+Z afterwards to restore the previous contents.

    Git status list

    Pulsar ships with the fuzzy-finder packageopen in new window which provides LNX/WIN: Ctrl+T - MAC: Cmd+T to quickly open files in the project and LNX/WIN: Ctrl+B - MAC: Cmd+B to jump to any open editor. The package also provides LNX/WIN: Ctrl+Shift+B - MAC: Cmd+Shift+B which displays a list of all the untracked and modified files in the project. These will be the same files that you would see on the command line if you ran git status.

    Git status list

    An icon will appear to the right of each file letting you know whether it is untracked or modified.

    Commit editor

    Pulsar can be used as your Git commit editor and ships with the language-git packageopen in new window which adds syntax highlighting to edited commit, merge, and rebase messages.

    Git commit message highlighting

    You can configure Pulsar to be your Git commit editor with the following command:

    $ git config --global core.editor "pulsar --wait"
    +

    The language-gitopen in new window package will help remind you to be brief by colorizing the first lines of commit messages when they're longer than 50 or 65 characters.

    Status bar icons

    The status-baropen in new window package that ships with Pulsar includes several Git decorations that display on the right side of the status bar:

    Git Status Bar decorations

    The currently checked out branch name is shown with the number of commits the branch is ahead of or behind its upstream branch. An icon is added if the file is untracked, modified, or ignored. The number of lines added and removed since the file was last committed will be displayed as well.

    Line diffs

    The included git-diffopen in new window package colorizes the gutter next to lines that have been added, edited, or removed.

    Git line diff indications

    This package also adds Alt+G Down and Alt+GUp keybindings that allow you to move the cursor to the next or previous diff in the current editor.

    Open on GitHub

    If the project you're working on is on GitHub, there are also some very useful integrations you can use. Most of the commands will take the current file you're viewing and open a view of that file on GitHub - for instance, the blame or commit history of that file.

    • Alt+G O - Open file on GitHub
    • Alt+G B - Open Blame view of file on GitHub
    • Alt+G H - Open History view of file on GitHub
    • Alt+G C - Copy the URL of the current file on GitHub to the clipboard
    • Alt+G R - Branch compare on GitHub

    The branch comparison shows you the commits that are on the branch you're currently working on locally that are not on the mainline branch.

    Open Blame of file on GitHub

    GitHub package

    The GitHub package brings Git and GitHub integration right inside Pulsar.

    Most of the functionality lives within the Git and GitHub dock items.

    The Git and GitHub panels

    There are different ways to access them, probably the most common way is through their keybindings:

    • Open the Git panel: Ctrl+9
    • Open the GitHub panel: Ctrl+8

    Another way is from the menu: Packages -> GitHub -> Toggle Git Tab and Toggle GitHub Tab

    Or you can also toggle the Git panel from the Status Bar by clicking on the changed files icon:

    Open Git panel


    Initialize repositories

    In case a project doesn't have a Git repository yet, you can create one from the Git panel.

    Initialize repositories

    Clone repositories

    To clone a repository, open the GitHub panel while you have no project folders open in Pulsar and click "Clone an existing GitHub repository". In the dialog, paste the URL of a repository and click "Clone". The new project will be added to the Tree View.

    GitHub panel

    Clone dialog

    Alternately, run the GitHub: Clone command to open the Clone dialog any time.

    Branch

    To open the branch tooltip, click the branch icon in the Status Bar. From there you can create or switch branches.

    Create or switch branches

    Stage

    After making some changes, stage anything you want to be part of the next commit. Choose between staging...

    • All changes: Click the "Stage All" button in the "Unstaged Changes" bar.
    • Files: Double-click a file or select a file and press Enter.
    • Hunk: Click on the "Stage Hunk" button or select a hunk and press Enter.
    • Lines: Click on a line (or drag on multiple lines) to select, then click on the "Stage Selection" button. Or use the LNX/WIN: Ctrl+/ - MAC: Cmd-/ key to toggle from hunk mode to line mode, then press LNX/WIN: Ctrl-Enter - MAC: Cmd-Enter to stage just a single line.

    Use the LNX/WIN: Cmd-Left - MAC: Ctrl-Left or LNX/WIN: Ctrl-Right - MAC: Cmd-Right arrow key to switch between file list and the diff view. Unstaging can be done in the same way.

    Stage changes

    Discard changes

    If you no longer want to keep some changes, you can discard them. It's similar to staging, but accessible behind a context menu.

    • All changes: Click the ... menu in the "Unstaged Changes" header and choose "Discard All Changes".
    • Files: Right-click a file (or multiple) and choose "Discard Changes".
    • Hunk: Click on the trash icon in the top bar of a hunk.
    • Lines: Right-click on a line (or multiple) and choose "Discard Selection".

    Discard changes

    Commit Preview

    To double check all changes that are going into your next commit, click the "See All Staged Changes" button above the commit message box. It lets you see all of your staged changes in a single pane. This "commit preview" can also serve as an inspiration for writing the commit message.

    Commit Preview

    Commit

    Once you've staged your changes, enter a commit message. Feel free to describe the commit in more detail after leaving an empty line. Finalize by clicking the Commit button. If you need more space, click the expand icon at the bottom right. It will open a commit editor in the center.

    Commit changes

    To add multiple co-authors to a commit, click the "👤➕" icon in the bottom left corner of the commit message editor. Now you can search by name, email or GitHub username to give credit to a co-author.

    Commit with co-authors

    Amend and undo

    In case you forgot to commit a change and would like to add it to your previous commit, right-click on the last commit, then choose "Amend" from the context menu.

    Amend previous commit

    If you want to edit the commit message of your last commit, or add/remove changes, click on the "Undo" button. It will roll back to the state just before you clicked on the commit button.

    Undo previous commit

    View commits

    Once you've made some commits, click on a commit message in the recent commit list to see the full diff and commit message associated with each:

    View commit detai

    Publish and push

    When you're ready to share your changes with your team members, click the Publish button in the Status Bar. It will push your local branch to the remote repository. After making more commits, you can Push them as well from the Status Bar.

    Publish and push commits

    Fetch and pull

    From time to time it's a good idea to click on the Fetch button to see if any other team member pushed changes. If so, click on Pull to merge the changes into your local branch.

    Fetch and pull commits

    If you prefer to rebase when pulling, you can configure Git to make it the default behavior:

    git config --global --bool pull.rebase true
    +

    Learn more about merge vs. rebaseopen in new window.

    Resolve conflicts

    Sometimes there can be conflicts when trying to merge. Files that have merge conflicts will show up in the "Merge Conflicts" list. Click on a file to open the editor. There you can resolve the conflict by picking a version or make further edits. Once done, stage the file and commit.

    Resolve conflicts

    Create a Pull Request

    When your changes are ready to be reviewed by your team members, open the "GitHub" panel Ctrl+8 and click on Open new pull request. It will open the browser where you can continue creating a pull request. If commits haven't been pushed or the branch isn't published yet, the GitHub package will do that automatically for you.

    Create a Pull Request

    View Pull Requests

    Once the pull request is created, it will appear under Current pull request at the top of the panel. Underneath is a list of Open pull requests. It lets you quickly find a pull request by avatar, title or PR number. It also lets you keep an eye on the CI status. Clicking on a pull request in the list opens a center pane with more details, the timeline and conversations.

    View Pull Requests

    Open any Issue or Pull Request

    You can open issues or pull requests from any repo on GitHub. To do so, run the GitHub: Open Issue Or Pull Request command and paste the URL from an issue or pull request. Then press the Open Issue or Pull Request button and it will open a center pane. This lets you keep an issue or pull request as a reference, when working in another repo.

    Open Issue or Pull Request

    Checkout a Pull Request

    To test a pull request locally, open it in the workspace center by clicking on the pull request in the "open pull requests" list from the GitHub tab, then click on the Checkout button. It will automatically create a local branch and pull all the changes. If you would like to contribute to that pull request, start making changes, commit and push. Your contribution is now part of that pull request.

    Checkout a pull request

    View Pull Request review comments

    To view review comments on a Pull Request, open the Reviews Tab from the See Reviews button from the footer of a Pull Request Pane. Alternatively, if the pull request has already been checked out, Reviews Tab can also be open from the same button on GitHub Tab.

    Open review tab from footer

    You can see all the review summaries and comments of a pull request in the Reviews Tab. The comment section has a progress bar to help you keep track of how close are you to finish addressing the Pull Request comments (i.e. marking all comment threads on a Pull Request as "resolved"). Comment threads are greyed out after they have been resolved.

    Review tab

    After the pull request branch has been checked out, you can click Jump To File to open the commented on file and make changes as per the review comment right in the editor. If you would like to get the full context of the review comment, click Open Diff to open the diff view with line highlighting.

    Jump to file from review tab

    Conversely, in-editor comments are indicated by the comment icon in the gutter. Clicking the icon, either from within the editor or the diff view, will take you back to the Reviews Tab.

    Open review tab from diff

    Respond to a Pull Request review comment

    To respond to a Pull Request review comment, type your message and click Comment; a single line comment will be created in the same thread as the comment you responded to. After addressing a Pull Request review comment, click Resolve conversation to mark the whole thread as "resolved". The progress bar in the "Comments" section will update accordingly.

    Respond to a Pull Request review comment

    Writing in Pulsar

    Though it is probably most common to use Pulsar to write software code, Pulsar can also be used to write prose quite effectively. Most often this is done in some sort of markup language such as Asciidoc or Markdownopen in new window (in which this manual is written). Here we'll quickly cover a few of the tools Pulsar provides for helping you write prose.

    In these docs, we'll concentrate on writing in Markdown; however, other prose markup languages like Asciidoc have packages that provide similar functionality.

    Spell Checking

    If you're working in text (which includes plain text files, GitHub markdown, and Git commit messages by default), Pulsar will automatically try to check your spelling.

    Any misspelled words will be highlighted (by default with a dashed red line beneath the word), and you can pull up a menu of possible corrections by hitting LNX/WIN: Ctrl+Shift+; - MAC: Cmd+Shift+; (or by choosing "Correct Spelling" from the right-click context menu or from the Command Palette).

    Checking your spelling

    To add more types of files to the list of what Pulsar will try to spell check, go to the Spell Check package settings in your Settings view and add any grammars you want to spell check.

    The default grammars to spell check are text.plain, source.gfm, text.git-commit, source.asciidoc, source.rst, and text.restructuredtext but you can add other grammars if you wish to check those types of files too.

    The spell checking is implemented in the spell-checkopen in new window package.

    Previews

    When writing prose in a markup language, it's often very useful to get an idea of what the content will look like when it's rendered. Pulsar ships with a package for previewing Markdown by default.

    • Ctrl+Shift+M - Will toggle Preview mode for Markdown.

    Preview your prose

    As you edit the text, the preview will also update automatically. This makes it fairly easy to check your syntax as you type.

    You can also copy the rendered HTML from the preview pane into your system clipboard when the preview is focused and you press LNX: Ctrl+Ins - WIN: Ctrl+C - MAC: Cmd+C or if you right-click in the preview pane and choose "Copy as HTML".

    Markdown preview is implemented in the markdown-previewopen in new window package.

    Snippets

    There are also a number of great snippets available for writing Markdown quickly.

    If you type img and hit tab you get a Markdown-formatted image embed code like ![](). If you type table and hit tab you get a nice example table to fill out.

    | Header One | Header Two |
    +| :--------- | :--------- |
    +| Item One   | Item Two   |
    +

    Although there are only a handful of Markdown snippets (b for bold, i for italic, code for a code block, etc), they save you from having to look up the more obscure syntaxes. Again, you can easily see a list of all available snippets for the type of file you're currently in by choosing Snippets: Available in the Command Palette.

    Basic Customization

    Now that we are feeling comfortable with just about everything built into Pulsar, let's look at how to tweak it. Perhaps there is a keybinding that you use a lot but feels wrong or a color that isn't quite right for you. Pulsar is amazingly flexible, so let's go over some of the simpler flexes it can do.

    Configuring with CSON

    All of Pulsar's config files (with the exception of your style sheet and your Init Script are written in CSON, short for CoffeeScript Object Notationopen in new window. Just like its namesake JSON, JavaScript Object Notationopen in new window, CSON is a text format for storing structured data in the form of simple objects made up of key-value pairs.

    key:
    +  key: value
    +  key: value
    +  key: [value, value]
    +

    Objects are the backbone of any CSON file, and are delineated by indentation (as in the above example). A key's value can either be a String, a Number, an Object, a Boolean, null, or an Array of any of these data types.

    Warning

    Just like the more common JSON, CSON's keys can only be repeated once per object. If there are duplicate keys, then the last usage of that key overwrites all others, as if they weren't there. The same holds true for Pulsar's config files.

    Don't do this:

    # Only the second snippet will be loaded
    +'.source.js':
    +  'console.log':
    +    'prefix': 'log'
    +    'body': 'console.log(${1:"crash"});$2'
    +'.source.js':
    +  'console.error':
    +    'prefix': 'error'
    +    'body': 'console.error(${1:"crash"});$2'
    +

    Use this instead:

    # Both snippets will be loaded
    +'.source.js':
    +  'console.log':
    +    'prefix': 'log'
    +    'body': 'console.log(${1:"crash"});$2'
    +  'console.error':
    +    'prefix': 'error'
    +    'body': 'console.error(${1:"crash"});$2'
    +

    Style Tweaks

    If you want to apply quick-and-dirty personal styling changes without creating an entire theme that you intend to publish, you can add styles to the styles.less file in your LNX/MAC: ~/.pulsar - WIN: %USERPROFILE%\.pulsar directory. You can open this file in an editor from the LNX: Edit > Stylesheet - MAC: Pulsar > Stylesheet - WIN: File > Stylesheet menu.

    For example, to change the colors of the Status Bar, you could add the following rule to your styles.less file:

    .status-bar {
    +	color: white;
    +	background-color: black;
    +}
    +

    The easiest way to see what classes are available to style is to inspect the DOM manually via the Developer Tools. We'll go over the Developer Tools in great detail in the next chapter, but for now let's take a simple look. You can open the Developer Tools by pressing LNX/WIN: Ctrl+Shift+I - MAC: Alt+Cmd+I, which will bring up the Chromium Developer Tools panel.

    Developer Tools

    With the Developer Tools, you can inspect all the elements in Pulsar. If you want to update the style of something, you can figure out what classes it has and add a Less rule to your stylesheet to modify it.

    Tip

    If you are unfamiliar with Less, it is a basic CSS preprocessor that makes some things in CSS a bit easier. You can learn more about it at lesscss.orgopen in new window.

    If you prefer to use CSS instead, you can do that in the same styles.less file, since CSS is also valid in Less.

    Customizing Keybindings

    Pulsar keymaps work similarly to stylesheets. Just as stylesheets use selectors to apply styles to elements, Pulsar keymaps use selectors to associate key combinations with events in specific contexts. Here's a small example, excerpted from Pulsar's built-in keymap:

    'atom-text-editor':
    +  'enter': 'editor:newline'
    +
    +'atom-text-editor[mini] input':
    +  'enter': 'core:confirm'
    +

    This keymap defines the meaning of Enter in two different contexts. In a normal editor, pressing Enter triggers the editor:newline command, which causes the editor to insert a newline. But if the same keystroke occurs inside a select list's mini-editor, it instead triggers the core:confirm command based on the binding in the more-specific selector.

    By default, keymap.cson is loaded when Pulsar is started. It will always be loaded last, giving you the chance to override bindings that are defined by Pulsar's core keymaps or third-party packages. You can open this file in an editor from the LNX: Edit > Keymap - MAC: Pulsar > Keymap - WIN: File > Keymap menu.

    You can see all the keybindings that are currently configured in your installation of Pulsar in the Keybindings tab in the Settings View.

    If you run into problems with keybindings, the Keybinding Resolver is a huge help. It can be opened with the LNX/WIN: Ctrl+. - MAC: Cmd+. key combination. It will show you what keys Pulsar saw you press and what command Pulsar executed because of that combination.

    Global Configuration Settings

    Pulsar loads configuration settings from the config.cson file in your LNX/MAC: ~/.pulsar - WIN: %USERPROFILE%\.pulsar directory.

    '*':
    +  'core':
    +    'excludeVcsIgnoredPaths': true
    +  'editor':
    +    'fontSize': 18
    +

    The configuration is grouped into global settings under the * key and language-specific settings under scope named keys like .python.source or .html.text. Underneath that, you'll find configuration settings grouped by package name or one of the two core namespaces: core or editor.

    You can open this file in an editor from the LNX: Edit > Config - MAC: Pulsar > Config - WIN: File > Config menu.

    Configuration Key Reference

    • core
      • customFileTypes: Associations of language scope to file extensions (see Customizing Language Recognition)
      • disabledPackages: An array of package names to disable
      • excludeVcsIgnoredPaths: Don't search within files specified by .gitignore
      • ignoredNames: File names to ignore across all of Pulsar
      • projectHome: The directory where projects are assumed to be located
      • themes: An array of theme names to load, in cascading order
    • editor
      • autoIndent: Enable/disable basic auto-indent (defaults to true)
      • nonWordCharacters: A string of non-word characters to define word boundaries
      • fontSize: The editor font size
      • fontFamily: The editor font family
      • invisibles: A hash of characters Pulsar will use to render whitespace characters. Keys are whitespace character types, values are rendered characters (use value false to turn off individual whitespace character types)
        • tab: Hard tab characters
        • cr: Carriage return (for Microsoft-style line endings)
        • eol: \n characters
        • space: Leading and trailing space characters
      • lineHeight: Height of editor lines, as a multiplier of font size
      • preferredLineLength: Identifies the length of a line (defaults to 80)
      • showInvisibles: Whether to render placeholders for invisible characters (defaults to false)
      • showIndentGuide: Show/hide indent indicators within the editor
      • showLineNumbers: Show/hide line numbers within the gutter
      • softWrap: Enable/disable soft wrapping of text within the editor
      • softWrapAtPreferredLineLength: Enable/disable soft line wrapping at preferredLineLength
      • tabLength: Number of spaces within a tab (defaults to 2)
    • fuzzyFinder
      • ignoredNames: Files to ignore only in the fuzzy-finder
    • whitespace
      • ensureSingleTrailingNewline: Whether to reduce multiple newlines to one at the end of files
      • removeTrailingWhitespace: Enable/disable stripping of whitespace at the end of lines (defaults to true)
    • wrap-guide
      • columns: Array of hashes with a pattern and column key to match the path of the current editor to a column position.

    Language Specific Configuration Settings

    You can also set several configuration settings differently for different file types. For example, you may want Pulsar to soft wrap markdown files, have two-space tabs for ruby files, and four-space tabs for python files.

    There are several settings now scoped to an editor's language. Here is the current list:

    editor.autoIndent
    +editor.autoIndentOnPaste
    +editor.invisibles
    +editor.nonWordCharacters
    +editor.preferredLineLength
    +editor.scrollPastEnd
    +editor.showIndentGuide
    +editor.showInvisibles
    +editor.softWrap
    +editor.softWrapAtPreferredLineLength
    +editor.softWrapHangingIndent
    +editor.tabLength
    +

    Language-specific Settings in the Settings View

    You can edit these config settings in the Settings View on a per-language basis. Click on "Packages" tab in the navigation bar on the left, search for the language of your choice, select it, and edit away!

    Python-specific settings

    Language-specific Settings in your Config File

    You can also edit the config.cson directly. To open your configuration file via the Command Palette, press LNX/WIN: Ctrl+Shift+P - MAC: Cmd+Shift+P type open config, and press Enter.

    Global settings are under the * key, and each language can have its own top-level key. This key is the language's scope. Language-specific settings take precedence over anything set in the global section for that language only.

    '*': # all languages unless overridden
    +  'editor':
    +    'softWrap': false
    +    'tabLength': 8
    +
    +'.source.gfm': # markdown overrides
    +  'editor':
    +    'softWrap': true
    +
    +'.source.ruby': # ruby overrides
    +  'editor':
    +    'tabLength': 2
    +
    +'.source.python': # python overrides
    +  'editor':
    +    'tabLength': 4
    +

    Finding a Language's Scope Name

    In order to write these overrides effectively, you'll need to know the scope name for the language. We've already done this for finding a scope for writing a snippet in Snippet Format, but we can quickly cover it again.

    The scope name is shown in the settings view for each language. Click on "Packages" in the navigation on the left, search for the language of your choice, select it, and you should see the scope name under the language name heading:

    Finding a language grammar

    Another way to find the scope for a specific language is to open a file of its kind and:

    • LNX/WIN: Choose "Editor: Log Cursor Scope" in the Command Palette
    • MAC: Press Alt+Cmd+P to show all scopes for the current position of the cursor. The scope mentioned top most is always the language for this kind of file, the scopes following are specific to the cursor position:

    Finding a language grammar with cursor scope

    These scopes can be especially useful to style the editor, since they can also be used as class names in your stylesheet.

    Customizing Language Recognition

    If you want Pulsar to always recognize certain file types as a specific grammar, you'll need to manually edit your config.cson file. You can open it using the Application: Open Your Config command from the Command Palette. For example, if you wanted to add the foo extension to the CoffeeScript language, you could add this to your configuration file under the *.core section:

    '*':
    +  core:
    +    customFileTypes:
    +      'source.coffee': [
    +        'foo'
    +      ]
    +

    In the example above, source.coffee is the language's scope name (see Finding a Language's Scope Name for more information) and foo is the file extension to match without the period. Adding a period to the beginning of either of these will not work.

    Controlling Where Customization is Stored to Simplify Your Workflow

    The CSON configuration files for Pulsar are stored on disk on your machine. The location for this storage is customizable. The default is to use the home directory of the user executing the application. The Pulsar Home directory will, by default, be called .pulsar and will be located in the root of the home directory of the user.

    Custom home location with an environment variable

    An environment variable can be used to make Pulsar use a different location. This can be useful for several reasons. One of these may be that multiple user accounts on a machine want to use the same Pulsar Home. The environment variable used to specify an alternate location is called ATOM_HOME. If this environment variable exists, the location specified will be used to load and store Pulsar settings.

    Taking your customization with you with Portable Mode

    In addition to using the ATOM_HOME environment variable, Pulsar can also be set to use "Portable Mode".

    Portable Mode is most useful for taking Pulsar with you, with all your custom settings and packages, from machine to machine. This may take the form of keeping Pulsar on a USB drive or a cloud storage platform that syncs folders to different machines, like Dropbox. Pulsar is in Portable Mode when there is a directory named .pulsar sibling to the directory in which the pulsar executable file lives. For example, the installed Pulsar directory can be placed into a Dropbox folder next to a .pulsar folder.

    Portable mode directory structure

    With such a setup, Pulsar will use the same Home directory with the same settings for any machine with this directory syncronized/plugged in.

    Moving to Portable Mode

    Pulsar provides a command-line parameter option for setting Portable Mode.

    $ pulsar --portable
    +

    Executing pulsar with the --portable option will take the .pulsar directory you have in the default location (~/.pulsar) and copy the relevant contents for your configuration to a new home directory in the Portable Mode location. This enables easily moving from the default location to a portable operation without losing the customization you have already set up.

    Summary

    At this point you should be something of a Pulsar master user. You should be able to navigate and manipulate your text and files like a wizard. You should also be able to customize Pulsar backwards and forwards to make it look and act just how you want it to.

    In the next section, we're going to kick it up a notch: we'll take a look at changing and adding new functionality to the core of Pulsar itself. We're going to start creating packages for Pulsar. If you can dream it, you can build it.

    + + + diff --git a/docs/launch-manual/sections/using-pulsar/sections/autocomplete.html b/docs/launch-manual/sections/using-pulsar/sections/autocomplete.html new file mode 100644 index 0000000000..54d5b7e90c --- /dev/null +++ b/docs/launch-manual/sections/using-pulsar/sections/autocomplete.html @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + +

    Less than 1 minute

    Autocomplete

    If you're still looking to save some typing time, Pulsar also ships with simple autocompletion functionality.

    The autocomplete system lets you view and insert possible completions in the editor using Tab or Enter.

    Autocomplete menu

    By default, the autocomplete system will look through the current open file for strings that match what you're starting to type.

    If you want more options, in the Settings panel for the autocomplete-plus package you can toggle a setting to make autocomplete-plus look for text in all your open buffers rather than just the current file.

    The Autocomplete functionality is implemented in the autocomplete-plusopen in new window package.

    + + + diff --git a/docs/launch-manual/sections/using-pulsar/sections/basic-customization.html b/docs/launch-manual/sections/using-pulsar/sections/basic-customization.html new file mode 100644 index 0000000000..60ecc2d21a --- /dev/null +++ b/docs/launch-manual/sections/using-pulsar/sections/basic-customization.html @@ -0,0 +1,293 @@ + + + + + + + + + + + + + + +

    About 7 min

    Basic Customization

    Now that we are feeling comfortable with just about everything built into Pulsar, let's look at how to tweak it. Perhaps there is a keybinding that you use a lot but feels wrong or a color that isn't quite right for you. Pulsar is amazingly flexible, so let's go over some of the simpler flexes it can do.

    Configuring with CSON

    All of Pulsar's config files (with the exception of your style sheet and your Init Script are written in CSON, short for CoffeeScript Object Notationopen in new window. Just like its namesake JSON, JavaScript Object Notationopen in new window, CSON is a text format for storing structured data in the form of simple objects made up of key-value pairs.

    key:
    +  key: value
    +  key: value
    +  key: [value, value]
    +

    Objects are the backbone of any CSON file, and are delineated by indentation (as in the above example). A key's value can either be a String, a Number, an Object, a Boolean, null, or an Array of any of these data types.

    Warning

    Just like the more common JSON, CSON's keys can only be repeated once per object. If there are duplicate keys, then the last usage of that key overwrites all others, as if they weren't there. The same holds true for Pulsar's config files.

    Don't do this:

    # Only the second snippet will be loaded
    +'.source.js':
    +  'console.log':
    +    'prefix': 'log'
    +    'body': 'console.log(${1:"crash"});$2'
    +'.source.js':
    +  'console.error':
    +    'prefix': 'error'
    +    'body': 'console.error(${1:"crash"});$2'
    +

    Use this instead:

    # Both snippets will be loaded
    +'.source.js':
    +  'console.log':
    +    'prefix': 'log'
    +    'body': 'console.log(${1:"crash"});$2'
    +  'console.error':
    +    'prefix': 'error'
    +    'body': 'console.error(${1:"crash"});$2'
    +

    Style Tweaks

    If you want to apply quick-and-dirty personal styling changes without creating an entire theme that you intend to publish, you can add styles to the styles.less file in your LNX/MAC: ~/.pulsar - WIN: %USERPROFILE%\.pulsar directory. You can open this file in an editor from the LNX: Edit > Stylesheet - MAC: Pulsar > Stylesheet - WIN: File > Stylesheet menu.

    For example, to change the colors of the Status Bar, you could add the following rule to your styles.less file:

    .status-bar {
    +	color: white;
    +	background-color: black;
    +}
    +

    The easiest way to see what classes are available to style is to inspect the DOM manually via the Developer Tools. We'll go over the Developer Tools in great detail in the next chapter, but for now let's take a simple look. You can open the Developer Tools by pressing LNX/WIN: Ctrl+Shift+I - MAC: Alt+Cmd+I, which will bring up the Chromium Developer Tools panel.

    Developer Tools

    With the Developer Tools, you can inspect all the elements in Pulsar. If you want to update the style of something, you can figure out what classes it has and add a Less rule to your stylesheet to modify it.

    Tip

    If you are unfamiliar with Less, it is a basic CSS preprocessor that makes some things in CSS a bit easier. You can learn more about it at lesscss.orgopen in new window.

    If you prefer to use CSS instead, you can do that in the same styles.less file, since CSS is also valid in Less.

    Customizing Keybindings

    Pulsar keymaps work similarly to stylesheets. Just as stylesheets use selectors to apply styles to elements, Pulsar keymaps use selectors to associate key combinations with events in specific contexts. Here's a small example, excerpted from Pulsar's built-in keymap:

    'atom-text-editor':
    +  'enter': 'editor:newline'
    +
    +'atom-text-editor[mini] input':
    +  'enter': 'core:confirm'
    +

    This keymap defines the meaning of Enter in two different contexts. In a normal editor, pressing Enter triggers the editor:newline command, which causes the editor to insert a newline. But if the same keystroke occurs inside a select list's mini-editor, it instead triggers the core:confirm command based on the binding in the more-specific selector.

    By default, keymap.cson is loaded when Pulsar is started. It will always be loaded last, giving you the chance to override bindings that are defined by Pulsar's core keymaps or third-party packages. You can open this file in an editor from the LNX: Edit > Keymap - MAC: Pulsar > Keymap - WIN: File > Keymap menu.

    You can see all the keybindings that are currently configured in your installation of Pulsar in the Keybindings tab in the Settings View.

    If you run into problems with keybindings, the Keybinding Resolver is a huge help. It can be opened with the LNX/WIN: Ctrl+. - MAC: Cmd+. key combination. It will show you what keys Pulsar saw you press and what command Pulsar executed because of that combination.

    Global Configuration Settings

    Pulsar loads configuration settings from the config.cson file in your LNX/MAC: ~/.pulsar - WIN: %USERPROFILE%\.pulsar directory.

    '*':
    +  'core':
    +    'excludeVcsIgnoredPaths': true
    +  'editor':
    +    'fontSize': 18
    +

    The configuration is grouped into global settings under the * key and language-specific settings under scope named keys like .python.source or .html.text. Underneath that, you'll find configuration settings grouped by package name or one of the two core namespaces: core or editor.

    You can open this file in an editor from the LNX: Edit > Config - MAC: Pulsar > Config - WIN: File > Config menu.

    Configuration Key Reference

    • core
      • customFileTypes: Associations of language scope to file extensions (see Customizing Language Recognition)
      • disabledPackages: An array of package names to disable
      • excludeVcsIgnoredPaths: Don't search within files specified by .gitignore
      • ignoredNames: File names to ignore across all of Pulsar
      • projectHome: The directory where projects are assumed to be located
      • themes: An array of theme names to load, in cascading order
    • editor
      • autoIndent: Enable/disable basic auto-indent (defaults to true)
      • nonWordCharacters: A string of non-word characters to define word boundaries
      • fontSize: The editor font size
      • fontFamily: The editor font family
      • invisibles: A hash of characters Pulsar will use to render whitespace characters. Keys are whitespace character types, values are rendered characters (use value false to turn off individual whitespace character types)
        • tab: Hard tab characters
        • cr: Carriage return (for Microsoft-style line endings)
        • eol: \n characters
        • space: Leading and trailing space characters
      • lineHeight: Height of editor lines, as a multiplier of font size
      • preferredLineLength: Identifies the length of a line (defaults to 80)
      • showInvisibles: Whether to render placeholders for invisible characters (defaults to false)
      • showIndentGuide: Show/hide indent indicators within the editor
      • showLineNumbers: Show/hide line numbers within the gutter
      • softWrap: Enable/disable soft wrapping of text within the editor
      • softWrapAtPreferredLineLength: Enable/disable soft line wrapping at preferredLineLength
      • tabLength: Number of spaces within a tab (defaults to 2)
    • fuzzyFinder
      • ignoredNames: Files to ignore only in the fuzzy-finder
    • whitespace
      • ensureSingleTrailingNewline: Whether to reduce multiple newlines to one at the end of files
      • removeTrailingWhitespace: Enable/disable stripping of whitespace at the end of lines (defaults to true)
    • wrap-guide
      • columns: Array of hashes with a pattern and column key to match the path of the current editor to a column position.

    Language Specific Configuration Settings

    You can also set several configuration settings differently for different file types. For example, you may want Pulsar to soft wrap markdown files, have two-space tabs for ruby files, and four-space tabs for python files.

    There are several settings now scoped to an editor's language. Here is the current list:

    editor.autoIndent
    +editor.autoIndentOnPaste
    +editor.invisibles
    +editor.nonWordCharacters
    +editor.preferredLineLength
    +editor.scrollPastEnd
    +editor.showIndentGuide
    +editor.showInvisibles
    +editor.softWrap
    +editor.softWrapAtPreferredLineLength
    +editor.softWrapHangingIndent
    +editor.tabLength
    +

    Language-specific Settings in the Settings View

    You can edit these config settings in the Settings View on a per-language basis. Click on "Packages" tab in the navigation bar on the left, search for the language of your choice, select it, and edit away!

    Python-specific settings

    Language-specific Settings in your Config File

    You can also edit the config.cson directly. To open your configuration file via the Command Palette, press LNX/WIN: Ctrl+Shift+P - MAC: Cmd+Shift+P type open config, and press Enter.

    Global settings are under the * key, and each language can have its own top-level key. This key is the language's scope. Language-specific settings take precedence over anything set in the global section for that language only.

    '*': # all languages unless overridden
    +  'editor':
    +    'softWrap': false
    +    'tabLength': 8
    +
    +'.source.gfm': # markdown overrides
    +  'editor':
    +    'softWrap': true
    +
    +'.source.ruby': # ruby overrides
    +  'editor':
    +    'tabLength': 2
    +
    +'.source.python': # python overrides
    +  'editor':
    +    'tabLength': 4
    +

    Finding a Language's Scope Name

    In order to write these overrides effectively, you'll need to know the scope name for the language. We've already done this for finding a scope for writing a snippet in Snippet Format, but we can quickly cover it again.

    The scope name is shown in the settings view for each language. Click on "Packages" in the navigation on the left, search for the language of your choice, select it, and you should see the scope name under the language name heading:

    Finding a language grammar

    Another way to find the scope for a specific language is to open a file of its kind and:

    • LNX/WIN: Choose "Editor: Log Cursor Scope" in the Command Palette
    • MAC: Press Alt+Cmd+P to show all scopes for the current position of the cursor. The scope mentioned top most is always the language for this kind of file, the scopes following are specific to the cursor position:

    Finding a language grammar with cursor scope

    These scopes can be especially useful to style the editor, since they can also be used as class names in your stylesheet.

    Customizing Language Recognition

    If you want Pulsar to always recognize certain file types as a specific grammar, you'll need to manually edit your config.cson file. You can open it using the Application: Open Your Config command from the Command Palette. For example, if you wanted to add the foo extension to the CoffeeScript language, you could add this to your configuration file under the *.core section:

    '*':
    +  core:
    +    customFileTypes:
    +      'source.coffee': [
    +        'foo'
    +      ]
    +

    In the example above, source.coffee is the language's scope name (see Finding a Language's Scope Name for more information) and foo is the file extension to match without the period. Adding a period to the beginning of either of these will not work.

    Controlling Where Customization is Stored to Simplify Your Workflow

    The CSON configuration files for Pulsar are stored on disk on your machine. The location for this storage is customizable. The default is to use the home directory of the user executing the application. The Pulsar Home directory will, by default, be called .pulsar and will be located in the root of the home directory of the user.

    Custom home location with an environment variable

    An environment variable can be used to make Pulsar use a different location. This can be useful for several reasons. One of these may be that multiple user accounts on a machine want to use the same Pulsar Home. The environment variable used to specify an alternate location is called ATOM_HOME. If this environment variable exists, the location specified will be used to load and store Pulsar settings.

    Taking your customization with you with Portable Mode

    In addition to using the ATOM_HOME environment variable, Pulsar can also be set to use "Portable Mode".

    Portable Mode is most useful for taking Pulsar with you, with all your custom settings and packages, from machine to machine. This may take the form of keeping Pulsar on a USB drive or a cloud storage platform that syncs folders to different machines, like Dropbox. Pulsar is in Portable Mode when there is a directory named .pulsar sibling to the directory in which the pulsar executable file lives. For example, the installed Pulsar directory can be placed into a Dropbox folder next to a .pulsar folder.

    Portable mode directory structure

    With such a setup, Pulsar will use the same Home directory with the same settings for any machine with this directory syncronized/plugged in.

    Moving to Portable Mode

    Pulsar provides a command-line parameter option for setting Portable Mode.

    $ pulsar --portable
    +

    Executing pulsar with the --portable option will take the .pulsar directory you have in the default location (~/.pulsar) and copy the relevant contents for your configuration to a new home directory in the Portable Mode location. This enables easily moving from the default location to a portable operation without losing the customization you have already set up.

    + + + diff --git a/docs/launch-manual/sections/using-pulsar/sections/editing-and-deleting-text.html b/docs/launch-manual/sections/using-pulsar/sections/editing-and-deleting-text.html new file mode 100644 index 0000000000..cb9af881b6 --- /dev/null +++ b/docs/launch-manual/sections/using-pulsar/sections/editing-and-deleting-text.html @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + +

    About 5 min

    Editing and Deleting Text

    So far we've looked at a number of ways to move around and select regions of a file, so now let's actually change some of that text. Obviously you can type in order to insert characters, but there are also a number of ways to delete and manipulate text that could come in handy.

    Basic Manipulation

    There are a handful of cool keybindings for basic text manipulation that might come in handy. These range from moving around lines of text and duplicating lines to changing the case.

    Pulsar also has built in functionality to re-flow a paragraph to hard-wrap at a given maximum line length. You can format the current selection to have lines no longer than 80 (or whatever number editor.preferredLineLength is set to) characters using LNX/WIN: Ctrl+Shift+Q - MAC: Alt+Cmd+Q. If nothing is selected, the current paragraph will be reflowed.

    Deleting and Cutting

    You can also delete or cut text out of your buffer with some shortcuts. Be ruthless.

    Multiple Cursors and Selections

    One of the cool things that Pulsar can do out of the box is support multiple cursors. This can be incredibly helpful in manipulating long lists of text.

    Using these commands you can place cursors in multiple places in your document and effectively execute the same commands in multiple places at once.

    Using multiple cursors

    This can be incredibly helpful in doing many type of repetitive tasks such as renaming variables or changing the format of some text. You can use this with almost any package or command - for example, changing case and moving or duplicating lines.

    You can also use the mouse to select text with the LNX/WIN: Ctrl - MAC: Cmd key pressed down to select multiple regions of your text simultaneously.

    Return to a single cursor with Esc or by clicking anywhere in the file to position a cursor there as normal.

    Whitespace

    Pulsar comes with several commands to help you manage the whitespace in your document. One very useful pair of commands converts leading spaces into tabs and converts leading tabs into spaces. If you're working with a document that has mixed whitespace, these commands are great for helping to normalize the file. There are no keybindings for the whitespace commands, so you will have to search your command palette for "Convert Spaces to Tabs" (or vice versa) to run one of these commands.

    The whitespace commands are implemented in the whitespaceopen in new window package. The settings for the whitespace commands are managed on the page for the whitespace package.

    Managing your whitespace settings

    Note

    The "Remove Trailing Whitespace" option is on by default. This means that every time you save any file opened in Pulsar, it will strip all trailing whitespace from the file. If you want to disable this, go to the whitespace package in your settings panel and uncheck that option.

    Pulsar will also by default ensure that your file has a trailing newline. You can also disable this option on that screen.

    Brackets

    Pulsar ships with intelligent and easy to use bracket handling.

    It will by default highlight [], (), and {} style brackets when your cursor is over them. It will also highlight matching XML and HTML tags.

    Pulsar will also automatically autocomplete [], (), and {}, "", '', “”, ‘’, «», ‹›, and `` when you type the leading one. If you have a selection and you type any of these opening brackets or quotes, Pulsar will enclose the selection with the opening and closing brackets or quotes.

    There are a few other interesting bracket related commands that you can use.

    The brackets functionality is implemented in the bracket-matcheropen in new window package. Like all of these packages, to change defaults related to bracket handling, or to disable it entirely, you can navigate to this package in the Settings view.

    Encoding

    Pulsar also ships with some basic file encoding support should you find yourself working with non-UTF-8 encoded files, or should you wish to create one.

    If you pull up the file encoding dialog, you can choose an alternate file encoding to save your file in.

    When you open a file, Pulsar will try to auto-detect the encoding. If Pulsar can't identify the encoding, the encoding will default to UTF-8, which is also the default encoding for new files.

    Changing your file encoding

    If you pull up the encoding menu and change the active encoding to something else, the file will be written out in that encoding the next time you save the file.

    The encoding selector is implemented in the encoding-selectoropen in new window package.

    + + + diff --git a/docs/launch-manual/sections/using-pulsar/sections/find-and-replace.html b/docs/launch-manual/sections/using-pulsar/sections/find-and-replace.html new file mode 100644 index 0000000000..74be1aae25 --- /dev/null +++ b/docs/launch-manual/sections/using-pulsar/sections/find-and-replace.html @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + +

    About 2 min

    Find and Replace

    Finding and replacing text in your file or project is quick and easy in Pulsar.

    If you launch either of those commands, you'll be greeted with the Find and Replace panel at the bottom of your screen.

    Find and replace text in the current file

    To search within your current file you can press LNX/WIN: Cmd+F - MAC: Ctrl+F, type in a search string and press LNX/WIN/MAC: Enter or LNX/WINF3 - MAC: Cmd+G or the "Find Next" button) multiple times to cycle through all the matches in that file. Alt+Enter will find all occurrences of the search string. The Find and Replace panel also contains buttons for toggling case sensitivity, performing regular expression matching, scoping the search to selections, and performing whole word search.

    If you type a string in the replacement text box, you can replace matches with a different string. For example, if you wanted to replace every instance of the string "Atom" with the string "Pulsar", you would enter those values in the two text boxes and press the "Replace All" button to perform the replacements.

    Note

    Note: Pulsar uses JavaScript regular expressions to perform regular expression searches.

    When doing a regular expression search, the replacement syntax to refer back to search groups is $1, $2, … $&. Refer to JavaScript's guide to regular expressionsopen in new window to learn more about regular expression syntax you can use in Pulsar.

    You can also find and replace throughout your entire project if you invoke the panel with LNX/WIN: Ctrl+Shift+F - MAC: Cmd+Shift+F.

    Find and replace text in your project

    This is a great way to find out where in your project a function is called, an anchor is linked to or a specific misspelling is located. Click on the matching line to jump to that location in that file.

    You can limit a search to a subset of the files in your project by entering a glob patternopen in new window into the "File/Directory pattern" text box. For example, the pattern src/*.js would restrict the search to JavaScript files in the src directory. The "globstar" pattern (**) can be used to match arbitrarily many subdirectories. For example, docs/**/*.md will match docs/a/foo.md, docs/a/b/foo.md, etc. You can enter multiple glob patterns separated by commas, which is useful for searching in multiple file types or subdirectories.

    When you have multiple project folders open, this feature can also be used to search in only one of those folders. For example, if you had the folders /path1/folder1 and /path2/folder2 open, you could enter a pattern starting with folder1 to search only in the first folder.

    Press Esc while focused on the Find and Replace panel to clear the pane from your workspace.

    The Find and Replace functionality is implemented in the find-and-replaceopen in new window package and uses the scandalopen in new window Node module to do the actual searching.

    + + + diff --git a/docs/launch-manual/sections/using-pulsar/sections/folding.html b/docs/launch-manual/sections/using-pulsar/sections/folding.html new file mode 100644 index 0000000000..cf7f0e5a82 --- /dev/null +++ b/docs/launch-manual/sections/using-pulsar/sections/folding.html @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + +

    About 1 min

    Folding

    If you want to see an overview of the structure of the code file you're working on, folding can be a helpful tool. Folding hides blocks of code such as functions or looping blocks in order to simplify what is on your screen.

    + + + diff --git a/docs/launch-manual/sections/using-pulsar/sections/github-package.html b/docs/launch-manual/sections/using-pulsar/sections/github-package.html new file mode 100644 index 0000000000..dc5b0da291 --- /dev/null +++ b/docs/launch-manual/sections/using-pulsar/sections/github-package.html @@ -0,0 +1,224 @@ + + + + + + + + + + + + + + +

    About 6 min

    GitHub package

    The GitHub package brings Git and GitHub integration right inside Pulsar.

    Most of the functionality lives within the Git and GitHub dock items.

    The Git and GitHub panels

    There are different ways to access them, probably the most common way is through their keybindings:

    • Open the Git panel: Ctrl+9
    • Open the GitHub panel: Ctrl+8

    Another way is from the menu: Packages -> GitHub -> Toggle Git Tab and Toggle GitHub Tab

    Or you can also toggle the Git panel from the Status Bar by clicking on the changed files icon:

    Open Git panel


    Initialize repositories

    In case a project doesn't have a Git repository yet, you can create one from the Git panel.

    Initialize repositories

    Clone repositories

    To clone a repository, open the GitHub panel while you have no project folders open in Pulsar and click "Clone an existing GitHub repository". In the dialog, paste the URL of a repository and click "Clone". The new project will be added to the Tree View.

    GitHub panel

    Clone dialog

    Alternately, run the GitHub: Clone command to open the Clone dialog any time.

    Branch

    To open the branch tooltip, click the branch icon in the Status Bar. From there you can create or switch branches.

    Create or switch branches

    Stage

    After making some changes, stage anything you want to be part of the next commit. Choose between staging...

    • All changes: Click the "Stage All" button in the "Unstaged Changes" bar.
    • Files: Double-click a file or select a file and press Enter.
    • Hunk: Click on the "Stage Hunk" button or select a hunk and press Enter.
    • Lines: Click on a line (or drag on multiple lines) to select, then click on the "Stage Selection" button. Or use the LNX/WIN: Ctrl+/ - MAC: Cmd-/ key to toggle from hunk mode to line mode, then press LNX/WIN: Ctrl-Enter - MAC: Cmd-Enter to stage just a single line.

    Use the LNX/WIN: Cmd-Left - MAC: Ctrl-Left or LNX/WIN: Ctrl-Right - MAC: Cmd-Right arrow key to switch between file list and the diff view. Unstaging can be done in the same way.

    Stage changes

    Discard changes

    If you no longer want to keep some changes, you can discard them. It's similar to staging, but accessible behind a context menu.

    • All changes: Click the ... menu in the "Unstaged Changes" header and choose "Discard All Changes".
    • Files: Right-click a file (or multiple) and choose "Discard Changes".
    • Hunk: Click on the trash icon in the top bar of a hunk.
    • Lines: Right-click on a line (or multiple) and choose "Discard Selection".

    Discard changes

    Commit Preview

    To double check all changes that are going into your next commit, click the "See All Staged Changes" button above the commit message box. It lets you see all of your staged changes in a single pane. This "commit preview" can also serve as an inspiration for writing the commit message.

    Commit Preview

    Commit

    Once you've staged your changes, enter a commit message. Feel free to describe the commit in more detail after leaving an empty line. Finalize by clicking the Commit button. If you need more space, click the expand icon at the bottom right. It will open a commit editor in the center.

    Commit changes

    To add multiple co-authors to a commit, click the "👤➕" icon in the bottom left corner of the commit message editor. Now you can search by name, email or GitHub username to give credit to a co-author.

    Commit with co-authors

    Amend and undo

    In case you forgot to commit a change and would like to add it to your previous commit, right-click on the last commit, then choose "Amend" from the context menu.

    Amend previous commit

    If you want to edit the commit message of your last commit, or add/remove changes, click on the "Undo" button. It will roll back to the state just before you clicked on the commit button.

    Undo previous commit

    View commits

    Once you've made some commits, click on a commit message in the recent commit list to see the full diff and commit message associated with each:

    View commit detai

    Publish and push

    When you're ready to share your changes with your team members, click the Publish button in the Status Bar. It will push your local branch to the remote repository. After making more commits, you can Push them as well from the Status Bar.

    Publish and push commits

    Fetch and pull

    From time to time it's a good idea to click on the Fetch button to see if any other team member pushed changes. If so, click on Pull to merge the changes into your local branch.

    Fetch and pull commits

    If you prefer to rebase when pulling, you can configure Git to make it the default behavior:

    git config --global --bool pull.rebase true
    +

    Learn more about merge vs. rebaseopen in new window.

    Resolve conflicts

    Sometimes there can be conflicts when trying to merge. Files that have merge conflicts will show up in the "Merge Conflicts" list. Click on a file to open the editor. There you can resolve the conflict by picking a version or make further edits. Once done, stage the file and commit.

    Resolve conflicts

    Create a Pull Request

    When your changes are ready to be reviewed by your team members, open the "GitHub" panel Ctrl+8 and click on Open new pull request. It will open the browser where you can continue creating a pull request. If commits haven't been pushed or the branch isn't published yet, the GitHub package will do that automatically for you.

    Create a Pull Request

    View Pull Requests

    Once the pull request is created, it will appear under Current pull request at the top of the panel. Underneath is a list of Open pull requests. It lets you quickly find a pull request by avatar, title or PR number. It also lets you keep an eye on the CI status. Clicking on a pull request in the list opens a center pane with more details, the timeline and conversations.

    View Pull Requests

    Open any Issue or Pull Request

    You can open issues or pull requests from any repo on GitHub. To do so, run the GitHub: Open Issue Or Pull Request command and paste the URL from an issue or pull request. Then press the Open Issue or Pull Request button and it will open a center pane. This lets you keep an issue or pull request as a reference, when working in another repo.

    Open Issue or Pull Request

    Checkout a Pull Request

    To test a pull request locally, open it in the workspace center by clicking on the pull request in the "open pull requests" list from the GitHub tab, then click on the Checkout button. It will automatically create a local branch and pull all the changes. If you would like to contribute to that pull request, start making changes, commit and push. Your contribution is now part of that pull request.

    Checkout a pull request

    View Pull Request review comments

    To view review comments on a Pull Request, open the Reviews Tab from the See Reviews button from the footer of a Pull Request Pane. Alternatively, if the pull request has already been checked out, Reviews Tab can also be open from the same button on GitHub Tab.

    Open review tab from footer

    You can see all the review summaries and comments of a pull request in the Reviews Tab. The comment section has a progress bar to help you keep track of how close are you to finish addressing the Pull Request comments (i.e. marking all comment threads on a Pull Request as "resolved"). Comment threads are greyed out after they have been resolved.

    Review tab

    After the pull request branch has been checked out, you can click Jump To File to open the commented on file and make changes as per the review comment right in the editor. If you would like to get the full context of the review comment, click Open Diff to open the diff view with line highlighting.

    Jump to file from review tab

    Conversely, in-editor comments are indicated by the comment icon in the gutter. Clicking the icon, either from within the editor or the diff view, will take you back to the Reviews Tab.

    Open review tab from diff

    Respond to a Pull Request review comment

    To respond to a Pull Request review comment, type your message and click Comment; a single line comment will be created in the same thread as the comment you responded to. After addressing a Pull Request review comment, click Resolve conversation to mark the whole thread as "resolved". The progress bar in the "Comments" section will update accordingly.

    Respond to a Pull Request review comment

    + + + diff --git a/docs/launch-manual/sections/using-pulsar/sections/grammar.html b/docs/launch-manual/sections/using-pulsar/sections/grammar.html new file mode 100644 index 0000000000..64f24783fd --- /dev/null +++ b/docs/launch-manual/sections/using-pulsar/sections/grammar.html @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + +

    Less than 1 minute

    Grammar

    The "grammar" of a file is what language Pulsar has associated with that file. Types of grammars would include "Java" or "GitHub-Flavored Markdown". We looked at this a bit when we created some snippets in Snippets.

    When you load a file, Pulsar does a little work to try to figure out what type of file it is. Largely this is accomplished by looking at its file extension (.md is generally a Markdown file, etc.), though sometimes it has to inspect the content a bit to figure it out.

    When you open a file and Pulsar can't determine a grammar for the file, it will default to "Plain Text", which is the simplest one. If it does default to "Plain Text", picks the wrong grammar for the file, or if for any reason you wish to change the selected grammar, you can pull up the Grammar Selector with Ctrl+Shift+L.

    Grammar Selector

    When the grammar of a file is changed, Atom will remember that for the current session.

    The Grammar Selector functionality is implemented in the grammar-selectoropen in new window package.

    + + + diff --git a/docs/launch-manual/sections/using-pulsar/sections/moving-in-pulsar.html b/docs/launch-manual/sections/using-pulsar/sections/moving-in-pulsar.html new file mode 100644 index 0000000000..2f96228a07 --- /dev/null +++ b/docs/launch-manual/sections/using-pulsar/sections/moving-in-pulsar.html @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + +

    About 5 min

    Moving in Pulsar

    While it's pretty easy to move around Pulsar by clicking with the mouse or using the arrow keys, there are some keybindings that may help you keep your hands on the keyboard and navigate around a little faster.

    You can also move directly to a specific line (and column) number with Ctrl+G. This will bring up a dialog that asks which line you would like to jump to. You can also use the row:column syntax to jump to a character in that line as well.

    Go directly to a line

    Additional Movement and Selection Commands

    Pulsar also has a few movement and selection commands that don't have keybindings by default. You can access these commands from the Command Palette, but if you find yourself using commands that don't have a keybinding often, have no fear! You can easily add an entry to your keymap.cson to create a key combination. You can open keymap.cson file in an editor from the LNX: Edit > Keymap - MAC: Pulsar > Keymap - WIN: File > Keymap menu. For example, the command editor:move-to-beginning-of-screen-line is available in the command palette, but it's not bound to any key combination. To create a key combination you need to add an entry in your keymap.cson file. For editor:select-to-previous-word-boundary, you can add the following to your keymap.cson:

    This will bind the command editor:select-to-previous-word-boundary to LNX/WIN: Ctrl+Shift+E - MAC: Cmd+Shift+E. For more information on customizing your keybindings, see Customizing Keybindings.

    Here's a list of Movement and Selection Commands that do not have a keyboard shortcut by default:

    You can also jump around a little more informatively with the Symbols View. To jump to a symbol such as a method definition, press LNX/WIN: Ctrl+R - MAC: Cmd+R. This opens a list of all symbols in the current file, which you can fuzzy filter similarly to LNX/WIN: Ctrl+T - MAC: Cmd+T. You can also search for symbols across your project but it requires a tags file.

    Search by symbol across your project

    You can generate a tags file by using the ctags utilityopen in new window. Once it is installed, you can use it to generate a tags file by running a command to generate it. See the ctags documentationopen in new window for details.

    You can customize how tags are generated by creating your own .ctags file in your home directory, LNX/MAC: ~/.ctags - WIN: %USERPROFILE%\.ctags. An example can be found hereopen in new window.

    The symbols navigation functionality is implemented in the symbols-viewopen in new window package.

    Bookmarks

    Pulsar also has a great way to bookmark specific lines in your project so you can jump back to them quickly.

    If you press LNX/WIN: Alt+Ctrl+F2 - MAC Cmd+F2, Pulsar will toggle a "bookmark" on the current line. You can set these throughout your project and use them to quickly find and jump to important lines of your project. A small bookmark symbol is added to the line gutter, like on line 22 of the image below.

    If you hit F2, Pulsar will jump to the next bookmark in the file you currently have focused. If you use Shift+F2 it will cycle backwards through them instead.

    You can also see a list of all your project's current bookmarks and quickly filter them and jump to any of them by hitting Ctrl+F2.

    View and filter bookmarks

    The bookmarks functionality is implemented in the bookmarksopen in new window package.

    + + + diff --git a/docs/launch-manual/sections/using-pulsar/sections/panes.html b/docs/launch-manual/sections/using-pulsar/sections/panes.html new file mode 100644 index 0000000000..2a2e2a9993 --- /dev/null +++ b/docs/launch-manual/sections/using-pulsar/sections/panes.html @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + +

    About 2 min

    Panes

    Tip

    If you don't like using tabs, you don't have to. You can disable the tabs packageopen in new window and each pane will still support multiple pane items. You just won't have tabs to use to click between them.

    + + + diff --git a/docs/launch-manual/sections/using-pulsar/sections/pending-pane-items.html b/docs/launch-manual/sections/using-pulsar/sections/pending-pane-items.html new file mode 100644 index 0000000000..f6627ba40a --- /dev/null +++ b/docs/launch-manual/sections/using-pulsar/sections/pending-pane-items.html @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + +

    Less than 1 minute

    Pending Pane Items

    "Pending Pane Items" were formerly referred to as "Preview Tabs"

    When you open a new file by single-clicking in the Tree View, it will open in a new tab with an italic title. This indicates that the file is "pending". When a file is pending, it will be replaced by the next pending file that is opened. This allows you to click through a bunch of files to find something without having to go back and close them all.

    You can confirm a pending file by doing any of the following:

    • Double-clicking the tab of the file
    • Double-clicking the file in the tree view
    • Editing the contents of the file
    • Saving the file

    You can also open a file already confirmed by double-clicking it in the tree view instead of single-clicking it.

    Disabling Pending Pane Items

    Allow Pending Pane Items setting

    If you would prefer to not have files open in pending form, you can disable this behavior by unchecking "Allow Pending Pane Items" in the Core Settings section of the Settings View. With pending pane items disabled, single-clicking a file in the Tree View will select the file but not open it. You will have to double-click the file to open it.

    + + + diff --git a/docs/launch-manual/sections/using-pulsar/sections/pulsar-packages.html b/docs/launch-manual/sections/using-pulsar/sections/pulsar-packages.html new file mode 100644 index 0000000000..93cd991786 --- /dev/null +++ b/docs/launch-manual/sections/using-pulsar/sections/pulsar-packages.html @@ -0,0 +1,243 @@ + + + + + + + + + + + + + + +

    About 4 min

    Pulsar Packages

    First we'll start with the Pulsar package system. As we mentioned previously, Pulsar itself is a very basic core of functionality that ships with a number of useful packages that add new features like the Tree Viewopen in new window and the Settings Viewopen in new window.

    In fact, there are more than 80 packages that comprise all of the functionality that is available in Pulsar by default. For example, the Welcome screenopen in new window that you see when you first start Pulsar, the spell checkeropen in new window, the themesopen in new window and the Fuzzy Finderopen in new window are all packages that are separately maintained and all use the same APIs that you have access to, as we'll see in great detail in Hacking the Core.

    This means that packages can be incredibly powerful and can change everything from the very look and feel of the entire interface to the basic operation of even core functionality.

    In order to install a new package, you can use the Install tab in the now familiar Settings View. Open up the Settings View using LNX/WIN: Ctrl+, - MAC: Cmd+, click on the "Install" tab and type your search query into the box under Install Packages.

    The packages listed here have been published to https://web.pulsar-edit.devopen in new window which is the official registry for Pulsar packages. Searching on the Settings View will go to the Pulsar package registry and pull in anything that matches your search terms.

    Package install screen

    All of the packages will come up with an "Install" button. Clicking that will download the package and install it. Your editor will now have the functionality that the package provides.

    Package Settings

    Once a package is installed in Pulsar, it will show up in the Settings View under the "Packages" tab, along with all the pre-installed packages that come with Pulsar. To filter the list in order to find one, you can type into search box directly under the "Installed Packages" heading.

    Package settings screen

    Clicking on the "Settings" button for a package will give you the settings screen for that package specifically. Here you have the option of changing some of the default variables for the package, seeing what all the command keybindings are, disabling the package temporarily, looking at the source code, seeing the current version of the package, reporting issues and uninstalling the package.

    If a new version of any of your packages is released, Pulsar will automatically detect it and you can upgrade the package from either this screen or from the "Updates" tab. This helps you easily keep all your installed packages up to date.

    Pulsar Themes

    You can also find and install new themes for Pulsar from the Settings View. These can be either UI themes or syntax themes and you can search for them from the "Install" tab, just like searching for new packages. Make sure to press the "Themes" toggle next to the search box.

    Theme search screen

    Clicking on the theme title will take you to a profile page for the theme on pulsar-edit.dev, which often has a screenshot of the theme. This way you can see what it looks like before installing it.

    Clicking on "Install" will install the theme and make it available in the Theme dropdowns as we saw in Changing the Theme.

    Example of the Unity UI theme with Monokai syntax theme

    Command Line

    You can also install packages or themes from the command line using ppm (Pulsar Package Manager). This is used by running pulsar -p <commmand> or pulsar --package <command>.

    Tip

    Check that you have ppm available by running the following command in your terminal:

    $ pulsar -p help install
    +

    You should see a message print out with details about the pulsar -p install command.

    If you do not, see the Installing Pulsar section for instructions on how to install the pulsar command for your system.

    You can install packages by using the pulsar -p install command:

    • pulsar -p install <package_name> to install the latest version.
    • pulsar -p install <package_name>@<package_version> to install a specific version.

    For example pulsar -p install minimap@4.40.0 installs the 4.40.0 release of the minimapopen in new window package.

    You can also use ppm to find new packages to install. If you run pulsar -p search, you can search the package registry for a search term.

    $ pulsar -p search linter
    +> Search Results For 'linter' (30)
    +> ├── linter A Base Linter with Cow Powers (9863242 downloads, 4757 stars)
    +> ├── linter-ui-default Default UI for the Linter package (7755748 downloads, 1201 stars)
    +> ├── linter-eslint Lint JavaScript on the fly, using ESLint (v7 or older) (2418043 downloads, 1660 stars)
    +> ├── linter-jshint Linter plugin for JavaScript, using jshint (1202044 downloads, 1271 stars)
    +> ├── linter-gcc Lint C and C++ source files using gcc / g++ (863989 downloads, 194 stars)
    +> ...
    +> ├── linter-shellcheck Lint Bash on the fly, using shellcheck (136938 downloads, 280 stars)
    +> └── linter-rust Lint Rust-files, using rustc and/or cargo (132550 downloads, 91 stars)
    +

    You can use pulsar -p view to see more information about a specific package.

    $ pulsar -p view linter
    +> linter
    +> ├── 3.4.0
    +> ├── https://github.com/steelbrain/linter
    +> ├── A Base Linter with Cow Powers
    +> ├── 9863242 downloads
    +> └── 4757 stars
    +>
    +> Run `pulsar -p install linter` to install this package.
    +

    Using ppm to install from other sources

    By default ppm will be using the Pulsar Package Repositoryopen in new window. However you can also use it to install from other locations which can be useful if you are trying to install a package not published to the Pulsar Package Repository.

    Github or Git Remotes

    Pulsar can install from a GitHub repository or any valid git remote url. The -b option can then be used to specify a particular tag or branch.

    Git remote
    pulsar -p install <git_remote> [-b <branch_or_tag>]

    GitHub
    pulsar -p install <github_username>/<github_project> [-b <branch_or_tag>]

    For example to install the Generic-LSPopen in new window package from GitHub you could use either:

    pulsar -p install https://github.com/mauricioszabo/generic-lsp/

    or

    pulsar -p install mauricioszabo/generic-lsp

    This will use the current HEAD commit of the default branch. If you want to install a specific version from a branch or tag then use the -b option:

    e.g. pulsar -p install https://github.com/mauricioszabo/generic-lsp/ -b v1.0.3

    + + + diff --git a/docs/launch-manual/sections/using-pulsar/sections/pulsar-selections.html b/docs/launch-manual/sections/using-pulsar/sections/pulsar-selections.html new file mode 100644 index 0000000000..62c3223c91 --- /dev/null +++ b/docs/launch-manual/sections/using-pulsar/sections/pulsar-selections.html @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + +

    About 2 min

    Pulsar Selections

    Text selections in Pulsar support a number of actions, such as scoping deletion, indentation and search actions, and marking text for actions such as quoting and bracketing.

    Selections mirror many of the movement commands. They're actually exactly the same keybindings as the movement commands, but with a Shift key added in.

    + + + diff --git a/docs/launch-manual/sections/using-pulsar/sections/snippets.html b/docs/launch-manual/sections/using-pulsar/sections/snippets.html new file mode 100644 index 0000000000..4468746f15 --- /dev/null +++ b/docs/launch-manual/sections/using-pulsar/sections/snippets.html @@ -0,0 +1,264 @@ + + + + + + + + + + + + + + +

    About 3 min

    Snippets

    Snippets are an incredibly powerful way to quickly generate commonly needed code syntax from a shortcut.

    The idea is that you can type something like habtm and then press the Tab key and it will expand into has_and_belongs_to_many.

    Many Core and Community packages come bundled with their own snippets that are specific to it. For example, the language-html package that provides support for HTML syntax highlighting and grammar comes with dozens of snippets to create many of the various HTML tags you might want to use. If you create a new HTML file in Pulsar, you can type html and then press Tab and it will expand to:

    <!DOCTYPE html>
    +<html lang="en" dir="ltr">
    +	<head>
    +		<meta charset="utf-8" />
    +		<title></title>
    +	</head>
    +	<body></body>
    +</html>
    +

    It will also position the cursor in the lang attribute value so you can edit it if necessary. Many snippets have multiple focus points that you can move through with the Tab key as well - for instance, in the case of this HTML snippet, after the cursor is placed in the lang attribute value, you can continue pressing Tab and the cursor will move to the dir attribute value, then to the middle of the title tag, then finally to the middle of the body tag.

    To see all the available snippets for the file type that you currently have open, choose "Snippets: Available" in the Command Palette.

    View all available snippets

    You can also use fuzzy search to filter this list down by typing in the selection box. Selecting one of them will execute the snippet where your cursor is (or multiple cursors are).

    Creating Your Own Snippets

    So that's pretty cool, but what if there is something the language package didn't include or something that is custom to the code you write? Luckily it's incredibly easy to add your own snippets.

    There is a text file in your LNX/MAC: ~/.pulsar - WIN: %USERPROFILE%\.pulsar directory called snippets.cson that contains all your custom snippets that are loaded when you launch Pulsar. You can also easily open up that file by selecting the LNX: Edit > Snippets - MAC: Pulsar > Snippets - WIN: File > Snippets menu.

    Snippet Format

    So let's look at how to write a snippet. The basic snippet format looks like this:

    '.source.js':
    +  'console.log':
    +    'prefix': 'log'
    +    'body': 'console.log(${1:"crash"});$2'
    +

    The leftmost keys are the selectors where these snippets should be active. The easiest way to determine what this should be is to go to the language package of the language you want to add a snippet for and look for the "Scope" string.

    For example, if we wanted to add a snippet that would work for Java files, we would look up the language-java package in our Settings view and we can see the Scope is source.java. Then the top level snippet key would be that prepended by a period (like a CSS class selector would do).

    Finding the selector scope for a snippet

    The next level of keys are the snippet names. These are used for describing the snippet in a more readable way in the snippet menu. You can name them whatever you want.

    Under each snippet name is a prefix that should trigger the snippet and a body to insert when the snippet is triggered.

    Each $ followed by a number is a tab stop. Tab stops are cycled through by pressing Tab once a snippet has been triggered.

    Tab stops with the same number will create multiple cursors.

    The above example adds a log snippet to JavaScript files that would expand to:

    console.log("crash");
    +

    The string "crash" would be initially selected and pressing tab again would place the cursor after the ;

    Warning

    Snippet keys, unlike CSS selectors, can only be repeated once per level. If there are duplicate keys at the same level, then only the last one will be read. See Configuring with CSON for more information.

    Multi-line Snippet Body

    You can also use CoffeeScript multi-line syntaxopen in new window using """ for larger templates:

    '.source.js':
    +  'if, else if, else':
    +    'prefix': 'ieie'
    +    'body': """
    +      if (${1:true}) {
    +        $2
    +      } else if (${3:false}) {
    +        $4
    +      } else {
    +        $5
    +      }
    +    """
    +

    As you might expect, there is a snippet to create snippets. If you open up a snippets file and type snip and then press Tab, you will get the following text inserted:

    '.source.js':
    +  'Snippet Name':
    +    'prefix': 'hello'
    +    'body': 'Hello World!'
    +

    💥 Just fill that bad boy out and you have yourself a snippet. As soon as you save the file, Pulsar should reload the snippets and you will immediately be able to try it out.

    Multiple Snippets per Source

    You can see below the format for including multiple snippets for the same scope in your snippets.cson file. Just include the snippet name, prefix, and body keys for additional snippets inside the scope key:

    '.source.gfm':
    +  'Hello World':
    +    'prefix': 'hewo'
    +    'body': 'Hello World!'
    +
    +  'Github Hello':
    +    'prefix': 'gihe'
    +    'body': 'Octocat says Hi!'
    +
    +  'Octocat Image Link':
    +    'prefix': 'octopic'
    +    'body': '![GitHub Octocat](https://assets-cdn.github.com/images/modules/logos_page/Octocat.png)'
    +

    Again, see Configuring with CSON for more information on CSON key structure and non-repeatability.

    More Info

    The snippets functionality is implemented in the snippetsopen in new window package.

    For more examples, see the snippets in the language-htmlopen in new window and language-javascriptopen in new window packages.

    + + + diff --git a/docs/launch-manual/sections/using-pulsar/sections/summary.html b/docs/launch-manual/sections/using-pulsar/sections/summary.html new file mode 100644 index 0000000000..11010a749f --- /dev/null +++ b/docs/launch-manual/sections/using-pulsar/sections/summary.html @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + +

    Less than 1 minute

    Summary

    At this point you should be something of a Pulsar master user. You should be able to navigate and manipulate your text and files like a wizard. You should also be able to customize Pulsar backwards and forwards to make it look and act just how you want it to.

    In the next section, we're going to kick it up a notch: we'll take a look at changing and adding new functionality to the core of Pulsar itself. We're going to start creating packages for Pulsar. If you can dream it, you can build it.

    + + + diff --git a/docs/launch-manual/sections/using-pulsar/sections/version-control-in-pulsar.html b/docs/launch-manual/sections/using-pulsar/sections/version-control-in-pulsar.html new file mode 100644 index 0000000000..af60f5416a --- /dev/null +++ b/docs/launch-manual/sections/using-pulsar/sections/version-control-in-pulsar.html @@ -0,0 +1,224 @@ + + + + + + + + + + + + + + +

    About 2 min

    Version Control in Pulsar

    Version control is an important aspect of any project and Pulsar comes with basic Gitopen in new window and GitHubopen in new window integration built in.

    In order to use version control in Pulsar, the project root needs to contain the Git repository.

    Checkout HEAD revision

    The LNX/WIN: Alt+Ctrl+Z - MAC: Alt+Cmd+Z keybinding checks out the HEAD revision of the file in the editor.

    This is a quick way to discard any saved and staged changes you've made and restore the file to the version in the HEAD commit. This is essentially the same as running git checkout HEAD -- <path> and git reset HEAD -- <path> from the command line for that path.

    Git checkout

    This command goes onto the undo stack so you can use LNX/WIN: Ctrl+Z - MAC: Cmd+Z afterwards to restore the previous contents.

    Git status list

    Pulsar ships with the fuzzy-finder packageopen in new window which provides LNX/WIN: Ctrl+T - MAC: Cmd+T to quickly open files in the project and LNX/WIN: Ctrl+B - MAC: Cmd+B to jump to any open editor. The package also provides LNX/WIN: Ctrl+Shift+B - MAC: Cmd+Shift+B which displays a list of all the untracked and modified files in the project. These will be the same files that you would see on the command line if you ran git status.

    Git status list

    An icon will appear to the right of each file letting you know whether it is untracked or modified.

    Commit editor

    Pulsar can be used as your Git commit editor and ships with the language-git packageopen in new window which adds syntax highlighting to edited commit, merge, and rebase messages.

    Git commit message highlighting

    You can configure Pulsar to be your Git commit editor with the following command:

    $ git config --global core.editor "pulsar --wait"
    +

    The language-gitopen in new window package will help remind you to be brief by colorizing the first lines of commit messages when they're longer than 50 or 65 characters.

    Status bar icons

    The status-baropen in new window package that ships with Pulsar includes several Git decorations that display on the right side of the status bar:

    Git Status Bar decorations

    The currently checked out branch name is shown with the number of commits the branch is ahead of or behind its upstream branch. An icon is added if the file is untracked, modified, or ignored. The number of lines added and removed since the file was last committed will be displayed as well.

    Line diffs

    The included git-diffopen in new window package colorizes the gutter next to lines that have been added, edited, or removed.

    Git line diff indications

    This package also adds Alt+G Down and Alt+GUp keybindings that allow you to move the cursor to the next or previous diff in the current editor.

    Open on GitHub

    If the project you're working on is on GitHub, there are also some very useful integrations you can use. Most of the commands will take the current file you're viewing and open a view of that file on GitHub - for instance, the blame or commit history of that file.

    • Alt+G O - Open file on GitHub
    • Alt+G B - Open Blame view of file on GitHub
    • Alt+G H - Open History view of file on GitHub
    • Alt+G C - Copy the URL of the current file on GitHub to the clipboard
    • Alt+G R - Branch compare on GitHub

    The branch comparison shows you the commits that are on the branch you're currently working on locally that are not on the mainline branch.

    Open Blame of file on GitHub

    + + + diff --git a/docs/launch-manual/sections/using-pulsar/sections/writing-in-pulsar.html b/docs/launch-manual/sections/using-pulsar/sections/writing-in-pulsar.html new file mode 100644 index 0000000000..199c3276e7 --- /dev/null +++ b/docs/launch-manual/sections/using-pulsar/sections/writing-in-pulsar.html @@ -0,0 +1,226 @@ + + + + + + + + + + + + + + +

    About 2 min

    Writing in Pulsar

    Though it is probably most common to use Pulsar to write software code, Pulsar can also be used to write prose quite effectively. Most often this is done in some sort of markup language such as Asciidoc or Markdownopen in new window (in which this manual is written). Here we'll quickly cover a few of the tools Pulsar provides for helping you write prose.

    In these docs, we'll concentrate on writing in Markdown; however, other prose markup languages like Asciidoc have packages that provide similar functionality.

    Spell Checking

    If you're working in text (which includes plain text files, GitHub markdown, and Git commit messages by default), Pulsar will automatically try to check your spelling.

    Any misspelled words will be highlighted (by default with a dashed red line beneath the word), and you can pull up a menu of possible corrections by hitting LNX/WIN: Ctrl+Shift+; - MAC: Cmd+Shift+; (or by choosing "Correct Spelling" from the right-click context menu or from the Command Palette).

    Checking your spelling

    To add more types of files to the list of what Pulsar will try to spell check, go to the Spell Check package settings in your Settings view and add any grammars you want to spell check.

    The default grammars to spell check are text.plain, source.gfm, text.git-commit, source.asciidoc, source.rst, and text.restructuredtext but you can add other grammars if you wish to check those types of files too.

    The spell checking is implemented in the spell-checkopen in new window package.

    Previews

    When writing prose in a markup language, it's often very useful to get an idea of what the content will look like when it's rendered. Pulsar ships with a package for previewing Markdown by default.

    • Ctrl+Shift+M - Will toggle Preview mode for Markdown.

    Preview your prose

    As you edit the text, the preview will also update automatically. This makes it fairly easy to check your syntax as you type.

    You can also copy the rendered HTML from the preview pane into your system clipboard when the preview is focused and you press LNX: Ctrl+Ins - WIN: Ctrl+C - MAC: Cmd+C or if you right-click in the preview pane and choose "Copy as HTML".

    Markdown preview is implemented in the markdown-previewopen in new window package.

    Snippets

    There are also a number of great snippets available for writing Markdown quickly.

    If you type img and hit tab you get a Markdown-formatted image embed code like ![](). If you type table and hit tab you get a nice example table to fill out.

    | Header One | Header Two |
    +| :--------- | :--------- |
    +| Item One   | Item Two   |
    +

    Although there are only a handful of Markdown snippets (b for bold, i for italic, code for a code block, etc), they save you from having to look up the more obscure syntaxes. Again, you can easily see a list of all available snippets for the type of file you're currently in by choosing Snippets: Available in the Command Palette.

    + + + diff --git a/docs/packages/core/atom-languageclient/index.html b/docs/packages/core/atom-languageclient/index.html new file mode 100644 index 0000000000..17f458fa16 --- /dev/null +++ b/docs/packages/core/atom-languageclient/index.html @@ -0,0 +1,223 @@ + + + + + + + + Atom-LanguageClient | + + + + + + +

    Atom-LanguageClient

    Less than 1 minute

    Atom-LanguageClient

    Welcome to the atom-languageclient wiki!

    This wiki has been nearly directly taken from the upstream Atom Wikiopen in new window. That may mean that parts of it are out of date, but otherwise hopefully is a helpful resource relating to our Atom-LanguageClient Repoopen in new window!

    + + + diff --git a/docs/packages/core/atom-languageclient/list.html b/docs/packages/core/atom-languageclient/list.html new file mode 100644 index 0000000000..ae44ad222b --- /dev/null +++ b/docs/packages/core/atom-languageclient/list.html @@ -0,0 +1,223 @@ + + + + + + + + List of Pulsar Packages using Pulsar LanguageClient | + + + + + + +

    List of Pulsar Packages using Pulsar LanguageClient

    About 1 min

    List of Pulsar Packages using atom-languageclient

    Note

    Please note that its possible this list is outdated, as its original version was published by @'Damien Guard' on Apr 21, 2018.

    Here are the known Pulsar packages that are using atom-languageclient today:

    Official Packages

    Community Packages

    + + + diff --git a/docs/packages/core/atom-languageclient/release-process.html b/docs/packages/core/atom-languageclient/release-process.html new file mode 100644 index 0000000000..5afb17f542 --- /dev/null +++ b/docs/packages/core/atom-languageclient/release-process.html @@ -0,0 +1,223 @@ + + + + + + + + Release Process | + + + + + + +

    Release Process

    Less than 1 minute

    Release Process

    Note

    Please note that its possible this is outdated, as its original version was published by @'David Wilson' on Jan 22, 2018.

    Steps

    1. Pull down the latest changes from master.
    2. Edit CHANGELOG.mdopen in new window to add relevant release notes for changes wince the previous release with a corresponding ## N.N.N section header.
    3. Add and commit CHANGELOG.md with message "Updated CHANGELOG for [version in format N.N.N]".
    4. Run npm run test and verify that no tests failed.
    5. Run npm run flow and verify that there are no errors.
    6. Run npm version [version type] where the version type is major, minor, or path depending on the semantic versioningopen in new window impact of the changes.
    7. Run git log -1 and make sure a commit was made for the new version (double-check version in package.json if you like).
    8. If all looks good, run npm publish to publish the package.
    9. Run git push --tags to push the new version tag to GitHub.
    10. Go to the atom-languageclient tags pageopen in new window and click "Add Release Notes" for the tag corresponding to the release you just published.
    11. Paste the contents of your new CHANGELOG.md section, sans version header, into the "Describe this release" box.
    12. Click the "Publish release" button.
    13. 🎉
    + + + diff --git a/docs/packages/core/autocomplete-plus/autocomplete-providers.html b/docs/packages/core/autocomplete-plus/autocomplete-providers.html new file mode 100644 index 0000000000..7cd724703a --- /dev/null +++ b/docs/packages/core/autocomplete-plus/autocomplete-providers.html @@ -0,0 +1,223 @@ + + + + + + + + Autocomplete Providers | + + + + + + +

    Autocomplete Providers

    About 5 min

    Autocomplete Providers

    Note

    Please note that its possible this is outdated, as its original version was published by @'percova' on Nov 12, 2019.

    Built-In Providers

    GrammarSelectorProviderStatus
    All*SymbolProviderDefault Provider
    All*FuzzyProviderDeprecated

    Providers For Built-In Grammars

    GrammarSelectorProviderAPI Status
    Null Grammar.text.plain.null-grammar  
    CoffeeScript (Literate)open in new window.source.litcoffee
    CoffeeScriptopen in new window.source.coffee
    JSONopen in new window.source.json
    Shell Sessionopen in new window.text.shell-session
    Shell Scriptopen in new window.source.shell
    Hyperlinkopen in new window.text.hyperlink
    TODOopen in new window.text.todo
    Copen in new window.source.cautocomplete-clangopen in new window
    C++open in new window.source.cppautocomplete-clangopen in new window2.0.0
    Clojureopen in new window.source.clojureproto-replopen in new window
    CSSopen in new window.source.cssautocomplete-cssopen in new window2.0.0
    GitHub Markdownopen in new window.source.gfmautocomplete-bibtexopen in new window1.1.0
    Git Configopen in new window.source.git-config
    Git Commit Messageopen in new window.text.git-commit
    Git Rebase Messageopen in new window.text.git-rebase
    HTML (Go)open in new window.text.html.gohtml
    Goopen in new window.source.gogo-plusopen in new window, autocomplete-goopen in new window2.0.0
    Go Templateopen in new window.source.gotemplate
    HTMLopen in new window.text.html.basicautocomplete-htmlopen in new window2.0.0
    JavaScriptopen in new window.source.jsatom-ternjsopen in new window2.0.0
    Java Propertiesopen in new window.source.java-properties
    Regular Expressions (JavaScript)open in new window.source.js.regexp
    JavaServer Pagesopen in new window.text.html.jspautocomplete-jspopen in new window2.0.0
    Javaopen in new window.source.javaautocomplete-java-minusopen in new window2.0.0
    JUnit Test Reportopen in new window.text.junit-test-report
    Makefileopen in new window.source.makefile
    LESSopen in new window.source.css.less
    SQL (Mustache)open in new window.source.sql.mustache
    HTML (Mustache)open in new window.text.html.mustache
    Objective-C++open in new window.source.objcppautocomplete-clangopen in new window
    Strings Fileopen in new window.source.strings
    Objective-Copen in new window.source.objcautocomplete-clangopen in new window
    Property List (XML)open in new window.text.xml.plist
    Property List (Old-Style)open in new window.source.plist
    Perlopen in new window.source.perl
    PHPopen in new window.text.html.php
    PHP.source.phpphp-integrator-autocomplete-plusopen in new window, atom-autocomplete-phpopen in new window, autocomplete-phpopen in new window2.0.0
    Python Consoleopen in new window.text.python.console
    Python Tracebackopen in new window.text.python.traceback
    Regular Expressions (Python)open in new window.source.regexp.python
    Pythonopen in new window.source.pythonautocomplete-pythonopen in new window, autocomplete-python-jediopen in new window
    Ruby on Rails (RJS)open in new window.source.ruby.rails.rjs
    Rubyopen in new window.source.ruby
    HTML (Ruby - ERB)open in new window.text.html.erb
    HTML (Rails)open in new window.text.html.ruby
    SQL (Rails)open in new window.source.sql.ruby
    JavaScript (Rails)open in new window.source.js.rails .source.js.jquery
    Ruby on Railsopen in new window.source.ruby.rails
    Sassopen in new window.source.sass
    Plain Textopen in new window.text.plain
    SCSSopen in new window.source.css.scss
    SQLopen in new window.source.sqlautocomplete-sqlopen in new window2.0.0
    TOMLopen in new window.source.toml
    XSLopen in new window.text.xml.xsl
    XMLopen in new window.text.xmlautocomplete-xmlopen in new window2.0.0
    YAMLopen in new window.source.yaml

    Providers For Third-Party Grammars

    GrammarSelectorProviderAPI Status
    Apexopen in new window.source.apexmavensmate-atomopen in new window1.0.0
    AsciiDocopen in new window.source.asciidocautocomplete-asciidocopen in new window2.0.0
    C#open in new window.source.csomnisharp-atomopen in new window2.0.0
    ComputerCraftopen in new window.source.computercraftautocomplete-computercraftopen in new window1.0.0
    Curryopen in new window.source.curryautocomplete-curryopen in new window4.0.0
    Dartopen in new window.source.dartdart-toolsopen in new window
    Dartopen in new window.source.dartdartlangopen in new window
    Elixiropen in new window.source.elixirautocomplete-elixiropen in new window2.0.0
    Erlangopen in new window.source.erlangautocomplete-erlangopen in new window2.0.0
    GLSLopen in new window.source.glslautocomplete-glslopen in new window2.0.0
    HackLangopen in new window.source.hackautocomplete-hackopen in new window2.0.0
    Haskellopen in new window.source.haskellautocomplete-haskellopen in new window1.0.0
    Haskellopen in new window.source.haskellide-haskellopen in new window1.0.0
    Haxeopen in new window.source.haxeautocomplete-haxeopen in new window1.1.0
    LaTeXopen in new window.text.tex.latexautocomplete-latex-citeopen in new window, autocomplete-latex-referencesopen in new window, autocomplete-glossariesopen in new window2.0.0
    Markoopen in new window.text.markoautocomplete-markoopen in new window2.0.0
    Nunjucksopen in new window.source.nunjucks, .text.html.nunjucksautocomplete-nunjucksopen in new window2.0.0
    Pigopen in new window.source.pigpigopen in new window2.0.0
    Q/Kopen in new window.source.qautocomplete-kdb-qopen in new window2.0.0
    Rustopen in new window.source.rustraceropen in new window2.0.0
    Turtleopen in new window.source.turtleturtle-completeropen in new window2.0.0
    TypeScriptopen in new window.source.tsatom-typescriptopen in new window8.11.0
    Visualforceopen in new window.visualforcemavensmate-atomopen in new window1.1.0
    WordPress Coding Standard Whitelist Flagsopen in new window.php .commentautocomplete-wpcs-flagsopen in new window2.0.0

    Providers Not Tied To A Specific Grammar

    SelectorProviderStatus
    *autocomplete-emojisopen in new window1.0.0
    *autocomplete-snippetsopen in new window2.0.0
    *autocomplete-pathsopen in new window1.0.0
    *atom-path-intellisenseopen in new window1.2.1
    *atom-ctagsopen in new window2.0.0
    .source.js, .source.jsxide-flowopen in new window1.1.0
    .source.js, .source.jsx, .source.coffeeautocomplete-underdashopen in new window2.0.0
    .source.css, .source.css.less, .source.sass, .source.css.scss, .source.stylusproject-palette-finderopen in new window1.1.0
    *you-complete-meopen in new window2.0.0
    English word autocompetion with the hint of explanation.autocomplete-en-enopen in new window2.0.0

    --

    Providers Requested By The Community

    If you'd like to contribute and are interested in learning how to write an autocomplete-plus Provider, start here:

    • Emmet: https://github.com/atom-community/autocomplete-plus/issues/156
    • LESS: https://github.com/atom-community/autocomplete-plus/issues/151

    Packages That Claim Autocomplete, But Are Not API 1.0.0 Compatible

    • https://github.com/maun/atom-rust-plus (never published, uses autocomplete-plus-asyncopen in new window)
    • https://atom.io/packages/ios (doesn't make use of autocomplete-plus)
    • https://atom.io/packages/language-hn (see: https://github.com/ignaciocases/language-hn/issues/1 for API 1.0.0 compatibility)
    • https://atom.io/packages/rsense (see: https://github.com/rsense/atom-rsense/issues/1 for API 1.0.0 compatibility)

    Deprecated Providers

    If you are using one of these providers, please uninstall the package as it is no longer maintained.

    Other Forks Of Autocomplete

    • https://github.com/xumingthepoet/autocomplete-plus-elixir (never published)
    • https://atom.io/packages/autocomplete-jedi (fork of autocomplete)
    • https://atom.io/packages/rubymotion (extends default autocomplete package)
    + + + diff --git a/docs/packages/core/autocomplete-plus/index.html b/docs/packages/core/autocomplete-plus/index.html new file mode 100644 index 0000000000..a18eb904f8 --- /dev/null +++ b/docs/packages/core/autocomplete-plus/index.html @@ -0,0 +1,223 @@ + + + + + + + + Autocomplete-Plus | + + + + + + +

    Autocomplete-Plus

    Less than 1 minute

    Autocomplete-Plus

    Welcome to the autocomplete-plus wiki!

    This wiki has been nearly directly taken from the taken from the upstream Atom Wikiopen in new window. That may mean that parts of it are out of date, but otherwise hopefully is a helpful resource relating to our Autocomplete-Plus Repoopen in new window.

    + + + diff --git a/docs/packages/core/autocomplete-plus/provider-api.html b/docs/packages/core/autocomplete-plus/provider-api.html new file mode 100644 index 0000000000..81c5dcf126 --- /dev/null +++ b/docs/packages/core/autocomplete-plus/provider-api.html @@ -0,0 +1,316 @@ + + + + + + + + Provider API | + + + + + + +

    Provider API

    About 5 min

    Provider API

    Note

    Please note that its possible this is outdated, as its original version was published by @'Nathan Sobo' on Jan 3, 2018.

    The Provider API allows you to make autocomplete+ awesome. The Pulsar community will ultimately judge the quality of Pulsar's autocomplete experience by the breadth and depth of the provider ecosystem. We're so excited that you're here reading about how to make Pulsar awesome.

    The following examples are in CoffeeScript. If you would like to add JavaScript examples, please feel free to edit this page!

    Defining A Provider

    provider =
    +  # This will work on JavaScript and CoffeeScript files, but not in js comments.
    +  selector: '.source.js, .source.coffee'
    +  disableForSelector: '.source.js .comment'
    +
    +  # This will take priority over the default provider, which has an inclusionPriority of 0.
    +  # `excludeLowerPriority` will suppress any providers with a lower priority
    +  # i.e. The default provider will be suppressed
    +  inclusionPriority: 1
    +  excludeLowerPriority: true
    +
    +  # This will be suggested before the default provider, which has a suggestionPriority of 1.
    +  suggestionPriority: 2
    +
    +  # Let autocomplete+ filter and sort the suggestions you provide.
    +  filterSuggestions: true
    +
    +  # Required: Return a promise, an array of suggestions, or null.
    +  getSuggestions: ({editor, bufferPosition, scopeDescriptor, prefix, activatedManually}) ->
    +    new Promise (resolve) ->
    +      resolve([text: 'something'])
    +
    +  # (optional): (*experimental*) called when user the user selects a suggestion for the purpose of loading additional information about the suggestion.
    +  getSuggestionDetailsOnSelect: (suggestion) ->
    +    new Promise (resolve) ->
    +      resolve(newSuggestion)
    +
    +  # (optional): called _after_ the suggestion `replacementPrefix` is replaced
    +  # by the suggestion `text` in the buffer
    +  onDidInsertSuggestion: ({editor, triggerPosition, suggestion}) ->
    +
    +  # (optional): called when your provider needs to be cleaned up. Unsubscribe
    +  # from things, kill any processes, etc.
    +  dispose: ->
    +

    The properties of a provider:

    • selector (required): Defines the scope selector(s) (can be comma-separated) for which your provider should receive suggestion requests
    • getSuggestions (required): Is called when a suggestion request has been dispatched by autocomplete+ to your provider. Return an array of suggestions (if any) in the order you would like them displayed to the user. Returning a Promise of an array of suggestions is also supported.
    • getSuggestionDetailsOnSelect (optional): (experimental) Is called when a suggestion is selected by the user for the purpose of loading more information about the suggestion. Return a Promise of the new suggestion to replace it with or return null if no change is needed.
    • disableForSelector (optional): Defines the scope selector(s) (can be comma-separated) for which your provider should not be used
    • inclusionPriority (optional): A number to indicate its priority to be included in a suggestions request. The default provider has an inclusion priority of 0. Higher priority providers can suppress lower priority providers with excludeLowerPriority.
    • excludeLowerPriority (optional): Will not use lower priority providers when this provider is used.
    • suggestionPriority (optional): A number to determine the sort order of suggestions. The default provider has an suggestion priority of 1
    • filterSuggestions (optional): If set to true, autocomplete+ will perform fuzzy filtering and sorting on the list of matches returned by getSuggestions.
    • dispose (optional): Will be called if your provider is being destroyed by autocomplete+
    • onDidInsertSuggestion (optional): Function that is called when a suggestion from your provider was inserted into the buffer

    Support For Asynchronous Request Handling

    Some providers satisfy a suggestion request in an asynchronous way (e.g. it may need to dispatch requests to an external process to get suggestions). To asynchronously provide suggestions, simply return a Promise from your getSuggestions:

    getSuggestions: (options) ->
    +  return new Promise (resolve) ->
    +    # Build your suggestions here asynchronously...
    +    resolve(suggestions) # When you are done, call resolve and pass your suggestions to it
    +

    The Suggestion Request's Options Object

    An options object will be passed to your getSuggestions function, with the following properties:

    • editor: The current TextEditor
    • bufferPosition: The position of the cursor
    • scopeDescriptor: The scope descriptoropen in new window for the current cursor position
    • prefix: The word characters immediately preceding the current cursor position
    • activatedManually: Whether the autocomplete request was initiated by the user (e.g. with ctrl+space)

    Suggestions

    provider =
    +  selector: '.source.js, .source.coffee'
    +  disableForSelector: '.source.js .comment'
    +
    +  getSuggestions: ({editor, bufferPosition, scopeDescriptor, prefix}) ->
    +    new Promise (resolve) ->
    +      # Find your suggestions here
    +      suggestion =
    +        text: 'someText' # OR
    +        snippet: 'someText(${1:myArg})'
    +        displayText: 'someText' # (optional)
    +        replacementPrefix: 'so' # (optional)
    +        type: 'function' # (optional)
    +        leftLabel: '' # (optional)
    +        leftLabelHTML: '' # (optional)
    +        rightLabel: '' # (optional)
    +        rightLabelHTML: '' # (optional)
    +        className: '' # (optional)
    +        iconHTML: '' # (optional)
    +        description: '' # (optional)
    +        descriptionMoreURL: '' # (optional)
    +        characterMatchIndices: [0, 1, 2] # (optional)
    +      resolve([suggestion])
    +

    Your suggestions should be returned from getSuggestions as an array of objects with the following properties:

    • text (required; or snippet): The text which will be inserted into the editor, in place of the prefix
    • snippet (required; or text): A snippet string. This will allow users to tab through function arguments or other options. e.g. 'myFunction(${1:arg1}, ${2:arg2})'. See the snippetsopen in new window package for more information.
    • displayText (optional): A string that will show in the UI for this suggestion. When not set, snippet || text is displayed. This is useful when snippet or text displays too much, and you want to simplify. e.g. {type: 'attribute', snippet: 'class="$0"$1', displayText: 'class'}
    • replacementPrefix (optional): The text immediately preceding the cursor, which will be replaced by the text. If not provided, the prefix passed into getSuggestions will be used.
    • type (optional): The suggestion type. It will be converted into an icon shown against the suggestion. screenshotopen in new window. Predefined styles exist for variable, constant, property, value, method, function, class, type, keyword, tag, snippet, import, require. This list represents nearly everything being colorized.
    • leftLabel (optional): This is shown before the suggestion. Useful for return values. screenshotopen in new window
    • leftLabelHTML (optional): Use this instead of leftLabel if you want to use html for the left label.
    • rightLabel (optional): An indicator (e.g. function, variable) denoting the "kind" of suggestion this represents
    • rightLabelHTML (optional): Use this instead of rightLabel if you want to use html for the right label.
    • className (optional): Class name for the suggestion in the suggestion list. Allows you to style your suggestion via CSS, if desired
    • iconHTML (optional): If you want complete control over the icon shown against the suggestion. e.g. iconHTML: '<i class="icon-move-right"></i>' screenshotopen in new window. The background color of the icon will still be determined (by default) from the type.
    • description (optional): A doc-string summary or short description of the suggestion. When specified, it will be displayed at the bottom of the suggestions list.
    • descriptionMoreURL (optional): A url to the documentation or more information about this suggestion. When specified, a More.. link will be displayed in the description area. autocomplete-description
    • characterMatchIndices (optional): A list of indexes where the characters in the prefix appear in this suggestion's text. e.g. "foo" in "foo_bar" would be [0, 1, 2].

    Registering Your Provider With autocomplete+

    API 4.0.0

    In your package.json, add:

    "providedServices": {
    +  "autocomplete.provider": {
    +    "versions": {
    +      "4.0.0": "provide"
    +    }
    +  }
    +}
    +

    Then, in your main.coffee (or whatever file you define as your main in package.json i.e. "main": "./lib/your-main" would imply your-main.coffee), add the following:

    For a single provider:

    module.exports =
    +  provide: -> @yourProviderHere
    +

    For multiple providers, just return an array:

    module.exports =
    +  provide: -> [@yourProviderHere, @yourOtherProviderHere]
    +

    Provider Examples

    We've taken to making each provider its own clean repo:

    Check out the lib directory in each of these for the code!

    Tips

    Generating a new prefix

    The prefix passed to getSuggestions may not be sufficient for your language. You may need to generate your own prefix. Here is a pattern to use your own prefix:

    provider =
    +  selector: '.source.js, .source.coffee'
    +
    +  getSuggestions: ({editor, bufferPosition}) ->
    +    prefix = @getPrefix(editor, bufferPosition)
    +
    +    new Promise (resolve) ->
    +      suggestion =
    +        text: 'someText'
    +        replacementPrefix: prefix
    +      resolve([suggestion])
    +
    +  getPrefix: (editor, bufferPosition) ->
    +    # Whatever your prefix regex might be
    +    regex = /[\w0-9_-]+$/
    +
    +    # Get the text for the line up to the triggered buffer position
    +    line = editor.getTextInRange([[bufferPosition.row, 0], bufferPosition])
    +
    +    # Match the regex to the line, and return the match
    +    line.match(regex)?[0] or ''
    +
    + + + diff --git a/docs/packages/core/autocomplete-plus/symbolprovider-config-api.html b/docs/packages/core/autocomplete-plus/symbolprovider-config-api.html new file mode 100644 index 0000000000..13b925b20f --- /dev/null +++ b/docs/packages/core/autocomplete-plus/symbolprovider-config-api.html @@ -0,0 +1,272 @@ + + + + + + + + SymbolProvider Config API | + + + + + + +

    SymbolProvider Config API

    About 2 min

    SymbolProvider Config API

    Note

    Please note that its possible this is outdated, as its original version was published by @'Phil' on Jan 24, 2017.

    The builtin SymbolProvider allows showing the types of symbols in the suggestion list.

    symbol-provider-notes

    The icon colors are intended to match the syntax color of the symbol type. e.g. functions are blue in this theme, so the function icon is also blue.

    Each language can tune how the SymbolProvider tags symbols by modifying the config of the language packageopen in new window.

    # An example for the CoffeeScript language
    +'.source.coffee':
    +  autocomplete:
    +    symbols:
    +      class:
    +        selector: '.class.name, .inherited-class, .instance.type'
    +        typePriority: 4
    +      function:
    +        selector: '.function.name'
    +        typePriority: 3
    +      variable:
    +        selector: '.variable'
    +        typePriority: 2
    +      '': # the catch-all
    +        selector: '.source'
    +        typePriority: 1
    +      builtin:
    +        suggestions: [
    +          'one'
    +          'two'
    +        ]
    +

    A more generic example:

    
    +'.source.language':
    +  autocomplete:
    +    symbols:
    +      typename1:
    +        selector: '.some, .selectors'
    +        typePriority: 1
    +      typename2:
    +        suggestions: [...]
    +      typename3:
    +        ...
    +

    Any number of Typename objects are supported, and typename can be anything you choose. However, you are encouraged to use one of the predefined typenames. There are predefined styles for the following types:

    builtin
    +class
    +constant
    +function
    +import
    +keyword
    +method
    +module
    +mixin
    +package
    +property
    +require
    +snippet
    +tag
    +type
    +value
    +variable
    +

    Typename Objects

    Typename objects support the following properties:

    • selector: The selector that matches your symbol types. e.g. '.variable.name'. You can also have several selectors separated by commas, just like in CSS '.variable.name, .storage.variable'
    • typePriority: The priority this Typename object has over others. e.g. in our CoffeeScript example above, if a symbol is tagged with the function type in one part of the code, but class in another part of the code, it will be displayed to the user as a class because class has a higher typePriority
    • suggestions: This allows you to specify explicit completions for some scope. A good use is builtins: e.g. ['Math', 'Array', ...] in JavaScript
      • Items in the suggestions list can also objects using any of the properties from the provider API.

    Finding Scope Selectors

    Coming up with the selectors for a given Typename object might be the hardest part of defining the autocomplete.symbols config. Fortunately there is a tool to help

    Open the command paletteopen in new window, then search for log cursor scope. You will be presented with a blue box like the following:

    scopenames

    Each bullet in the box is a node. The last bullet is the symbol itself, and each preceding line is a parent of the symbol — just like CSS. With this information, you can see that the symbol can be matched with several selectors: '.variable', '.variable.assignment', '.source.coffee .variable', etc.

    + + + diff --git a/docs/packages/core/github/index.html b/docs/packages/core/github/index.html new file mode 100644 index 0000000000..b190711741 --- /dev/null +++ b/docs/packages/core/github/index.html @@ -0,0 +1,223 @@ + + + + + + + + GitHub | + + + + + + +

    GitHub

    Less than 1 minute

    GitHub

    Welcome to the github wiki!

    This wiki contains a single file from the original upstream Atom Wikiopen in new window. Which while serves no purpose for the current mission or direction of Pulsar, just seemed like an interesting insight into how Atom originally began, and how progress originally happened.

    Note

    Please note that it's possible this is outdated, as its original version was published by @'Michelle Tilley' on Jun 7, 2017.

    Roadmaps

    Short Term Roadmapopen in new window

    Monthly Planning

    + + + diff --git a/docs/packages/core/github/june-2017.html b/docs/packages/core/github/june-2017.html new file mode 100644 index 0000000000..23d30a68ff --- /dev/null +++ b/docs/packages/core/github/june-2017.html @@ -0,0 +1,223 @@ + + + + + + + + June 2017 - Monthly Planning | + + + + + + +

    June 2017 - Monthly Planning

    About 1 min

    June 2017 - Monthly Planning

    Note

    Please note that its possible this is outdated, as its original version was published by @'Katrina Uychaco' on Jun 14, 2017.

    Looking Back

    • Beta launch went very well
    • Relatively few problems
    • Performance work @smashwilson and @kuychaco did is ✨
    • GPG/credential helper work that @smashwilson shipped was solid
    • Feeling good about shipping to stable

    This Month

    • Ensure room to triage and fix issues that may arise after shipping to stable
    • File change notification service @smashwilson
    • GitHub integration @BinaryMuse @kuychaco
      • Create new PR
      • Fill out existing functionality (timeline item types, new fields, etc.)
      • Add mechanism for manual/auto refreshing
      • Continue to investigate possibility of real-time updates
    • Git integration @kuychaco
      • Add "last commit(s)" view under commit box
      • Investigate removing amend checkbox in favor of more robust HEAD management

    Other Topics Discussed

    • Web Workers
      • Wait to see if issues arise with current implementation
    • Better remote/branch management
      • Existing UI mocks are relatively old and pre-docks
      • Want to ensure what we ship is close to what we'll want long term to ensure UI/UX stability over time
      • Resume conversation around design and pick this up in coming months
    • core:undo support
      • Undo support is a misnomer in a system where there's not a linear set of edits
      • Want to design a system where any action is recoverable (different from undoable)
      • Really talking about UI Git porcelain at this point
      • Start deeper conversations now, talk about more at mini-summit
    • Git log / graph
      • We could do something simple now very quickly but would it be up to the standards we want?
      • Decided to work on "last commit(s)" view for June, talk about this more at mini-summit
    • Stash support
      • Start talking about UI/UX
    • Additional GitHub PR work
      • "Checking out" a PR from the GH pane is a great first step toward implementing a full code review flow
    + + + diff --git a/docs/packages/core/ide-java/incomplete-classpath-warning.html b/docs/packages/core/ide-java/incomplete-classpath-warning.html new file mode 100644 index 0000000000..cf2ca9606a --- /dev/null +++ b/docs/packages/core/ide-java/incomplete-classpath-warning.html @@ -0,0 +1,223 @@ + + + + + + + + Incomplete Classpath Warning | + + + + + + +

    Incomplete Classpath Warning

    Less than 1 minute

    Incomplete Classpath Warning

    Note

    Please note that its possible this is outdated, as its original version was published by @'Damein Guard' on Feb 16, 2018.

    In order to properly analyze Java files a project definition must be found that indicates the packages, paths to search for classes etc.

    You should open the folder that contains the pom.xml or build.gradle file if you want full diagnostics, errors, auto-completion etc.

    If you don't need the full analysis you can ignore this warning and work with a subset of the available features.

    + + + diff --git a/docs/packages/core/ide-java/index.html b/docs/packages/core/ide-java/index.html new file mode 100644 index 0000000000..bfd576fc9f --- /dev/null +++ b/docs/packages/core/ide-java/index.html @@ -0,0 +1,223 @@ + + + + + + + + IDE-Java | + + + + + + +

    IDE-Java

    Less than 1 minute

    IDE-Java

    Welcome to the ide-java wiki!

    This wiki contains a single file from the original upstream Atom Wikiopen in new window. That may mean parts of it are out of date, but otherwise hopefully it is a helpful resource relating to our IDE-Java Repoopen in new window!

    + + + diff --git a/docs/packages/core/index.html b/docs/packages/core/index.html new file mode 100644 index 0000000000..66b2119d7f --- /dev/null +++ b/docs/packages/core/index.html @@ -0,0 +1,223 @@ + + + + + + + + Core Packages Documentation | + + + + + + +

    Core Packages Documentation

    Less than 1 minute

    Core Pulsar Packages Documentation

    This aims to be a collection of information about the first-party packages published for Pulsar.

    Secondly, and currently, it is an archive of every Wiki that was published from the upstream Atom Organization.

    This means some of this information may be old, or no longer relevant. If it is, it has been marked as so at the top of the file under a warning.

    None-the-less the information can still be helpful, and may prove to be valuable. Which is why it has been archived here.

    Archived Wikis

    + + + diff --git a/docs/packages/index.html b/docs/packages/index.html new file mode 100644 index 0000000000..b6cc975046 --- /dev/null +++ b/docs/packages/index.html @@ -0,0 +1,223 @@ + + + + + + + + Packages | + + + + + + +

    Packages

    About 1 min

    Packages Documentation

    This aims to be a collection of information about the packages published for the Pulsar Editor. Currently, anything in Core Packages is an archive of every Wiki that was published from the upstream Atom Organization.

    This means some of this information may be old, or no longer relevant. If it is, it has been marked as so at the top of the file under a warning. None-the-less the information can still be helpful, and may prove to be valuable. Which is why it has been archived here.

    As well, we have a record of community packages as well. This is meant to be a record of these. This section should be updated as we get more community contributions of these. The process for adding this will be added at a later date.

    Core Packages

    Updated

    • TODO: Add Documentation from updated packages documentation.

    Archived Documentation

    Community Packages

    There are currently thousands of community packages published to the Pulsar Package Registry, if you are coming from Atom you should be able to find all of your familiar packages as we have archived everything that Atom once had published.

    If one of your favorite packages that you had on Atom is missing feel free to contact usopen in new window or take a look through the Pulsar Package Registry Administrative Actions Logopen in new window to see if it's been officially removed.

    Otherwise to browse the vast collection of packages available to install on Pulsar you can open up the Pulsar Settings Package page, or browse them on the Pulsar Package Registry Websiteopen in new window.

    + + + diff --git a/docs/resources/conduct/index.html b/docs/resources/conduct/index.html new file mode 100644 index 0000000000..3e4e23b2cb --- /dev/null +++ b/docs/resources/conduct/index.html @@ -0,0 +1,224 @@ + + + + + + + + Code of Conduct | + + + + + + +

    Code of Conduct

    Less than 1 minute

    Pulsar Code of Conduct

    Our Pledge

    In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.

    Our Standards

    Examples of behavior that contributes to creating a positive environment include:

    • Using welcoming and inclusive language
    • Being respectful of differing viewpoints and experiences
    • Gracefully accepting constructive criticism
    • Focusing on what is best for the community
    • Showing empathy towards other community members
    • Considering all suggestions regardless of personal opinions

    Examples of unacceptable behavior by participants include:

    • The use of sexualized language or imagery and unwelcome sexual attention or advances
    • Trolling, insulting/derogatory comments, and personal or political attacks
    • Public or private harassment
    • Publishing others' private information, such as a physical or electronic address, without explicit permission
    • Other conduct which could reasonably be considered inappropriate in a professional setting

    Our Responsibilities

    Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.

    Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.

    Scope

    This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.

    Enforcement

    Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team using our Discord Serveropen in new window. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.

    Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.

    Attribution

    This Code of Conduct is adapted from the Contributor Covenantopen in new window, version 1.4, available at https://contributor-covenant.org/version/1/4open in new window

    + + + diff --git a/docs/resources/glossary/index.html b/docs/resources/glossary/index.html new file mode 100644 index 0000000000..503da36e58 --- /dev/null +++ b/docs/resources/glossary/index.html @@ -0,0 +1,223 @@ + + + + + + + + Glossary | + + + + + + + + + + diff --git a/docs/resources/index.html b/docs/resources/index.html new file mode 100644 index 0000000000..e46ff6b71c --- /dev/null +++ b/docs/resources/index.html @@ -0,0 +1,223 @@ + + + + + + + + Resources | + + + + + + +

    Resources

    Less than 1 minute

    A collection of miscellaneous resources around Pulsar and the project.

    Glossary

    A list of terms and their meanings used by the application and community.

    Pulsar API

    The Pulsar API reference.

    Project tooling

    A list of tools and services used by the Pulsar team and information about them.

    + + + diff --git a/docs/resources/privacy/index.html b/docs/resources/privacy/index.html new file mode 100644 index 0000000000..046e2e057a --- /dev/null +++ b/docs/resources/privacy/index.html @@ -0,0 +1,224 @@ + + + + + + + + Privacy Policy | + + + + + + +

    Privacy Policy

    Less than 1 minute

    Pulsar Edit Privacy Policy

    The Pulsar team greatly values your, and everybody's, privacy. We therefore keep any collection of personal data to an absolute minimum, but what little is collected across the entirety of the Pulsar Project will be detailed here.

    Also please note that this Privacy Policy excludes any Community Packages that may be installed as those are created and maintained by a third party.

    At the bottom of each service listed in this document there will be a "TLDR" to quickly sum up all data that is collected, how it's used, and who can use it.

    Date of the Last Change to the Privacy Policy: 2022, December 10th

    Pulsar Backend (https://api.pulsar-edit.dev)

    The Pulsar Backend is the service you connect to when using all Pulsar Packages on the web via https://web.pulsar-edit.dev and when using the built in Pulsar Settings View or Package Manageropen in new window to interact with community packages.

    All actions are subject to this service's Privacy Policy, including browsing, publishing, installing, deleting, starring, or unstarring packages.

    Over the course of using the Pulsar Backend the following personal details are collected about you, and used as described below.

    What Information is Collected

    • IP Address
    • Browser Used and/or Version
    • Operating System and/or Version

    How is this Information Collected

    When you send a request of any kind to the Pulsar Backend this information is made available to us through the web request itself and the User Agentopen in new window of the request.

    What is Done with this Information

    • Your IP address is logged from the Pulsar Backend to aid in later troubleshooting that may need to occur.
    • Your Browser Details and Operating System Details are logged by the service used to Host the Pulsar Backend. But no further action is taken with this information once logged.

    Who has Access to this Information

    The only individuals that have access to the above details are Pulsar's Core Admin Team in charge of the Pulsar Backend.

    How Long is this information Kept

    The logs that contain the above information is kept in the cloud for 30 days before it is automatically deleted.

    TLDR

    • The Pulsar Backend logs your IP Address, and your User-Agent Details of your Web Request when accessing it's service.
    • These logs are kept for 30 days until they are deleted automatically.
    • The only people that have access to these logs are Pulsar's Core Admin Team in charge of the Pulsar Backend.
    • The only reason this data is used is to aid in troubleshooting when/if needed.

    Pulsar User Account

    When you create a user account with Pulsar to star, or publish packages, you provide some details to Pulsar to create the account. It should be made explicit that Pulsar does not store any type of credentials your account at any time.

    What Information is Collected

    • GitHub Username
    • GitHub Gravatar URL
    • GitHub node_id (Think of your node_id like the random number assigned to you when making a user account. This is public information)

    How is this Information Collected

    When you sign up with Pulsar, and choose to make an account, this is the data collected to make sure your account works, and is able to protect Packages from Malicious Actors pretending to be someone they're not.

    What is Done with this Information

    This information is used to let you authenticate against the Pulsar Backend when publishing, deleted, or starring a package.

    Who has Access to this Information

    The only people who have access to this information are Pulsar's Core Admin Team in charge of the Backend.

    How Long is this Information Kept

    The above details will be kept until either you request deletion of your account, or there is a built in supported method to delete your Pulsar User Account.

    How do I have this Information Deleted

    To request removal of the above details feel free to contact the Pulsar Core Admin Team, for quicker response times, contact use through the Pulsar Discordopen in new window. Or you can contact us through any of the supported methodsopen in new window, or even via email to admin@pulsar-edit.dev

    TLDR

    • Creating a Pulsar User Account provides Pulsar-Edit with your GitHub name, GitHub Gravatar URL, and GitHub node_id.
    • This information is kept until you request deletion or there is a built in way to delete your Pulsar User Account.
    • The only people who can access this information is the Pulsar Core Admin Team.
    • This information is purely there to help you use the Pulsar Backend to publish, delete, star, or otherwise interact with Packages.
    • Pulsar User Accounts contain Zero details of how you log in, or any type of API, OAuth, or other token to access your account.

    Pulsar Website (https://pulsar-edit.dev)

    The Pulsar Website is a service you connect to anytime you visit our website.

    This Website is hosted on GitHub Pagesopen in new window which while conveinent for us, also means that we have no built in mechanism to access any personal data of any kind through this service. That is to say we don't; no analytics are set up on the website, and no extra code is run on the website to collect any data.

    TLDR

    • The Pulsar Website Collects Zero Data about you, or where you are.
    • It does not Log anything, save anything, or extract anything.
    • It's just a basic website.

    Pulsar Editor

    The main Pulsar Application, available hereopen in new window is the program you use anytime you launch Pulsar, or edit text within it.

    While Atomopen in new window the original implementation of the Editor did collect telemetry and metrics about the users, when we began work on the editor one of the first things that we removed was the telemetryopen in new window.

    That is to say now, the Pulsar Editor collects Zero information about its users.

    TLDR

    • The Pulsar Editor Collects Zero Data about you, or where you are.
    • It does not log anything, send anything to remote servers, or extract anything.
    • You can view when this was removed from our upstream hereopen in new window.
    + + + diff --git a/docs/resources/pulsar-api/index.html b/docs/resources/pulsar-api/index.html new file mode 100644 index 0000000000..2b81b33cbd --- /dev/null +++ b/docs/resources/pulsar-api/index.html @@ -0,0 +1,223 @@ + + + + + + + + Pulsar API | + + + + + + +

    Pulsar API

    Less than 1 minute

    Under Construction

    This document is under construction, please check back soon for updates. Please see our socials and feel free to ask for assistance or inquire as to the status of this document.

    + + + diff --git a/docs/resources/tooling/index.html b/docs/resources/tooling/index.html new file mode 100644 index 0000000000..60457e9a39 --- /dev/null +++ b/docs/resources/tooling/index.html @@ -0,0 +1,224 @@ + + + + + + + + Tooling | + + + + + + +

    Tooling

    Less than 1 minute

    Pulsar tooling

    Here you will find a list of tools used by the Pulsar team and information about them.

    Continuous Integration

    Cirrus CIopen in new window

    Cirrus CI is used for Pulsar's continuous integration as well as for building application binaries.

    Codacyopen in new window

    Codacy is used to scan committed code for any issues that may have been missed.

    Currently though Codacy is only used on the following repositories:

    i18n (Internationalization)

    Crowdinopen in new window

    Crowdin will be used for Pulsar's internationalization efforts but exact details on this are still pending.

    Package Managers

    While most repositories you can easily tell what Package Manager is being used by checking for a specific lock file, there are some execptions to this that should be noted.

    Cloud Database

    The package-backendopen in new window currently uses DigitalOcean to host the PostgreSQL Pulsar Package Repositories data in the cloud.

    Cloud Compute

    Both the package-backendopen in new window and package-frontendopen in new window use Google App Engine to host the compute instance of these websites in the cloud.

    Additional Testing tools

    Action Pulsar Dependency Testeropen in new window

    Pulsar Dependency Tester is a GitHub action used to test changes of a core Pulsar Dependency if you need to determine how your core package dependency will run on the current version of the Pulsar Editor.

    This GitHub Action is a direct Pulsar replacement of the previous Action to test Atom Dependencies Setup Atomopen in new window.

    + + + diff --git a/docs/resources/website/index.html b/docs/resources/website/index.html new file mode 100644 index 0000000000..eb3fdad049 --- /dev/null +++ b/docs/resources/website/index.html @@ -0,0 +1,265 @@ + + + + + + + + Website | + + + + + + +

    Website

    Less than 1 minute

    Here is all the information on how the Pulsar website is built and configured.

    The website itself is built using VuePress v2open in new window and the Vuepress Hope Themeopen in new window.

    See the documentation there for generic items not covered in this guide.

    Building the website

    Prerequisites

    Clone the repository and install

    The website repository is https://github.com/pulsar-edit/pulsar-edit.github.ioopen in new window. Other assets are stored on other repositories but these will be downloaded automatically.

    git clone https://github.com/pulsar-edit/pulsar-edit.github.io
    +
    +cd pulsar-edit.github.io
    +
    +pnpm install
    +

    Running the website

    Once installed there are a number of scripts available to help you develop and build the site. Just prefix each command with pnpm. e,g, pnpm dev.

    dev

    Starts a watch task that will rebuild VuePress whenever a change has been made to the included Markdown and JavaScript files. Additionally, it launches the development server to test the results in the browser.

    build

    Creates an optimized production build.

    format

    Runs Prettieropen in new window over all Markdown files included in the repository to ensure consistent formatting.

    Note: This task will run automatically on every commit, so it can be ignored in most cases

    lint

    Lints all Markdown files in the repository.

    Note: This task will run automatically on every commit, so it can be ignored in most cases

    Notes

    Whilst dev does run a watch task, not everything will be updated and some changes will require you to shut down the server and start it again. For example adding @include files to another file will not rebuild automatically.

    If you wish to add new files from another repository via alias or @include then you will need to run pnpm update to get the latest version of the repository - the pnpm-lock.yml file will also be updated and must be part of any commit.

    Configuration files

    Nearly everything regarding the configuration of the website itself is controlled via the files found in the /docs/.vuepress directory.

    Currently we have three main configuration files.

    config.js

    This is the main configuration file for the website. This controls everything from the available settings, additional VuePress plugins, website description and various other elements to control various settings and plugins.

    For a full reference you can look at the documentation for VuePressopen in new window and the Hope Themeopen in new window.

    This file is broken down to to keep it tidy, the below files are imported to config.js to extend the configuration file without making it unwieldy.

    This controls the layout for the links in the top middle of the page and is always displayed.

    Items that go here are ones that we always want to be shown and should always be available for quick navigation.

    Each object can have a number of different values. The main ones we use are:

    • text: This sets the text for the label.
    • icon: Used to prefix an icon to the item. The theme supports the free FontAwesomeopen in new window font natively. To add an icon you need to specify its name without the first fa- e.g. fa-houseopen in new window would be specified as solid fa-house.
    • link: This controls where the link will actually take you. This can be a relative reference internal to the website or can be a URL to an external site.
    • children: Allows you to specify an array of child objects which will appear as a dropdown on mouseover. Use of this disables the link value. Each child can be defined as a full object as described here or can simply be a relative link from which the text will be set by the YAML title.

    For a full reference you can look at the documentation for VuePressopen in new window and the Hope Themeopen in new window.

    This control what is displayed in the sidebar on the left of the website. It is not displayed globally, only on directories which are set within the sidebar, currently we only have docs configured.

    Like navbar.js each sidebare item is configured as an object with a number of different values.

    • text: This sets the text for the label.
    • link: Controls the relative link for navigating the documents within the section
    • icon: Used to prefix an icon to the item. The theme supports the free FontAwesomeopen in new window font natively. To add an icon you need to specify its name without the first fa- e.g. fa-houseopen in new window would be specified as solid fa-house.
    • prefix: This adds a file path prefix to the item so its children do not need to specify the full path.
    • collapsable: (sic) Controls whether the item can be collapsed. Note: This a breaking change on a future version of the Hope Theme so will need to be renamed collapsible when updated, see: Issueopen in new window.
    • children: Takes an array of objects configured as above. Can also be set as a simple relative link in which case the title will be the YAML title of the document it links to.

    For a full reference you can look at the documentation for VuePressopen in new window and the Hope Themeopen in new window.

    Theme

    Within the styles/ directory you will find the .scss file for controlling various aspects of the website's theme.

    TODO: This will be updated when we actually modify these files extensively.

    File Organization

    One of the most important things to take note of when adding new documentation is where it should go within the website layout.

    The generalized overall layout of the website looks like this:

    docs
    +├── root level .md files
    +└── section folder
    +    ├── sections
    +    └── index.md
    +

    The general idea is that for files that can stand by themselves (for example the About Us page, Repositories etc.) they exist at the docs/ "root" level.

    For anything that is more complex it needs to have a section directory named appropriately, an index.md file within it and a sections directory.

    index.md

    This index file needs to have a YAML frontmatter to define, at a minimum, the title of the document. This is displayed as an H1 header for the page (note: subsequent H1 headers will be ignored so always start at H2).

    The rest of this index file will be used to display the actual content you want to show. This is done in a number of ways.

    First of all you can just include standard markdown. This is often used for introducing the section or adding one of our reusable components (e.g. a danger container).

    The rest of the file should consist of @includes which take data from other folders on the website and integrates it automatically. Usually this will be the sections files which will be covered next.

    e.g.

    @include(sections/file-organization.md)
    +

    However you can also use @include to feature files from a different section of the website or even files from outside the main site. We use this to include files which are maintained on the organization .github repoopen in new window for org-level documents.

    This is done by having a value defined on the config.js file which will provide an alias for us to use:

    if (file.startsWith("@orgdocs")) {
    +	return file.replace(
    +		"@orgdocs",
    +		path.resolve(__dirname, "../../node_modules/.github/")
    +	);
    +}
    +

    This allows us to include org-level docs by using this special alias.

    e.g.

    @include(@orgdocs/TOOLING.md)
    +

    Sections

    The sections directory is where we include the rest of the documents broken down by section. These should be self contained files which can be used alone but are designed to be included on the section page. This approach allows us flexibility with ordering as well as including these files in other places without needing to duplicate the material.

    Files here can be navigated to directly on the website but should not be linked to directly.

    These files shoud not have any YAML frontmatter as they will be included and shown as text.

    Assets

    Assets should be uploaded to the .github repoopen in new window repository so they can be used org-wide.

    An alias for this exists in config.js to access files from this repository.

    alias: {
    +  '@images': path.resolve(__dirname, '../../node_modules/.github/images')
    +},
    +

    So to include an image you simply need to use the standard markdown image link along with the alias:

    e.g.

    ![MyImage](@images/path/to/image.png)
    +

    Documentation style & reusable components

    The documentation for the project should maintain a consistent style where possible. This covers a number of aspects such as writing style, naming conventions, folder structure, links etc.

    All docs are currently in American English (en-US) but localization is planned.

    Structure

    The main structure for documentation can be seen in docs/docs. There are a number of "root" sections:

    • launch-manual - For the current main Pulsar documentation
    • packages - Currently holds wiki info from the atom package repos
    • resources - For other referenced docs
    • blog - For the website blog
    • atom-archive - For "as is" archived Atom documentation

    Within each section is an index.md which will usually contain info about each sub-section as well as links to them. These correspond to the second level items on the sidebar.

    Inside sections are the sub-sections which group more specific topics. These also have an index.md which corresponds to the third level item on the sidebar. This file is displayed on the website as a single long documenent but is actually created from a number of @include() lines which reference individual sections within the next sections directory. These should be relative to the location of index.md e.g. @include(sections/pulsar-packages.md). This file also contains the frontmatter for defining the title, language and description of the file and should also be the first level heading for the page. Here is also where you can place a container such as Under Construction to apply to the entire page.

    Inside the next sections directory should be the actual content of the document. Each section should start with a second level header and should not contain any frontmatter.

    Internal links can just be to the header (e.g.[Structure](#structure)), this to all sections included on the parent index.md so care should be made to not create any duplicate headers.

    All other links should be relative but do not need to reference the index file itself (e.g.[Installing](../getting-started#installing)) will find the heading #installing within the index file in the getting-started directory above.

    Images should be added to [pulsar-assets](https://github.com/pulsar-edit/pulsar-assets) and referenced from the package imported from it. This is done via an alias on the .vuepress/config.js file which adds most of the path for you: '@images': path.resolve(__dirname, '../../node_modules/pulsar-assets/images')
    so the link to your image would just be ![myImage](@images/pulsar/myImage.png "My Image").

    Naming

    The name of the application is Pulsar and should be capitalized as such. Whilst the website and GitHub org name is Pulsar-Edit, this should not be used within documentation outside of links to the GitHub org or website.

    Operating systems should be named as such:

    • Linux - All GNU/Linux distributions
    • macOS - Apple's current operating system family
    • Windows - Microsoft Windows family of operating systems

    This is also the order they should appear in within the tab switcher.

    When using the #tabs switcher they should be in this order.

    When referencing them inline then they should be abbreviated to the following, strongly emphasized and separated by a -:

    • Linux = LNX
    • macOS = MAC
    • Windows = WIN

    To keep order consistent it should be LNX -> MAC -> WIN. If instructions: common to two then it should either be LNX/MAC, LNX/WIN -> MAC/WIN

    For Linux we may sometimes need to reference particular distros or families of distributions. We currently use:

    • Ubuntu/Debian for all distributions based on Debian or Ubuntu
    • Fedora/RHEL for all distrububtions based on Fedora Linux & Red Hat Red Hat Enterprise Linux. This includes AlmaLinux, CentOS, Rocky Linux etc.
    • Arch - for all Arch based distributions such as Manjaro, Garuda, ArcoLinux etc.
    • OpenSUSE - for all OpenSUSE based distributions such as GeckoLinux

    We may need to add more in the future but generally users of less popular or more technical distributions such as Gentoo or NixOS understand how to adapt to their OS from the instructions above.

    Containers

    Where you want to display an info, warning or tab/code switcher in the document you should use a container with the ::: syntax.

    e.g.

    ::: tabs#filename
    +
    +@tab Linux
    +
    +Lorem ipsum dolor sit amet...
    +
    +@tab macOS
    +
    +Lorem ipsum dolor sit amet...
    +
    +@tab Windows
    +
    +Lorem ipsum dolor sit amet...
    +
    +:::
    +

    or

    ::: tip My Helpful Tip
    +
    +You might want to do X to get Y
    +
    +:::
    +

    You can also find a list of currently maintained preformatted containers for various purposes at pulsar-edit.github.io/common-text-blocks.mdopen in new window.

    See VuePress Hope documentationopen in new window

    Writing style

    TODO: Needs consensus

    Blog guide

    This is a guide on how to add a blog post to the website which will be shown on https://pulsar-edit.dev/article/open in new window.

    We are using the Vuepress Blog Pluginopen in new window which comes as part of our Vuepress Hope Theme with some light configuration to suit our purposes.

    This is all implemented in the main website repositoryopen in new window.

    Writing a new post

    • Create a new .md file in pulsar-edit.github.io/docs/blog.
      • This file should be named YYYYMMDD-<author>-<title>.md e.g 20221031-CreativeUsername-ThisIsMyBlogPost.md
    • The metadata displayed on the website is dependent on a number of items that are configured in the YAML frontmatter of the file. You may in theory omit any of these except the title field but it's strongly recommend that you use title, author, date, category and tag as the minimum as the others will default to false.
      • Frontmatter items supported currently are:
        • title - String: The displayed title of the post, consider this as H1
        • author - String: The name of the author to be displayed.
        • date - Date (ISO 8601): Allows display of date as well as enabling sorting on the timeline, set to the same as your filename date but with hyphens (e.g. 2022-10-31).
        • category - String (multiline): Enables filtering by category, this should be based on the subject of the post e.g. release, dev log, announcement. This is a multiline field if you want to set more than one category.
        • tag - String (multiline): Enables filtering by tags, this should be based on the content of the post and areas it touches on e.g. website, editor, config.
        • sticky - Bool: Enables "pinning" on the
        • star - Bool: Enables use of the star category for any important articles we want to remain visible.
        • article - Bool: You probably won't want to use this but setting this to false will exclude this page from appearing. This is set on the "example" blog post intentionally.
    • An excerpt can be added to the post by creating an html comment <!-- more -->. Anything above the comment will be treated as the excerpt and anything underneath will be the content of the post.
      • We are looking at implementing an auto excerpt feature but this doesn't seem to be working on the theme at the moment, this file will be updated if and when it is available.
    • The rest of the post is just standard Markdown - we are currently only supporting the standard features as per the MDEnhanceopen in new window plugin but if we need more features such as GFM then please discuss and we can look at adding it to the website.
    • Images are supported with the standard image syntax (![myImage](./assets/myImage.png)) and should be located in the blog/assets directory. You may also use standard html <img> tags, particularly if you wish to control the displayed size of the image.
    • Create a PR to the repo and make it obvious it is a blog post, by including [BLOG] in the title of your PR. Please don't submit it alongside any website changes.

    See example postopen in new window with everything above.

    Testing locally

    See building

    Website "Blog" page(s)

    The website itself has a number of features which the aforementioned frontmatter fields can influence.

    There is a "selector" at the top of the site which currently reads All, Star, Slides, Encrypted. The only ones we use are the first two and currently the theme doesn't support configuration of this. If necessary we may be able to hide it with CSS.
    Any article with Star set to true will be shown in the star category.

    On the right bar are 4 counters/links and filters for Articles, Category, Tag and Timeline.
    Category and Tag are used to filter articles depending on the categories submitted in the respective frontmatter fields. The Timeline allows views of blog posts over time according to the dates set in the date frontmatter field.

    Questions? Suggestions

    Just ask in Discord or GH Discussions and ping the @documentation team.

    This is very much barebones default config so please let us know if you have any suggestions as to how we can improve it.

    + + + diff --git a/docs/resources/website/sections/blog-guide.html b/docs/resources/website/sections/blog-guide.html new file mode 100644 index 0000000000..33f9a43985 --- /dev/null +++ b/docs/resources/website/sections/blog-guide.html @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + +

    About 3 min

    Blog guide

    This is a guide on how to add a blog post to the website which will be shown on https://pulsar-edit.dev/article/open in new window.

    We are using the Vuepress Blog Pluginopen in new window which comes as part of our Vuepress Hope Theme with some light configuration to suit our purposes.

    This is all implemented in the main website repositoryopen in new window.

    Writing a new post

    • Create a new .md file in pulsar-edit.github.io/docs/blog.
      • This file should be named YYYYMMDD-<author>-<title>.md e.g 20221031-CreativeUsername-ThisIsMyBlogPost.md
    • The metadata displayed on the website is dependent on a number of items that are configured in the YAML frontmatter of the file. You may in theory omit any of these except the title field but it's strongly recommend that you use title, author, date, category and tag as the minimum as the others will default to false.
      • Frontmatter items supported currently are:
        • title - String: The displayed title of the post, consider this as H1
        • author - String: The name of the author to be displayed.
        • date - Date (ISO 8601): Allows display of date as well as enabling sorting on the timeline, set to the same as your filename date but with hyphens (e.g. 2022-10-31).
        • category - String (multiline): Enables filtering by category, this should be based on the subject of the post e.g. release, dev log, announcement. This is a multiline field if you want to set more than one category.
        • tag - String (multiline): Enables filtering by tags, this should be based on the content of the post and areas it touches on e.g. website, editor, config.
        • sticky - Bool: Enables "pinning" on the
        • star - Bool: Enables use of the star category for any important articles we want to remain visible.
        • article - Bool: You probably won't want to use this but setting this to false will exclude this page from appearing. This is set on the "example" blog post intentionally.
    • An excerpt can be added to the post by creating an html comment <!-- more -->. Anything above the comment will be treated as the excerpt and anything underneath will be the content of the post.
      • We are looking at implementing an auto excerpt feature but this doesn't seem to be working on the theme at the moment, this file will be updated if and when it is available.
    • The rest of the post is just standard Markdown - we are currently only supporting the standard features as per the MDEnhanceopen in new window plugin but if we need more features such as GFM then please discuss and we can look at adding it to the website.
    • Images are supported with the standard image syntax (![myImage](./assets/myImage.png)) and should be located in the blog/assets directory. You may also use standard html <img> tags, particularly if you wish to control the displayed size of the image.
    • Create a PR to the repo and make it obvious it is a blog post, by including [BLOG] in the title of your PR. Please don't submit it alongside any website changes.

    See example postopen in new window with everything above.

    Testing locally

    See building

    Website "Blog" page(s)

    The website itself has a number of features which the aforementioned frontmatter fields can influence.

    There is a "selector" at the top of the site which currently reads All, Star, Slides, Encrypted. The only ones we use are the first two and currently the theme doesn't support configuration of this. If necessary we may be able to hide it with CSS.
    Any article with Star set to true will be shown in the star category.

    On the right bar are 4 counters/links and filters for Articles, Category, Tag and Timeline.
    Category and Tag are used to filter articles depending on the categories submitted in the respective frontmatter fields. The Timeline allows views of blog posts over time according to the dates set in the date frontmatter field.

    Questions? Suggestions

    Just ask in Discord or GH Discussions and ping the @documentation team.

    This is very much barebones default config so please let us know if you have any suggestions as to how we can improve it.

    + + + diff --git a/docs/resources/website/sections/building.html b/docs/resources/website/sections/building.html new file mode 100644 index 0000000000..46f3c26ac6 --- /dev/null +++ b/docs/resources/website/sections/building.html @@ -0,0 +1,228 @@ + + + + + + + + + + + + + + +

    Less than 1 minute

    Building the website

    Prerequisites

    Clone the repository and install

    The website repository is https://github.com/pulsar-edit/pulsar-edit.github.ioopen in new window. Other assets are stored on other repositories but these will be downloaded automatically.

    git clone https://github.com/pulsar-edit/pulsar-edit.github.io
    +
    +cd pulsar-edit.github.io
    +
    +pnpm install
    +

    Running the website

    Once installed there are a number of scripts available to help you develop and build the site. Just prefix each command with pnpm. e,g, pnpm dev.

    dev

    Starts a watch task that will rebuild VuePress whenever a change has been made to the included Markdown and JavaScript files. Additionally, it launches the development server to test the results in the browser.

    build

    Creates an optimized production build.

    format

    Runs Prettieropen in new window over all Markdown files included in the repository to ensure consistent formatting.

    Note: This task will run automatically on every commit, so it can be ignored in most cases

    lint

    Lints all Markdown files in the repository.

    Note: This task will run automatically on every commit, so it can be ignored in most cases

    Notes

    Whilst dev does run a watch task, not everything will be updated and some changes will require you to shut down the server and start it again. For example adding @include files to another file will not rebuild automatically.

    If you wish to add new files from another repository via alias or @include then you will need to run pnpm update to get the latest version of the repository - the pnpm-lock.yml file will also be updated and must be part of any commit.

    + + + diff --git a/docs/resources/website/sections/configuration-files.html b/docs/resources/website/sections/configuration-files.html new file mode 100644 index 0000000000..ca0ed754aa --- /dev/null +++ b/docs/resources/website/sections/configuration-files.html @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + +

    About 2 min

    Configuration files

    Nearly everything regarding the configuration of the website itself is controlled via the files found in the /docs/.vuepress directory.

    Currently we have three main configuration files.

    config.js

    This is the main configuration file for the website. This controls everything from the available settings, additional VuePress plugins, website description and various other elements to control various settings and plugins.

    For a full reference you can look at the documentation for VuePressopen in new window and the Hope Themeopen in new window.

    This file is broken down to to keep it tidy, the below files are imported to config.js to extend the configuration file without making it unwieldy.

    This controls the layout for the links in the top middle of the page and is always displayed.

    Items that go here are ones that we always want to be shown and should always be available for quick navigation.

    Each object can have a number of different values. The main ones we use are:

    • text: This sets the text for the label.
    • icon: Used to prefix an icon to the item. The theme supports the free FontAwesomeopen in new window font natively. To add an icon you need to specify its name without the first fa- e.g. fa-houseopen in new window would be specified as solid fa-house.
    • link: This controls where the link will actually take you. This can be a relative reference internal to the website or can be a URL to an external site.
    • children: Allows you to specify an array of child objects which will appear as a dropdown on mouseover. Use of this disables the link value. Each child can be defined as a full object as described here or can simply be a relative link from which the text will be set by the YAML title.

    For a full reference you can look at the documentation for VuePressopen in new window and the Hope Themeopen in new window.

    This control what is displayed in the sidebar on the left of the website. It is not displayed globally, only on directories which are set within the sidebar, currently we only have docs configured.

    Like navbar.js each sidebare item is configured as an object with a number of different values.

    • text: This sets the text for the label.
    • link: Controls the relative link for navigating the documents within the section
    • icon: Used to prefix an icon to the item. The theme supports the free FontAwesomeopen in new window font natively. To add an icon you need to specify its name without the first fa- e.g. fa-houseopen in new window would be specified as solid fa-house.
    • prefix: This adds a file path prefix to the item so its children do not need to specify the full path.
    • collapsable: (sic) Controls whether the item can be collapsed. Note: This a breaking change on a future version of the Hope Theme so will need to be renamed collapsible when updated, see: Issueopen in new window.
    • children: Takes an array of objects configured as above. Can also be set as a simple relative link in which case the title will be the YAML title of the document it links to.

    For a full reference you can look at the documentation for VuePressopen in new window and the Hope Themeopen in new window.

    Theme

    Within the styles/ directory you will find the .scss file for controlling various aspects of the website's theme.

    TODO: This will be updated when we actually modify these files extensively.

    + + + diff --git a/docs/resources/website/sections/document-style.html b/docs/resources/website/sections/document-style.html new file mode 100644 index 0000000000..53053d3142 --- /dev/null +++ b/docs/resources/website/sections/document-style.html @@ -0,0 +1,243 @@ + + + + + + + + + + + + + + +

    About 3 min

    Documentation style & reusable components

    The documentation for the project should maintain a consistent style where possible. This covers a number of aspects such as writing style, naming conventions, folder structure, links etc.

    All docs are currently in American English (en-US) but localization is planned.

    Structure

    The main structure for documentation can be seen in docs/docs. There are a number of "root" sections:

    • launch-manual - For the current main Pulsar documentation
    • packages - Currently holds wiki info from the atom package repos
    • resources - For other referenced docs
    • blog - For the website blog
    • atom-archive - For "as is" archived Atom documentation

    Within each section is an index.md which will usually contain info about each sub-section as well as links to them. These correspond to the second level items on the sidebar.

    Inside sections are the sub-sections which group more specific topics. These also have an index.md which corresponds to the third level item on the sidebar. This file is displayed on the website as a single long documenent but is actually created from a number of @include() lines which reference individual sections within the next sections directory. These should be relative to the location of index.md e.g. @include(sections/pulsar-packages.md). This file also contains the frontmatter for defining the title, language and description of the file and should also be the first level heading for the page. Here is also where you can place a container such as Under Construction to apply to the entire page.

    Inside the next sections directory should be the actual content of the document. Each section should start with a second level header and should not contain any frontmatter.

    Internal links can just be to the header (e.g.[Structure](#structure)), this to all sections included on the parent index.md so care should be made to not create any duplicate headers.

    All other links should be relative but do not need to reference the index file itself (e.g.[Installing](../getting-started#installing)) will find the heading #installing within the index file in the getting-started directory above.

    Images should be added to [pulsar-assets](https://github.com/pulsar-edit/pulsar-assets) and referenced from the package imported from it. This is done via an alias on the .vuepress/config.js file which adds most of the path for you: '@images': path.resolve(__dirname, '../../node_modules/pulsar-assets/images')
    so the link to your image would just be ![myImage](@images/pulsar/myImage.png "My Image").

    Naming

    The name of the application is Pulsar and should be capitalized as such. Whilst the website and GitHub org name is Pulsar-Edit, this should not be used within documentation outside of links to the GitHub org or website.

    Operating systems should be named as such:

    • Linux - All GNU/Linux distributions
    • macOS - Apple's current operating system family
    • Windows - Microsoft Windows family of operating systems

    This is also the order they should appear in within the tab switcher.

    When using the #tabs switcher they should be in this order.

    When referencing them inline then they should be abbreviated to the following, strongly emphasized and separated by a -:

    • Linux = LNX
    • macOS = MAC
    • Windows = WIN

    To keep order consistent it should be LNX -> MAC -> WIN. If instructions: common to two then it should either be LNX/MAC, LNX/WIN -> MAC/WIN

    For Linux we may sometimes need to reference particular distros or families of distributions. We currently use:

    • Ubuntu/Debian for all distributions based on Debian or Ubuntu
    • Fedora/RHEL for all distrububtions based on Fedora Linux & Red Hat Red Hat Enterprise Linux. This includes AlmaLinux, CentOS, Rocky Linux etc.
    • Arch - for all Arch based distributions such as Manjaro, Garuda, ArcoLinux etc.
    • OpenSUSE - for all OpenSUSE based distributions such as GeckoLinux

    We may need to add more in the future but generally users of less popular or more technical distributions such as Gentoo or NixOS understand how to adapt to their OS from the instructions above.

    Containers

    Where you want to display an info, warning or tab/code switcher in the document you should use a container with the ::: syntax.

    e.g.

    ::: tabs#filename
    +
    +@tab Linux
    +
    +Lorem ipsum dolor sit amet...
    +
    +@tab macOS
    +
    +Lorem ipsum dolor sit amet...
    +
    +@tab Windows
    +
    +Lorem ipsum dolor sit amet...
    +
    +:::
    +

    or

    ::: tip My Helpful Tip
    +
    +You might want to do X to get Y
    +
    +:::
    +

    You can also find a list of currently maintained preformatted containers for various purposes at pulsar-edit.github.io/common-text-blocks.mdopen in new window.

    See VuePress Hope documentationopen in new window

    Writing style

    TODO: Needs consensus

    + + + diff --git a/docs/resources/website/sections/file-organization.html b/docs/resources/website/sections/file-organization.html new file mode 100644 index 0000000000..2f9e3a2a8d --- /dev/null +++ b/docs/resources/website/sections/file-organization.html @@ -0,0 +1,294 @@ + + + + + + + + + + + + + + +

    About 2 min

    File Organization

    One of the most important things to take note of when adding new documentation is where it should go within the website layout.

    The generalized overall layout of the website looks like this:

    docs
    +├── root level .md files
    +└── section folder
    +    ├── sections
    +    └── index.md
    +

    The general idea is that for files that can stand by themselves (for example the About Us page, Repositories etc.) they exist at the docs/ "root" level.

    For anything that is more complex it needs to have a section directory named appropriately, an index.md file within it and a sections directory.

    index.md

    This index file needs to have a YAML frontmatter to define, at a minimum, the title of the document. This is displayed as an H1 header for the page (note: subsequent H1 headers will be ignored so always start at H2).

    The rest of this index file will be used to display the actual content you want to show. This is done in a number of ways.

    First of all you can just include standard markdown. This is often used for introducing the section or adding one of our reusable components (e.g. a danger container).

    The rest of the file should consist of @includes which take data from other folders on the website and integrates it automatically. Usually this will be the sections files which will be covered next.

    e.g.

    
    +File not found
    +
    +

    However you can also use @include to feature files from a different section of the website or even files from outside the main site. We use this to include files which are maintained on the organization .github repoopen in new window for org-level documents.

    This is done by having a value defined on the config.js file which will provide an alias for us to use:

    if (file.startsWith("@orgdocs")) {
    +	return file.replace(
    +		"@orgdocs",
    +		path.resolve(__dirname, "../../node_modules/.github/")
    +	);
    +}
    +

    This allows us to include org-level docs by using this special alias.

    e.g.

    @include-push(/home/runner/work/pulsar-edit.github.io/pulsar-edit.github.io/node_modules/.github)
    +# Pulsar tooling
    +
    +Here you will find a list of tools used by the Pulsar team and information about them.
    +
    +## Continuous Integration
    +
    +### [Cirrus CI](https://cirrus-ci.com/github/pulsar-edit/pulsar)
    +
    +Cirrus CI is used for Pulsar's continuous integration as well as for building application binaries.
    +
    +### [Codacy](https://app.codacy.com/gh/pulsar-edit/repositories)
    +
    +Codacy is used to scan committed code for any issues that may have been missed.
    +
    +Currently though Codacy is only used on the following repositories:
    +
    +* [ppm](https://app.codacy.com/gh/pulsar-edit/ppm/dashboard)
    +* [pulsar](https://app.codacy.com/gh/pulsar-edit/pulsar/dashboard)
    +* [background-tips](https://app.codacy.com/gh/pulsar-edit/background-tips/dashboard)
    +* [autocomplete-plus](https://app.codacy.com/gh/pulsar-edit/autocomplete-plus/dashboard)
    +
    +## i18n (Internationalization)
    +
    +### [Crowdin](https://crowdin.pulsar-edit.dev/)
    +
    +Crowdin will be used for Pulsar's internationalization efforts but exact details on this are still pending.
    +
    +## Package Managers
    +
    +While most repositories you can easily tell what Package Manager is being used by checking for a specific lock file, there are some execptions to this that should be noted.
    +
    +* [pulsar](https://github.com/pulsar-edit/pulsar) uses `yarn` as its Package Manager.
    +* [pulsar-edit.github.io](https://github.com/pulsar-edit/pulsar-edit.github.io) uses `pnpm` as its Package Manager.
    +
    +## Cloud Database
    +
    +The [package-backend](https://github.com/pulsar-edit/package-backend) currently uses DigitalOcean to host the PostgreSQL Pulsar Package Repositories data in the cloud.
    +
    +## Cloud Compute
    +
    +Both the [package-backend](https://github.com/pulsar-edit/package-backend) and [package-frontend](https://github.com/pulsar-edit/package-frontend) use Google App Engine to host the compute instance of these websites in the cloud.
    +
    +## Additional Testing tools
    +
    +### [Action Pulsar Dependency Tester](https://github.com/marketplace/actions/action-pulsar-dependency-tester)
    +
    +Pulsar Dependency Tester is a GitHub action used to test changes of a core Pulsar Dependency if you need to determine how your core package dependency will run on the current version of the Pulsar Editor.
    +
    +This GitHub Action is a direct Pulsar replacement of the previous Action to test Atom Dependencies [Setup Atom](https://github.com/marketplace/actions/setup-atom).
    +
    +@include-pop()
    +
    +

    Sections

    The sections directory is where we include the rest of the documents broken down by section. These should be self contained files which can be used alone but are designed to be included on the section page. This approach allows us flexibility with ordering as well as including these files in other places without needing to duplicate the material.

    Files here can be navigated to directly on the website but should not be linked to directly.

    These files shoud not have any YAML frontmatter as they will be included and shown as text.

    Assets

    Assets should be uploaded to the .github repoopen in new window repository so they can be used org-wide.

    An alias for this exists in config.js to access files from this repository.

    alias: {
    +  '@images': path.resolve(__dirname, '../../node_modules/.github/images')
    +},
    +

    So to include an image you simply need to use the standard markdown image link along with the alias:

    e.g.

    ![MyImage](@images/path/to/image.png)
    +
    + + + diff --git a/donate.html b/donate.html new file mode 100644 index 0000000000..51fe99e630 --- /dev/null +++ b/donate.html @@ -0,0 +1,223 @@ + + + + + + + + Donate to Pulsar | + + + + + + +

    Donate to Pulsar

    Less than 1 minute

    Pulsar will always be free and fully open to the community but we do have some running costs associated with hosting the package repository along with other expenses.

    If you wish to contribute to the running costs of the project we have an OpenCollectiveopen in new window where you can safely donate and view how the money is being used.

    If you'd rather contribute via GitHub Sponsorsopen in new window you can do this on our GitHub profile.

    We are currently talking about other donation platforms that we might support so watch this space!

    + + + diff --git a/download-preselect.js b/download-preselect.js new file mode 100644 index 0000000000..d5159a5790 --- /dev/null +++ b/download-preselect.js @@ -0,0 +1,22 @@ +(function() { + // Only run if we are on the downloads page + const url = window.location.pathname; + if (url.indexOf('download.html') === -1) { return } + // Supported targets that correspond to the table headers (case-sensitive) + const targets = [ + // Exact match for "Windows NT" + {agent: /Windows NT/ig, header: 'Windows'}, + // Match for "Mac OS X" + {agent: /Mac OS X/ig, header: 'macOS'}, + // Match for "Linux" but not with "Android" + {agent: /(?!.*Android).*Linux.*$/ig, header: 'Linux'} + ]; + // Obtain the browser's user agent + const agent = navigator.userAgent; + // Get all details accordion tables + const detailsElements = Array.from(document.querySelectorAll('details.custom-container.details')); + // Find an instance of the target in the user agent (case-insensitive) + const target = targets.find(i => agent.match(i.agent) ); + // Set the open property on the element who's summary header corresponds to the found target + detailsElements.find(i => i.childNodes[0].innerHTML === target?.header)?.setAttribute('open', ''); +})(); diff --git a/download.html b/download.html new file mode 100644 index 0000000000..ef159844ad --- /dev/null +++ b/download.html @@ -0,0 +1,223 @@ + + + + + + + + Pulsar Downloads | + + + + + + +

    Pulsar Downloads

    About 5 min

    "Rolling" and "Regular" Releases

    We have "Rolling" and "Regular" releases to choose from. These differ only in the frequency of updates they receive.

    Note: There are no automatic updates at this time, regardless of which type of download you choose.

    Rolling Release

    These releases are built on each push to our master branch and automatically built by GitHub Actionsopen in new window and Cirrus CIopen in new window. These releases come with the latest fixes, updates and improvements so this is a great choice if you want to get the latest features as soon as possible.

    While we do our best to review and test each PR thoroughly before merging there is a chance that something falls through the cracks in which case be prepared to upgrade or switch to our Regular Releases until it is fixed.

    See below for links to the latest binaries for each OS. If you want to download manually or pick a binary from another branch or PR, then follow the manual instructions.

    Linux

    Info

    We currently have no Rolling Releases for Linux package managers - see Regular Releases for these instead.

    x86_64 - For most desktops and laptops with Intel or AMD processors

    PackageDistribution
    debopen in new windowDebian/Ubuntu etc.
    rpmopen in new windowFedora/RHEL etc.
    Appimageopen in new window[1][2]All distributions
    tar.gzopen in new windowAll distributions

    ARM_64 - For ARM based devices - Raspberry Pi, Pinebook etc.

    PackageDistribution
    debopen in new windowDebian/Ubuntu etc.
    rpmopen in new windowFedora/RHEL etc.
    Appimageopen in new window[1][2]All distributions
    tar.gzopen in new windowAll distributions

    [1] Appimage may require --no-sandbox as an argument to run correctly on some systems.
    [2] Some distributions no longer ship with libfuse2 which Appimage requires to run. You may need to install this manually, e.g on Ubuntu >=22.04 apt install libfuse2.

    macOS

    Silicon - For Apple Silicon (M1/M2) macs

    PackageType
    dmgopen in new windowDMG installer
    zipopen in new windowZip archive

    Intel - For Intel macs

    PackageType
    dmgopen in new windowDMG installer
    zipopen in new windowZip archive
    Windows

    Info

    We currently have no Rolling Releases for Windows package managers - see Regular Releases for these instead.

    Current binaries are not signed so will produce an error with Windows Smartscreen "Windows protected your PC"... You can bypass this by clicking "More info" then "Run anyway".

    PackageType
    Setupopen in new windowInstaller
    Portableopen in new windowPortable (no install)

    Regular Releases

    These releases are put out regularly according to milestones defined by the Pulsar team (currently roughly monthly).

    Compared to our Rolling Release these offer a more understood state by the team in terms of support. However these will sometimes feature issues that have already been resolved in our Rolling Release so if a particular fix or feature is important to you it may be worth swapping to one of those instead.

    Current version is v1.119.0open in new window.

    Linux

    x86_64 - For most desktops and laptops with Intel or AMD processors

    PackageDistribution
    debopen in new windowDebian/Ubuntu etc.
    rpmopen in new windowFedora/RHEL etc.
    AppImageopen in new window[1][2]All distributions
    tar.gzopen in new windowAll distributions

    ARM_64 - For ARM based devices - Raspberry Pi, Pinebook etc.

    PackageDistribution
    debopen in new windowDebian/Ubuntu etc.
    rpmopen in new windowFedora/RHEL etc.
    AppImageopen in new window[1][2]All distributions
    tar.gzopen in new windowAll distributions

    [1] Appimage may require --no-sandbox as an argument to run correctly on some systems.
    [2] Some distributions no longer ship with libfuse2 which Appimage requires to run. You may need to install this manually, e.g on Ubuntu >=22.04 apt install libfuse2.

    Package Managers

    Package ManagerDistributionCommand
    deb-getopen in new windowDebian/Ubuntu etc.deb-get install pulsar
    macOS

    Silicon - For Apple Silicon (M1/M2) macs

    PackageType
    dmgopen in new windowDMG installer
    zipopen in new windowZip archive

    Intel - For Intel macs

    PackageType
    dmgopen in new windowDMG installer
    zipopen in new windowZip archive
    Windows

    Info

    Current binaries are not signed so will produce an error with Windows Smartscreen "Windows protected your PC"... You can bypass this by clicking "More info" then "Run anyway".

    PackageType
    Setupopen in new windowInstaller
    Portableopen in new windowPortable (no install)
    Package ManagerCommand
    Chocolateyopen in new windowchoco install pulsar

    Manual download

    Binaries are built from a number of different branches and PRs but you should stick to the master branch releases for the most stable ones unless you know exactly what you are looking for.

    We currently build on both GitHub Actions and Cirrus CI:

    • GitHub Actions is used for building the majority of our binaries and builds are run on every commit or PR.
    • Cirrus CI is only used for building Apple silicon (macOS M1/M2) and ARM Linux binaries. These are run less frequently (every Monday, Wednesday, and Friday).

    To download a binary produced by GitHub Actions then follow the below steps:

    • Navigate to the pulsar-edit/pulsar-rolling-releasesopen in new window releases.
    • Select the most recent release (or for a specific release reference the timestamp part of the version e.g. 1.109.2023091606).
      • Most binaries will be available with every release but Apple silicon and ARM Linux binaries will not. You may need to look for earlier releases.
    • Choose the binary you wish to download from the Assets section (you may need click the Show all x assets link to display them all). See below for descriptions of all available binaries.

    To download a binary from Cirrus CI (Apple silicon and ARM Linux only) please follow the below steps:

    • Go to the master branch Pulsar Cirrus CI pageopen in new window.
    • Select the latest successful build (check for a finished green check mark ✅ on the right side of the page - make sure you do not select a failed one).
    • Select your system from the list of options:
      • arm-linux - ARM based Linux systems (Raspberry Pi etc.)
      • silicon_mac - M1 and M2 chip Apple Macs
    • Select binary > binaries from the Artifacts pane
    • Download the binary you require for your system:
    + + + diff --git a/encrypted/index.html b/encrypted/index.html new file mode 100644 index 0000000000..35f044b11a --- /dev/null +++ b/encrypted/index.html @@ -0,0 +1,223 @@ + + + + + + + + Encrypted | + + + + + + +
    + + + diff --git a/favicon.ico b/favicon.ico new file mode 100644 index 0000000000..5c4b109145 Binary files /dev/null and b/favicon.ico differ diff --git a/index.html b/index.html new file mode 100644 index 0000000000..dc7e4b773b --- /dev/null +++ b/index.html @@ -0,0 +1,223 @@ + + + + + + + + Home | + + + + + + +

    A Community-led Hyper-Hackable Text Editor

    Download 📥Documentation 📖Donate 🎁

    Cross-platform Editing

    Pulsar works across operating systems. Use it on OS X, Windows, or Linux.

    Built-in package manager

    Search and install new packages or create your own right from Pulsar.

    Smart Autocompletion

    Pulsar helps you write code faster with a smart and flexible autocomplete.

    File system browser

    Easily browse and open a single file, a whole project, or multiple projects in one window.

    Multiple panes

    Split your Pulsar interface into multiple panes to compare and edit code across files.

    Find and replace

    Find, preview, and replace text as you type in a file or across all your projects.

    Notices

    Welcome

    Welcome to all new visitors!

    Downloads and Releases

    We are constantly working on the editor and maintain both rolling and regular releases. You can find out more about the differences and download Pulsar on our downloads pageopen in new window and following the instructions.

    App Updates

    Currently Pulsar does not support automatic app updates. There are, however, automated notifications of new versions, which include links and instructions so you can easily get the new versions when they come out. (These notifications can be disabled if you wish.) Beyond that, new versions can be obtained via the Download links here on our website, or from our CI (continuous integration) pages as explained on our Download page.

    Blog

    For updates on the goings-on around here, please do check out our blogopen in new window!

    Packages

    One of our first and biggest tasks was to replace the closed source Atom.io package repository with our own so that users would still be able to download from the huge package ecosystem.

    Searching and downloading from the package repositoryopen in new window is now fully supported, as is publishing/updating/deleting packages. If you experience any issues, please feel free to report this to the Pulsar Team.

    Support and Community

    If you have any problems when using Pulsar, then please do let us know in one of our community areasopen in new window or as a GitHub issueopen in new window.


    Our Sponsors

    Thank you to the following organizations for sponsoring our project!

    + + + diff --git a/logo-name-footer.svg b/logo-name-footer.svg new file mode 100644 index 0000000000..7f210c91ba --- /dev/null +++ b/logo-name-footer.svg @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/logo-name-navbar-dark.svg b/logo-name-navbar-dark.svg new file mode 100644 index 0000000000..f3e5eddca8 --- /dev/null +++ b/logo-name-navbar-dark.svg @@ -0,0 +1,125 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/logo-name-navbar-light.svg b/logo-name-navbar-light.svg new file mode 100644 index 0000000000..b9213213a6 --- /dev/null +++ b/logo-name-navbar-light.svg @@ -0,0 +1,125 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/logo-only-light.svg b/logo-only-light.svg new file mode 100644 index 0000000000..0cb4a83197 --- /dev/null +++ b/logo-only-light.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/repos.html b/repos.html new file mode 100644 index 0000000000..ca10dcbc3f --- /dev/null +++ b/repos.html @@ -0,0 +1,223 @@ + + + + + + + + Pulsar Repositories | + + + + + + +

    Pulsar Repositories

    Less than 1 minute

    The Pulsar project is split up into a number of repositories, all hosted on GitHub.

    The very nature of the way Pulsar works means that there are a large number of packages that come together to make up the editor and each of these have their own repository.

    We also have a number of other repositories to host the website, package backend & frontend and our organization wide documentation & templates.

    All of the Pulsar repositories can be found under the organization at https://github.com/pulsar-editopen in new window but below is a list of some of the more regularly used ones you may wish to visit in order to contribute to the project. For a more comprehensive list you can visit the org-level REPOS fileopen in new window.

    Editor

    Websites and services

    + + + diff --git a/sitemap.xml b/sitemap.xml new file mode 100644 index 0000000000..896207535a --- /dev/null +++ b/sitemap.xml @@ -0,0 +1 @@ +https://pulsar-edit.dev/about.html2023-10-24T14:38:31.000Zweekly0.8https://pulsar-edit.dev/community.html2023-09-16T16:59:07.000Zweekly0.9https://pulsar-edit.dev/donate.html2023-01-13T02:26:36.000Zweekly0.8https://pulsar-edit.dev/download.html2024-07-17T20:00:22.000Zdaily0.9https://pulsar-edit.dev/2024-02-01T19:05:54.000Zdaily1.0https://pulsar-edit.dev/repos.html2022-11-26T04:46:56.000Z0.4https://pulsar-edit.dev/blog/20221112-Daeraxa-ExamplePost.html2022-11-13T03:04:05.000Zhttps://pulsar-edit.dev/blog/20221127-confused-Techie-SunsetMisadventureBackend.html2022-11-28T15:10:50.000Zhttps://pulsar-edit.dev/blog/20221208-Daeraxa-DistroTubeVideo.html2022-12-08T19:54:06.000Zhttps://pulsar-edit.dev/blog/20221215-confused-Techie-v1.100.1-beta.html2023-01-16T02:25:21.000Zhttps://pulsar-edit.dev/blog/20230114-confused-Techie-v1.101.0-beta.html2023-03-26T20:14:57.000Zhttps://pulsar-edit.dev/blog/20230201-Daeraxa-FebUpdate.html2023-02-01T21:27:28.000Zhttps://pulsar-edit.dev/blog/20230209-mauricioszabo-tree-sitter-part-1.html2023-02-12T19:13:48.000Zhttps://pulsar-edit.dev/blog/20230215-Daeraxa-v1.102.0.html2023-02-17T02:58:47.000Zhttps://pulsar-edit.dev/blog/20230216-Daeraxa-ReleaseStrategyUpdate.html2023-03-26T20:14:57.000Zhttps://pulsar-edit.dev/blog/20230227-Daeraxa-Survey1.html2023-02-27T02:31:41.000Zhttps://pulsar-edit.dev/blog/20230301-Daeraxa-MarUpdate.html2023-03-01T00:54:38.000Zhttps://pulsar-edit.dev/blog/20230315-Daeraxa-v1.103.0.html2023-03-17T18:12:26.000Zhttps://pulsar-edit.dev/blog/20230319-confused-Techie-HowLicenseNoneDeletedPackages.html2023-03-20T23:21:20.000Zhttps://pulsar-edit.dev/blog/20230326-Daeraxa-Survey1-Results.html2023-03-27T16:47:05.000Zhttps://pulsar-edit.dev/blog/20230401-Daeraxa-AprUpdate.html2023-03-31T00:51:07.000Zhttps://pulsar-edit.dev/blog/20230401-confused-Techie-PON.html2023-04-02T00:04:02.000Zhttps://pulsar-edit.dev/blog/20230418-Daeraxa-v1.104.0.html2023-04-18T00:28:29.000Zhttps://pulsar-edit.dev/blog/20230430-Daeraxa-Survey2.html2023-05-01T01:41:41.000Zhttps://pulsar-edit.dev/blog/20230501-Daeraxa-MayUpdate.html2023-05-05T00:04:23.000Zhttps://pulsar-edit.dev/blog/20230516-Daeraxa-v1.105.0.html2023-05-17T20:02:22.000Zhttps://pulsar-edit.dev/blog/20230525-Daeraxa-Survey2-Results.html2023-05-25T00:32:41.000Zhttps://pulsar-edit.dev/blog/20230601-Daeraxa-JuneUpdate.html2023-06-01T08:20:21.000Zhttps://pulsar-edit.dev/blog/20230610-Daeraxa-2kStars.html2023-06-10T02:05:21.000Zhttps://pulsar-edit.dev/blog/20230616-Daeraxa-v1.106.0.html2023-06-16T17:46:41.000Zhttps://pulsar-edit.dev/blog/20230701-Daeraxa-JulyUpdate.html2023-07-16T00:10:16.000Zhttps://pulsar-edit.dev/blog/20230715-DeeDeeG-v1.107.0.html2023-07-16T01:08:08.000Zhttps://pulsar-edit.dev/blog/20230716-Daeraxa-v1.107.1.html2023-07-17T01:19:11.000Zhttps://pulsar-edit.dev/blog/20230801-Daeraxa-AugustUpdate.html2023-08-03T03:17:15.000Zhttps://pulsar-edit.dev/blog/20230816-DeeDeeG-v1.108.0.html2023-08-17T00:28:51.000Zhttps://pulsar-edit.dev/blog/20230825-Daeraxa-ChocolateyUpdate.html2023-09-03T00:23:08.000Zhttps://pulsar-edit.dev/blog/20230903-confused-Techie-pulsars-ci.html2023-09-08T01:15:19.000Zhttps://pulsar-edit.dev/blog/20230904-Daeraxa-SeptemberUpdate.html2023-09-05T01:39:35.000Zhttps://pulsar-edit.dev/blog/20230916-Daeraxa-v1.109.0.html2023-09-16T13:24:13.000Zhttps://pulsar-edit.dev/blog/20230917-Daeraxa-LemmyCommunity.html2023-09-16T16:59:07.000Zhttps://pulsar-edit.dev/blog/20230925-savetheclocktower-modern-tree-sitter-part-1.html2023-09-25T21:46:30.000Zhttps://pulsar-edit.dev/blog/20230927-savetheclocktower-modern-tree-sitter-part-2.html2023-09-27T16:50:27.000Zhttps://pulsar-edit.dev/blog/20231004-Daeraxa-OctoberUpdate.html2023-10-08T22:47:33.000Zhttps://pulsar-edit.dev/blog/20231013-savetheclocktower-modern-tree-sitter-part-3.html2023-10-12T19:30:47.000Zhttps://pulsar-edit.dev/blog/20231016-Daeraxa-v1.110.0.html2023-10-16T16:39:30.000Zhttps://pulsar-edit.dev/blog/20231031-savetheclocktower-modern-tree-sitter-part-4.html2023-11-01T03:38:42.000Zhttps://pulsar-edit.dev/blog/20231109-Daeraxa-NovemberUpdate.html2023-11-10T00:56:04.000Zhttps://pulsar-edit.dev/blog/20231110-savetheclocktower-modern-tree-sitter-part-5.html2023-11-11T07:57:43.000Zhttps://pulsar-edit.dev/blog/20231116-Daeraxa-v1.111.0.html2023-11-16T23:25:10.000Zhttps://pulsar-edit.dev/blog/20231212-Daeraxa-DecemberUpdate.html2023-12-12T03:07:56.000Zhttps://pulsar-edit.dev/blog/20231216-Daeraxa-v1.112.0.html2023-12-16T17:04:27.000Zhttps://pulsar-edit.dev/blog/20231219-DeeDeeG-v1.112.1.html2023-12-19T06:48:42.000Zhttps://pulsar-edit.dev/blog/20240112-Daeraxa-JanuaryUpdate.html2024-01-14T03:13:35.000Zhttps://pulsar-edit.dev/blog/20240115-Daeraxa-v1.113.0.html2024-01-16T04:40:14.000Zhttps://pulsar-edit.dev/blog/20240122-savetheclocktower-modern-tree-sitter-part-6.html2024-01-23T02:15:59.000Zhttps://pulsar-edit.dev/blog/20240124-mauricioszabo-the-quest-for-electron-lts.html2024-01-29T00:33:18.000Zhttps://pulsar-edit.dev/blog/20240201-Daeraxa-FebruaryUpdate.html2024-02-02T05:03:19.000Zhttps://pulsar-edit.dev/blog/20240215-Daeraxa-v1.114.0.html2024-02-22T20:26:42.000Zhttps://pulsar-edit.dev/blog/20240323-savetheclocktower-v1.115.0.html2024-03-23T04:22:24.000Zhttps://pulsar-edit.dev/blog/20240417-confused-Techie-v1.116.0.html2024-04-17T23:55:29.000Zhttps://pulsar-edit.dev/blog/20240520-confused-Techie-v1.117.0.html2024-05-22T01:02:46.000Zhttps://pulsar-edit.dev/blog/20240616-confused-Techie-v1.118.0.html2024-06-17T03:01:28.000Zhttps://pulsar-edit.dev/blog/20240717-confused-Techie-v1.119.0.html2024-07-18T04:13:21.000Zhttps://pulsar-edit.dev/docs/2022-11-13T02:31:16.000Zhttps://pulsar-edit.dev/docs/atom-archive/2022-11-26T04:46:56.000Z0.2https://pulsar-edit.dev/docs/launch-manual/2022-11-21T01:02:34.000Zhttps://pulsar-edit.dev/docs/packages/2023-01-16T03:46:41.000Zhttps://pulsar-edit.dev/docs/resources/2022-11-13T02:31:16.000Zhttps://pulsar-edit.dev/docs/atom-archive/api/2022-11-26T04:46:56.000Z0.1https://pulsar-edit.dev/docs/atom-archive/atom-server-side-apis/2022-11-26T04:46:56.000Z0.1https://pulsar-edit.dev/docs/atom-archive/behind-atom/2022-11-26T04:46:56.000Z0.1https://pulsar-edit.dev/docs/atom-archive/faq/2022-11-26T04:46:56.000Z0.1https://pulsar-edit.dev/docs/atom-archive/getting-started/2022-11-26T04:46:56.000Z0.1https://pulsar-edit.dev/docs/atom-archive/hacking-atom/2022-11-26T04:46:56.000Z0.1https://pulsar-edit.dev/docs/atom-archive/resources/2022-11-26T04:46:56.000Z0.1https://pulsar-edit.dev/docs/atom-archive/shadow-dom/2022-11-26T04:46:56.000Z0.1https://pulsar-edit.dev/docs/atom-archive/upgrading-to-1-0-apis/2022-11-26T04:46:56.000Z0.1https://pulsar-edit.dev/docs/atom-archive/using-atom/2022-11-26T04:46:56.000Z0.1https://pulsar-edit.dev/docs/packages/core/2022-09-23T19:36:40.000Zhttps://pulsar-edit.dev/docs/resources/conduct/2022-11-26T02:17:58.000Zhttps://pulsar-edit.dev/docs/resources/glossary/2022-09-23T19:36:40.000Zhttps://pulsar-edit.dev/docs/resources/privacy/2022-12-14T18:44:24.000Zhttps://pulsar-edit.dev/docs/resources/pulsar-api/2023-09-08T02:52:35.000Zhttps://pulsar-edit.dev/docs/resources/tooling/2022-11-08T18:28:47.000Zhttps://pulsar-edit.dev/docs/resources/website/2022-11-13T16:25:40.000Zhttps://pulsar-edit.dev/docs/atom-archive/atom-server-side-apis/sections/atom-package-server-api.html2022-11-06T00:05:10.000Zhttps://pulsar-edit.dev/docs/atom-archive/atom-server-side-apis/sections/atom-update-server-api.html2022-11-06T00:05:10.000Zhttps://pulsar-edit.dev/docs/atom-archive/behind-atom/sections/configuration-api.html2022-11-13T03:23:20.000Zhttps://pulsar-edit.dev/docs/atom-archive/behind-atom/sections/developing-node-modules.html2022-11-13T03:23:20.000Zhttps://pulsar-edit.dev/docs/atom-archive/behind-atom/sections/how-atom-uses-chromium-snapshots.html2022-11-13T03:23:20.000Zhttps://pulsar-edit.dev/docs/atom-archive/behind-atom/sections/interacting-with-other-packages-via-services.html2022-11-13T03:23:20.000Zhttps://pulsar-edit.dev/docs/atom-archive/behind-atom/sections/keymaps-in-depth.html2022-11-13T03:23:20.000Zhttps://pulsar-edit.dev/docs/atom-archive/behind-atom/sections/maintaining-your-packages.html2022-11-13T03:23:20.000Zhttps://pulsar-edit.dev/docs/atom-archive/behind-atom/sections/scoped-settings-scopes-and-scope-descriptors.html2022-11-13T03:23:20.000Zhttps://pulsar-edit.dev/docs/atom-archive/behind-atom/sections/serialization-in-atom.html2022-11-13T03:23:20.000Zhttps://pulsar-edit.dev/docs/atom-archive/behind-atom/sections/summary.html2022-11-13T03:23:20.000Zhttps://pulsar-edit.dev/docs/atom-archive/faq/sections/atom-in-the-cloud.html2022-11-05T23:29:45.000Zhttps://pulsar-edit.dev/docs/atom-archive/faq/sections/how-can-i-contribute-to-atom.html2022-11-05T23:29:45.000Zhttps://pulsar-edit.dev/docs/atom-archive/faq/sections/how-can-i-tell-if-subpixel-antialiasing-is-working.html2022-11-05T23:29:45.000Zhttps://pulsar-edit.dev/docs/atom-archive/faq/sections/how-do-i-accept-input-from-my-program-or-script-when-using-the-script-package.html2022-11-05T23:29:45.000Zhttps://pulsar-edit.dev/docs/atom-archive/faq/sections/how-do-i-build-or-execute-code-i-ve-written-in-atom.html2022-11-05T23:29:45.000Zhttps://pulsar-edit.dev/docs/atom-archive/faq/sections/how-do-i-make-atom-recognize-a-file-with-extension-x-as-language-y.html2022-11-05T23:29:45.000Zhttps://pulsar-edit.dev/docs/atom-archive/faq/sections/how-do-i-make-the-welcome-screen-stop-showing-up.html2022-11-05T23:29:45.000Zhttps://pulsar-edit.dev/docs/atom-archive/faq/sections/how-do-i-preview-web-page-changes-automatically.html2022-11-05T23:29:45.000Zhttps://pulsar-edit.dev/docs/atom-archive/faq/sections/how-do-i-turn-on-line-wrap.html2022-11-05T23:29:45.000Zhttps://pulsar-edit.dev/docs/atom-archive/faq/sections/how-do-i-uninstall-atom-on-macos.html2022-11-05T23:29:45.000Zhttps://pulsar-edit.dev/docs/atom-archive/faq/sections/how-do-i-use-a-newline-in-the-result-of-find-and-replace.html2022-11-05T23:29:45.000Zhttps://pulsar-edit.dev/docs/atom-archive/faq/sections/i-am-unable-to-update-to-the-latest-version-of-atom-on-macos-how-do-i-fix-this.html2022-11-05T23:29:45.000Zhttps://pulsar-edit.dev/docs/atom-archive/faq/sections/i-have-a-question-about-a-specific-atom-community-package-where-is-the-best-place-to-ask-it.html2022-11-05T23:29:45.000Zhttps://pulsar-edit.dev/docs/atom-archive/faq/sections/i-m-getting-an-error-about-a-self-signed-certificate-what-do-i-do.html2022-11-05T23:29:45.000Zhttps://pulsar-edit.dev/docs/atom-archive/faq/sections/i-m-having-a-problem-with-julia-what-do-i-do.html2022-11-05T23:29:45.000Zhttps://pulsar-edit.dev/docs/atom-archive/faq/sections/i-m-having-a-problem-with-platformio-what-do-i-do.html2022-11-05T23:29:45.000Zhttps://pulsar-edit.dev/docs/atom-archive/faq/sections/i-m-trying-to-change-my-syntax-colors-from-styles-less-but-it-isn-t-working.html2022-11-05T23:29:45.000Zhttps://pulsar-edit.dev/docs/atom-archive/faq/sections/i-m-using-an-international-keyboard-and-keys-that-use-altgr-or-ctrl-alt-aren-t-working.html2022-11-05T23:29:45.000Zhttps://pulsar-edit.dev/docs/atom-archive/faq/sections/is-atom-open-source.html2022-11-05T23:29:45.000Zhttps://pulsar-edit.dev/docs/atom-archive/faq/sections/macos-mojave-font-rendering-change.html2022-11-05T23:29:45.000Zhttps://pulsar-edit.dev/docs/atom-archive/faq/sections/the-menu-bar-disappeared-how-do-i-get-it-back.html2022-11-05T23:29:45.000Zhttps://pulsar-edit.dev/docs/atom-archive/faq/sections/what-does-atom-cost.html2022-11-05T23:29:45.000Zhttps://pulsar-edit.dev/docs/atom-archive/faq/sections/what-does-safe-mode-do.html2023-03-05T18:56:40.000Zhttps://pulsar-edit.dev/docs/atom-archive/faq/sections/what-is-this-line-on-the-right-in-the-editor-view.html2022-11-05T23:29:45.000Zhttps://pulsar-edit.dev/docs/atom-archive/faq/sections/what-platforms-does-atom-run-on.html2022-11-05T23:29:45.000Zhttps://pulsar-edit.dev/docs/atom-archive/faq/sections/what-s-the-difference-between-an-ide-and-an-editor.html2022-11-05T23:29:45.000Zhttps://pulsar-edit.dev/docs/atom-archive/faq/sections/why-does-atom-collect-usage-data.html2022-11-05T23:29:45.000Zhttps://pulsar-edit.dev/docs/atom-archive/faq/sections/why-does-macos-say-that-atom-wants-to-access-my-calendar-contacts-photos-etc.html2022-11-05T23:29:45.000Zhttps://pulsar-edit.dev/docs/atom-archive/faq/sections/why-is-atom-deleting-trailing-whitespace-why-is-there-a-newline-at-the-end-of-the-file.html2022-11-05T23:29:45.000Zhttps://pulsar-edit.dev/docs/atom-archive/getting-started/sections/atom-basics.html2022-11-13T03:23:20.000Zhttps://pulsar-edit.dev/docs/atom-archive/getting-started/sections/installing-atom.html2022-09-25T05:21:28.000Zhttps://pulsar-edit.dev/docs/atom-archive/getting-started/sections/summary.html2022-09-23T19:36:40.000Zhttps://pulsar-edit.dev/docs/atom-archive/getting-started/sections/why-atom.html2022-09-23T19:36:40.000Zhttps://pulsar-edit.dev/docs/atom-archive/hacking-atom/sections/contributing-to-official-atom-packages.html2022-09-24T20:11:14.000Zhttps://pulsar-edit.dev/docs/atom-archive/hacking-atom/sections/converting-from-textmate.html2022-09-24T20:11:14.000Zhttps://pulsar-edit.dev/docs/atom-archive/hacking-atom/sections/creating-a-fork-of-a-core-package-in-atom-atom.html2022-11-13T03:28:34.000Zhttps://pulsar-edit.dev/docs/atom-archive/hacking-atom/sections/creating-a-grammar.html2022-09-24T20:11:14.000Zhttps://pulsar-edit.dev/docs/atom-archive/hacking-atom/sections/creating-a-legacy-textmate-grammar.html2022-09-24T20:11:14.000Zhttps://pulsar-edit.dev/docs/atom-archive/hacking-atom/sections/creating-a-theme.html2022-09-24T20:11:14.000Zhttps://pulsar-edit.dev/docs/atom-archive/hacking-atom/sections/cross-platform-compatibility.html2022-09-24T20:11:14.000Zhttps://pulsar-edit.dev/docs/atom-archive/hacking-atom/sections/debugging.html2022-09-24T20:11:14.000Zhttps://pulsar-edit.dev/docs/atom-archive/hacking-atom/sections/hacking-on-atom-core.html2022-09-24T20:11:14.000Zhttps://pulsar-edit.dev/docs/atom-archive/hacking-atom/sections/handling-uris.html2022-09-24T20:11:14.000Zhttps://pulsar-edit.dev/docs/atom-archive/hacking-atom/sections/iconography.html2022-09-24T20:11:14.000Zhttps://pulsar-edit.dev/docs/atom-archive/hacking-atom/sections/maintaining-a-fork-of-a-core-package-in-atom-atom.html2022-09-24T20:11:14.000Zhttps://pulsar-edit.dev/docs/atom-archive/hacking-atom/sections/package-active-editor-info.html2022-09-24T20:11:14.000Zhttps://pulsar-edit.dev/docs/atom-archive/hacking-atom/sections/package-modifying-text.html2022-09-24T20:11:14.000Zhttps://pulsar-edit.dev/docs/atom-archive/hacking-atom/sections/package-word-count.html2022-09-24T20:11:14.000Zhttps://pulsar-edit.dev/docs/atom-archive/hacking-atom/sections/publishing.html2022-09-24T20:11:14.000Zhttps://pulsar-edit.dev/docs/atom-archive/hacking-atom/sections/summary.html2022-09-24T20:11:14.000Zhttps://pulsar-edit.dev/docs/atom-archive/hacking-atom/sections/the-init-file.html2022-09-24T20:11:14.000Zhttps://pulsar-edit.dev/docs/atom-archive/hacking-atom/sections/tools-of-the-trade.html2022-09-24T20:11:14.000Zhttps://pulsar-edit.dev/docs/atom-archive/hacking-atom/sections/writing-specs.html2022-09-24T20:11:14.000Zhttps://pulsar-edit.dev/docs/atom-archive/resources/sections/glossary.html2022-11-05T23:41:38.000Zhttps://pulsar-edit.dev/docs/atom-archive/shadow-dom/sections/removing-shadow-dom-styles.html2022-11-05T23:46:24.000Zhttps://pulsar-edit.dev/docs/atom-archive/upgrading-to-1-0-apis/sections/upgrading-your-package.html2022-11-05T23:58:43.000Zhttps://pulsar-edit.dev/docs/atom-archive/upgrading-to-1-0-apis/sections/upgrading-your-syntax-theme.html2022-11-05T23:58:43.000Zhttps://pulsar-edit.dev/docs/atom-archive/upgrading-to-1-0-apis/sections/upgrading-your-ui-theme-or-package-selectors.html2022-11-05T23:58:43.000Zhttps://pulsar-edit.dev/docs/atom-archive/using-atom/sections/atom-packages.html2022-10-01T14:27:23.000Zhttps://pulsar-edit.dev/docs/atom-archive/using-atom/sections/atom-selections.html2022-10-01T14:27:23.000Zhttps://pulsar-edit.dev/docs/atom-archive/using-atom/sections/autocomplete.html2022-10-01T14:27:23.000Zhttps://pulsar-edit.dev/docs/atom-archive/using-atom/sections/basic-customization.html2022-10-01T14:27:23.000Zhttps://pulsar-edit.dev/docs/atom-archive/using-atom/sections/editing-and-deleting-text.html2022-10-01T14:27:23.000Zhttps://pulsar-edit.dev/docs/atom-archive/using-atom/sections/find-and-replace.html2022-10-01T14:27:23.000Zhttps://pulsar-edit.dev/docs/atom-archive/using-atom/sections/folding.html2022-10-01T14:27:23.000Zhttps://pulsar-edit.dev/docs/atom-archive/using-atom/sections/github-package.html2022-10-01T14:27:23.000Zhttps://pulsar-edit.dev/docs/atom-archive/using-atom/sections/grammar.html2022-10-01T14:27:23.000Zhttps://pulsar-edit.dev/docs/atom-archive/using-atom/sections/moving-in-atom.html2022-10-01T14:27:23.000Zhttps://pulsar-edit.dev/docs/atom-archive/using-atom/sections/panes.html2022-10-01T14:27:23.000Zhttps://pulsar-edit.dev/docs/atom-archive/using-atom/sections/pending-pane-items.html2022-10-01T14:27:23.000Zhttps://pulsar-edit.dev/docs/atom-archive/using-atom/sections/snippets.html2022-10-01T14:27:23.000Zhttps://pulsar-edit.dev/docs/atom-archive/using-atom/sections/summary.html2022-10-01T14:27:23.000Zhttps://pulsar-edit.dev/docs/atom-archive/using-atom/sections/version-control-in-atom.html2022-10-01T14:27:23.000Zhttps://pulsar-edit.dev/docs/atom-archive/using-atom/sections/writing-in-atom.html2022-10-01T14:27:23.000Zhttps://pulsar-edit.dev/docs/launch-manual/sections/behind-pulsar/2023-09-08T02:52:35.000Zhttps://pulsar-edit.dev/docs/launch-manual/sections/core-hacking/2023-09-08T02:52:35.000Zhttps://pulsar-edit.dev/docs/launch-manual/sections/faq/2023-09-08T02:52:35.000Zhttps://pulsar-edit.dev/docs/launch-manual/sections/getting-started/2022-12-27T01:35:19.000Zhttps://pulsar-edit.dev/docs/launch-manual/sections/using-pulsar/2023-09-08T02:52:35.000Zhttps://pulsar-edit.dev/docs/packages/core/atom-languageclient/2022-09-28T21:04:52.000Zhttps://pulsar-edit.dev/docs/packages/core/atom-languageclient/list.html2022-09-28T21:04:52.000Zhttps://pulsar-edit.dev/docs/packages/core/atom-languageclient/release-process.html2022-09-19T19:49:02.000Zhttps://pulsar-edit.dev/docs/packages/core/autocomplete-plus/autocomplete-providers.html2022-09-23T22:03:34.000Zhttps://pulsar-edit.dev/docs/packages/core/autocomplete-plus/2022-09-28T21:04:52.000Zhttps://pulsar-edit.dev/docs/packages/core/autocomplete-plus/provider-api.html2022-09-28T21:04:52.000Zhttps://pulsar-edit.dev/docs/packages/core/autocomplete-plus/symbolprovider-config-api.html2022-09-23T22:03:34.000Zhttps://pulsar-edit.dev/docs/packages/core/github/2022-09-29T00:34:32.000Zhttps://pulsar-edit.dev/docs/packages/core/github/june-2017.html2022-09-28T21:04:52.000Zhttps://pulsar-edit.dev/docs/packages/core/ide-java/incomplete-classpath-warning.html2022-09-28T21:04:52.000Zhttps://pulsar-edit.dev/docs/packages/core/ide-java/2022-09-28T21:04:52.000Zhttps://pulsar-edit.dev/docs/resources/website/sections/blog-guide.html2022-11-16T13:55:27.000Zhttps://pulsar-edit.dev/docs/resources/website/sections/building.html2022-11-13T20:29:01.000Zhttps://pulsar-edit.dev/docs/resources/website/sections/configuration-files.html2022-11-13T20:29:09.000Zhttps://pulsar-edit.dev/docs/resources/website/sections/document-style.html2022-11-13T16:25:40.000Zhttps://pulsar-edit.dev/docs/resources/website/sections/file-organization.html2022-12-14T18:56:35.000Zhttps://pulsar-edit.dev/docs/launch-manual/sections/behind-pulsar/sections/configuration-api.html2022-11-17T18:51:12.000Zhttps://pulsar-edit.dev/docs/launch-manual/sections/behind-pulsar/sections/developing-node-modules.html2022-11-21T00:59:36.000Zhttps://pulsar-edit.dev/docs/launch-manual/sections/behind-pulsar/sections/interacting-with-other-packages-via-services.html2022-11-17T18:51:12.000Zhttps://pulsar-edit.dev/docs/launch-manual/sections/behind-pulsar/sections/keymaps-in-depth.html2022-11-17T18:51:12.000Zhttps://pulsar-edit.dev/docs/launch-manual/sections/behind-pulsar/sections/maintaining-your-packages.html2022-11-21T01:00:24.000Zhttps://pulsar-edit.dev/docs/launch-manual/sections/behind-pulsar/sections/scoped-settings-scopes-and-scope-descriptors.html2022-11-17T18:51:12.000Zhttps://pulsar-edit.dev/docs/launch-manual/sections/behind-pulsar/sections/serialization-in-pulsar.html2022-11-17T19:18:21.000Zhttps://pulsar-edit.dev/docs/launch-manual/sections/behind-pulsar/sections/summary.html2022-11-17T18:51:12.000Zhttps://pulsar-edit.dev/docs/launch-manual/sections/core-hacking/sections/building-pulsar.html2023-01-07T00:10:23.000Zhttps://pulsar-edit.dev/docs/launch-manual/sections/core-hacking/sections/contributing-to-official-pulsar-packages.html2022-12-12T00:44:13.000Zhttps://pulsar-edit.dev/docs/launch-manual/sections/core-hacking/sections/converting-from-textmate.html2022-12-08T02:34:07.000Zhttps://pulsar-edit.dev/docs/launch-manual/sections/core-hacking/sections/creating-a-fork-of-a-core-package.html2022-12-12T17:06:55.000Zhttps://pulsar-edit.dev/docs/launch-manual/sections/core-hacking/sections/creating-a-grammar.html2022-12-08T02:34:07.000Zhttps://pulsar-edit.dev/docs/launch-manual/sections/core-hacking/sections/creating-a-legacy-textmate-grammar.html2022-12-08T02:34:07.000Zhttps://pulsar-edit.dev/docs/launch-manual/sections/core-hacking/sections/creating-a-theme.html2022-12-12T02:16:46.000Zhttps://pulsar-edit.dev/docs/launch-manual/sections/core-hacking/sections/cross-platform-compatibility.html2022-12-08T02:34:07.000Zhttps://pulsar-edit.dev/docs/launch-manual/sections/core-hacking/sections/debugging.html2024-01-29T04:54:41.000Zhttps://pulsar-edit.dev/docs/launch-manual/sections/core-hacking/sections/hacking-on-the-core.html2022-12-08T02:34:07.000Zhttps://pulsar-edit.dev/docs/launch-manual/sections/core-hacking/sections/handling-uris.html2022-12-08T02:34:07.000Zhttps://pulsar-edit.dev/docs/launch-manual/sections/core-hacking/sections/iconography.html2022-12-08T02:34:07.000Zhttps://pulsar-edit.dev/docs/launch-manual/sections/core-hacking/sections/maintaining-a-fork-of-a-core-package.html2022-12-14T18:56:35.000Zhttps://pulsar-edit.dev/docs/launch-manual/sections/core-hacking/sections/package-active-editor-info.html2022-12-08T02:34:07.000Zhttps://pulsar-edit.dev/docs/launch-manual/sections/core-hacking/sections/package-modifying-text.html2022-12-08T02:34:07.000Zhttps://pulsar-edit.dev/docs/launch-manual/sections/core-hacking/sections/package-word-count.html2022-12-08T02:34:07.000Zhttps://pulsar-edit.dev/docs/launch-manual/sections/core-hacking/sections/publishing.html2022-12-12T17:03:29.000Zhttps://pulsar-edit.dev/docs/launch-manual/sections/core-hacking/sections/summary.html2022-12-08T02:34:07.000Zhttps://pulsar-edit.dev/docs/launch-manual/sections/core-hacking/sections/the-init-file.html2022-12-08T02:34:07.000Zhttps://pulsar-edit.dev/docs/launch-manual/sections/core-hacking/sections/tools-of-the-trade.html2022-12-12T00:48:10.000Zhttps://pulsar-edit.dev/docs/launch-manual/sections/core-hacking/sections/using-ppm.html2022-12-12T02:36:51.000Zhttps://pulsar-edit.dev/docs/launch-manual/sections/core-hacking/sections/writing-specs.html2022-12-08T02:34:07.000Zhttps://pulsar-edit.dev/docs/launch-manual/sections/faq/sections/common-issues.html2023-02-16T07:42:19.000Zhttps://pulsar-edit.dev/docs/launch-manual/sections/faq/sections/get-help.html2023-09-16T16:59:07.000Zhttps://pulsar-edit.dev/docs/launch-manual/sections/getting-started/sections/installing-pulsar.html2023-04-05T20:07:58.000Zhttps://pulsar-edit.dev/docs/launch-manual/sections/getting-started/sections/pulsar-basics.html2023-05-03T10:29:37.000Zhttps://pulsar-edit.dev/docs/launch-manual/sections/getting-started/sections/summary.html2022-12-13T19:59:39.000Zhttps://pulsar-edit.dev/docs/launch-manual/sections/getting-started/sections/why-pulsar.html2022-12-13T19:59:39.000Zhttps://pulsar-edit.dev/docs/launch-manual/sections/using-pulsar/sections/autocomplete.html2022-11-23T18:50:14.000Zhttps://pulsar-edit.dev/docs/launch-manual/sections/using-pulsar/sections/basic-customization.html2022-11-23T18:50:14.000Zhttps://pulsar-edit.dev/docs/launch-manual/sections/using-pulsar/sections/editing-and-deleting-text.html2022-11-23T18:50:14.000Zhttps://pulsar-edit.dev/docs/launch-manual/sections/using-pulsar/sections/find-and-replace.html2023-03-11T13:40:51.000Zhttps://pulsar-edit.dev/docs/launch-manual/sections/using-pulsar/sections/folding.html2022-11-23T18:50:14.000Zhttps://pulsar-edit.dev/docs/launch-manual/sections/using-pulsar/sections/github-package.html2022-11-23T18:50:14.000Zhttps://pulsar-edit.dev/docs/launch-manual/sections/using-pulsar/sections/grammar.html2022-11-23T18:50:14.000Zhttps://pulsar-edit.dev/docs/launch-manual/sections/using-pulsar/sections/moving-in-pulsar.html2022-11-23T18:50:14.000Zhttps://pulsar-edit.dev/docs/launch-manual/sections/using-pulsar/sections/panes.html2022-11-23T18:50:14.000Zhttps://pulsar-edit.dev/docs/launch-manual/sections/using-pulsar/sections/pending-pane-items.html2022-11-23T18:50:14.000Zhttps://pulsar-edit.dev/docs/launch-manual/sections/using-pulsar/sections/pulsar-packages.html2022-11-29T17:38:06.000Zhttps://pulsar-edit.dev/docs/launch-manual/sections/using-pulsar/sections/pulsar-selections.html2022-11-23T18:50:14.000Zhttps://pulsar-edit.dev/docs/launch-manual/sections/using-pulsar/sections/snippets.html2022-11-23T18:50:14.000Zhttps://pulsar-edit.dev/docs/launch-manual/sections/using-pulsar/sections/summary.html2022-11-23T18:50:14.000Zhttps://pulsar-edit.dev/docs/launch-manual/sections/using-pulsar/sections/version-control-in-pulsar.html2022-11-23T18:50:14.000Zhttps://pulsar-edit.dev/docs/launch-manual/sections/using-pulsar/sections/writing-in-pulsar.html2022-11-23T18:50:14.000Zhttps://pulsar-edit.dev/category/https://pulsar-edit.dev/tag/https://pulsar-edit.dev/blog/https://pulsar-edit.dev/encrypted/https://pulsar-edit.dev/slide/https://pulsar-edit.dev/star/https://pulsar-edit.dev/timeline/https://pulsar-edit.dev/category/dev/https://pulsar-edit.dev/tag/backend/https://pulsar-edit.dev/category/log/https://pulsar-edit.dev/tag/sunset/https://pulsar-edit.dev/category/news/https://pulsar-edit.dev/tag/video/https://pulsar-edit.dev/category/survey/https://pulsar-edit.dev/tag/update/https://pulsar-edit.dev/tag/modernization/https://pulsar-edit.dev/tag/tree-sitter/https://pulsar-edit.dev/tag/release/https://pulsar-edit.dev/tag/releases/https://pulsar-edit.dev/tag/rolling/https://pulsar-edit.dev/tag/regular/https://pulsar-edit.dev/tag/community/https://pulsar-edit.dev/tag/socials/https://pulsar-edit.dev/tag/joke/https://pulsar-edit.dev/tag/feedback/https://pulsar-edit.dev/tag/github/https://pulsar-edit.dev/tag/stars/https://pulsar-edit.dev/tag/windows/https://pulsar-edit.dev/tag/chocolatey/https://pulsar-edit.dev/tag/package-manager/https://pulsar-edit.dev/tag/ci/https://pulsar-edit.dev/tag/electron/ \ No newline at end of file diff --git a/slide/index.html b/slide/index.html new file mode 100644 index 0000000000..5d7988d0a2 --- /dev/null +++ b/slide/index.html @@ -0,0 +1,223 @@ + + + + + + + + Slides | + + + + + + +
    + + + diff --git a/star/index.html b/star/index.html new file mode 100644 index 0000000000..f1fecf3b7c --- /dev/null +++ b/star/index.html @@ -0,0 +1,223 @@ + + + + + + + + Star | + + + + + + +
    + + + diff --git a/tag/backend/index.html b/tag/backend/index.html new file mode 100644 index 0000000000..9b0bc29651 --- /dev/null +++ b/tag/backend/index.html @@ -0,0 +1,226 @@ + + + + + + + + backend Tag | + + + + + + + + + + diff --git a/tag/chocolatey/index.html b/tag/chocolatey/index.html new file mode 100644 index 0000000000..01e2efe586 --- /dev/null +++ b/tag/chocolatey/index.html @@ -0,0 +1,225 @@ + + + + + + + + chocolatey Tag | + + + + + + + + + + diff --git a/tag/ci/index.html b/tag/ci/index.html new file mode 100644 index 0000000000..b1039df5a9 --- /dev/null +++ b/tag/ci/index.html @@ -0,0 +1,224 @@ + + + + + + + + ci Tag | + + + + + + + + + + diff --git a/tag/community/index.html b/tag/community/index.html new file mode 100644 index 0000000000..aaba18b6be --- /dev/null +++ b/tag/community/index.html @@ -0,0 +1,228 @@ + + + + + + + + community Tag | + + + + + + +
    + + + diff --git a/tag/electron/index.html b/tag/electron/index.html new file mode 100644 index 0000000000..2538e03d5f --- /dev/null +++ b/tag/electron/index.html @@ -0,0 +1,223 @@ + + + + + + + + electron Tag | + + + + + + + + + + diff --git a/tag/feedback/index.html b/tag/feedback/index.html new file mode 100644 index 0000000000..3bd3824802 --- /dev/null +++ b/tag/feedback/index.html @@ -0,0 +1,224 @@ + + + + + + + + feedback Tag | + + + + + + + + + + diff --git a/tag/github/index.html b/tag/github/index.html new file mode 100644 index 0000000000..8d0f97c4cc --- /dev/null +++ b/tag/github/index.html @@ -0,0 +1,224 @@ + + + + + + + + github Tag | + + + + + + + + + + diff --git a/tag/index.html b/tag/index.html new file mode 100644 index 0000000000..5d9fe4a128 --- /dev/null +++ b/tag/index.html @@ -0,0 +1,223 @@ + + + + + + + + Tag | + + + + + + + + + + diff --git a/tag/joke/index.html b/tag/joke/index.html new file mode 100644 index 0000000000..99ff9c7314 --- /dev/null +++ b/tag/joke/index.html @@ -0,0 +1,224 @@ + + + + + + + + joke Tag | + + + + + + + + + + diff --git a/tag/modernization/index.html b/tag/modernization/index.html new file mode 100644 index 0000000000..966f6caa20 --- /dev/null +++ b/tag/modernization/index.html @@ -0,0 +1,235 @@ + + + + + + + + modernization Tag | + + + + + + +
    Modern Tree-sitter, part 5: injections

    One annoying thing that software developers do is insist on writing in more than one language at once. Web developers are espeically obnoxious about this — routinely, for instance, putting CSS inside their HTML, or HTML inside their JavaScript, or CSS inside their HTML inside their JavaScript.

    +

    Code editors like Pulsar need to roll with this, so today we’ll talk about how the modern Tree-sitter system handles what we call injections.

    +

    savetheclocktowerNovember 10, 2023
    • dev
    • modernization
    • tree-sitter
    About 16 min
    Modern Tree-sitter, part 4: indentation and code folding

    Last time we looked at Tree-sitter’s query system and showed how it can be used to make a syntax highlighting engine in Pulsar. But syntax highlighting is simply the most visible of the various tasks that a language package performs.

    +

    Today we’ll look at two other systems — indentation hinting and code folding — and I’ll explain how queries can be used to support each one.

    +

    savetheclocktowerOctober 31, 2023
    • dev
    • modernization
    • tree-sitter
    About 13 min
    Modern Tree-sitter, part 3: syntax highlighting via queries

    Last time I laid out the case for why we chose to embrace TextMate-style scope names, even in newer Tree-sitter grammars. I set a difficult challenge for Pulsar: make it so that a Tree-sitter grammar can do anything a TextMate grammar can do.

    +

    Today I’d like to show you the specific problems that we had to solve in order to pull that off.

    +

    savetheclocktowerOctober 13, 2023
    • dev
    • modernization
    • tree-sitter
    About 16 min
    + + + diff --git a/tag/package-manager/index.html b/tag/package-manager/index.html new file mode 100644 index 0000000000..9811a05551 --- /dev/null +++ b/tag/package-manager/index.html @@ -0,0 +1,225 @@ + + + + + + + + package manager Tag | + + + + + + + + + + diff --git a/tag/regular/index.html b/tag/regular/index.html new file mode 100644 index 0000000000..9a7a270d9f --- /dev/null +++ b/tag/regular/index.html @@ -0,0 +1,224 @@ + + + + + + + + regular Tag | + + + + + + + + + + diff --git a/tag/release/index.html b/tag/release/index.html new file mode 100644 index 0000000000..b6ed6fec6e --- /dev/null +++ b/tag/release/index.html @@ -0,0 +1,233 @@ + + + + + + + + release Tag | + + + + + + +
    2
    + + + diff --git a/tag/releases/index.html b/tag/releases/index.html new file mode 100644 index 0000000000..69dd09d3f8 --- /dev/null +++ b/tag/releases/index.html @@ -0,0 +1,224 @@ + + + + + + + + releases Tag | + + + + + + + + + + diff --git a/tag/rolling/index.html b/tag/rolling/index.html new file mode 100644 index 0000000000..f1711d97a6 --- /dev/null +++ b/tag/rolling/index.html @@ -0,0 +1,224 @@ + + + + + + + + rolling Tag | + + + + + + + + + + diff --git a/tag/socials/index.html b/tag/socials/index.html new file mode 100644 index 0000000000..fdb3da6835 --- /dev/null +++ b/tag/socials/index.html @@ -0,0 +1,227 @@ + + + + + + + + socials Tag | + + + + + + + + + + diff --git a/tag/stars/index.html b/tag/stars/index.html new file mode 100644 index 0000000000..fa472b5b65 --- /dev/null +++ b/tag/stars/index.html @@ -0,0 +1,224 @@ + + + + + + + + stars Tag | + + + + + + + + + + diff --git a/tag/sunset/index.html b/tag/sunset/index.html new file mode 100644 index 0000000000..0677f68abb --- /dev/null +++ b/tag/sunset/index.html @@ -0,0 +1,226 @@ + + + + + + + + sunset Tag | + + + + + + + + + + diff --git a/tag/tree-sitter/index.html b/tag/tree-sitter/index.html new file mode 100644 index 0000000000..591f21a5f4 --- /dev/null +++ b/tag/tree-sitter/index.html @@ -0,0 +1,235 @@ + + + + + + + + tree-sitter Tag | + + + + + + +
    Modern Tree-sitter, part 5: injections

    One annoying thing that software developers do is insist on writing in more than one language at once. Web developers are espeically obnoxious about this — routinely, for instance, putting CSS inside their HTML, or HTML inside their JavaScript, or CSS inside their HTML inside their JavaScript.

    +

    Code editors like Pulsar need to roll with this, so today we’ll talk about how the modern Tree-sitter system handles what we call injections.

    +

    savetheclocktowerNovember 10, 2023
    • dev
    • modernization
    • tree-sitter
    About 16 min
    Modern Tree-sitter, part 4: indentation and code folding

    Last time we looked at Tree-sitter’s query system and showed how it can be used to make a syntax highlighting engine in Pulsar. But syntax highlighting is simply the most visible of the various tasks that a language package performs.

    +

    Today we’ll look at two other systems — indentation hinting and code folding — and I’ll explain how queries can be used to support each one.

    +

    savetheclocktowerOctober 31, 2023
    • dev
    • modernization
    • tree-sitter
    About 13 min
    Modern Tree-sitter, part 3: syntax highlighting via queries

    Last time I laid out the case for why we chose to embrace TextMate-style scope names, even in newer Tree-sitter grammars. I set a difficult challenge for Pulsar: make it so that a Tree-sitter grammar can do anything a TextMate grammar can do.

    +

    Today I’d like to show you the specific problems that we had to solve in order to pull that off.

    +

    savetheclocktowerOctober 13, 2023
    • dev
    • modernization
    • tree-sitter
    About 16 min
    + + + diff --git a/tag/update/index.html b/tag/update/index.html new file mode 100644 index 0000000000..797db07ba3 --- /dev/null +++ b/tag/update/index.html @@ -0,0 +1,233 @@ + + + + + + + + update Tag | + + + + + + +
    2
    + + + diff --git a/tag/video/index.html b/tag/video/index.html new file mode 100644 index 0000000000..726c6f2c1f --- /dev/null +++ b/tag/video/index.html @@ -0,0 +1,225 @@ + + + + + + + + video Tag | + + + + + + + + + + diff --git a/tag/windows/index.html b/tag/windows/index.html new file mode 100644 index 0000000000..405ec5f4e6 --- /dev/null +++ b/tag/windows/index.html @@ -0,0 +1,225 @@ + + + + + + + + windows Tag | + + + + + + + + + + diff --git a/text-only-light.svg b/text-only-light.svg new file mode 100644 index 0000000000..275597f6d4 --- /dev/null +++ b/text-only-light.svg @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/timeline/index.html b/timeline/index.html new file mode 100644 index 0000000000..6bfaf32e27 --- /dev/null +++ b/timeline/index.html @@ -0,0 +1,223 @@ + + + + + + + + Timeline | + + + + + + +
    + + + diff --git a/tutanota-logo.webp b/tutanota-logo.webp new file mode 100644 index 0000000000..0ba1729e80 Binary files /dev/null and b/tutanota-logo.webp differ